]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6370 isolate plugin classloader from core
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Fri, 24 Apr 2015 07:15:05 +0000 (09:15 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Mon, 11 May 2015 08:21:55 +0000 (10:21 +0200)
177 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/charts/package-info.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/package-info.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/notifications/alerts/package-info.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/package-info.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/package-info.java
pom.xml
server/sonar-server/pom.xml
server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContainer.java
server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java
server/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java
server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java
server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java
server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java
server/sonar-server/src/main/java/org/sonar/server/plugins/InstalledPluginReferentialFactory.java
server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java
server/sonar-server/src/main/java/org/sonar/server/plugins/PluginReferentialMetadataConverter.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarInstaller.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarsInstaller.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginUnzipper.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/plugins/StaticResourcesServlet.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ws/CancelAllPluginsWsAction.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledPluginsWsAction.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingPluginsWsAction.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UninstallPluginsWsAction.java
server/sonar-server/src/main/java/org/sonar/server/search/IndexQueue.java
server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java
server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
server/sonar-server/src/test/java/org/sonar/server/debt/DebtModelPluginRepositoryTest.java
server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java
server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerFileSystemTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/RailsAppsDeployerTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/SonarHomeTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/InstalledPluginReferentialFactoryTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/MimeTypesTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/PluginReferentialMetadataConverterTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarsInstallerTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginUnzipperTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/plugins/TestProjectUtils.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/CancelAllPluginsWsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledPluginsWsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingPluginsWsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UninstallPluginsWsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
server/sonar-server/src/test/projects/.gitignore [new file with mode: 0644]
server/sonar-server/src/test/projects/README.txt [new file with mode: 0644]
server/sonar-server/src/test/projects/pom.xml [new file with mode: 0644]
server/sonar-server/src/test/projects/test-base-plugin-v2/pom.xml [new file with mode: 0644]
server/sonar-server/src/test/projects/test-base-plugin-v2/src/BasePlugin.java [new file with mode: 0644]
server/sonar-server/src/test/projects/test-base-plugin-v2/target/test-base-plugin-0.2-SNAPSHOT.jar [new file with mode: 0644]
server/sonar-server/src/test/projects/test-base-plugin/pom.xml [new file with mode: 0644]
server/sonar-server/src/test/projects/test-base-plugin/src/BasePlugin.java [new file with mode: 0644]
server/sonar-server/src/test/projects/test-base-plugin/target/test-base-plugin-0.1-SNAPSHOT.jar [new file with mode: 0644]
server/sonar-server/src/test/projects/test-core-plugin/pom.xml [new file with mode: 0644]
server/sonar-server/src/test/projects/test-core-plugin/src/CorePlugin.java [new file with mode: 0644]
server/sonar-server/src/test/projects/test-core-plugin/target/test-core-plugin-0.1-SNAPSHOT.jar [new file with mode: 0644]
server/sonar-server/src/test/projects/test-extend-plugin/pom.xml [new file with mode: 0644]
server/sonar-server/src/test/projects/test-extend-plugin/src/ExtendPlugin.java [new file with mode: 0644]
server/sonar-server/src/test/projects/test-extend-plugin/target/test-extend-plugin-0.1-SNAPSHOT.jar [new file with mode: 0644]
server/sonar-server/src/test/projects/test-libs-plugin/pom.xml [new file with mode: 0644]
server/sonar-server/src/test/projects/test-libs-plugin/src/LibsPlugin.java [new file with mode: 0644]
server/sonar-server/src/test/projects/test-libs-plugin/target/test-libs-plugin-0.1-SNAPSHOT.jar [new file with mode: 0644]
server/sonar-server/src/test/projects/test-require-plugin/pom.xml [new file with mode: 0644]
server/sonar-server/src/test/projects/test-require-plugin/src/RequirePlugin.java [new file with mode: 0644]
server/sonar-server/src/test/projects/test-require-plugin/target/test-require-plugin-0.1-SNAPSHOT.jar [new file with mode: 0644]
server/sonar-server/src/test/projects/test-requirenew-plugin/pom.xml [new file with mode: 0644]
server/sonar-server/src/test/projects/test-requirenew-plugin/src/RequirePlugin.java [new file with mode: 0644]
server/sonar-server/src/test/projects/test-requirenew-plugin/target/test-requirenew-plugin-0.1-SNAPSHOT.jar [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/extension.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/foo-plugin.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/sonar-build-breaker-plugin-0.1.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginDownloaderTest/foo-plugin-1.0.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version1/extension.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version2/extension.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-1.0.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-2.0.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/not-a-plugin.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/old-plugin.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataTest/foo-plugin.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/bar-plugin-1.0.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-1.0.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-2.0.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/not-a-plugin.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/require-sq-2.5.jar [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginRepositoryTest/sonar-artifact-size-plugin-0.2.jar [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/updatecenter_controller.rb
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarInstaller.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginUnzipper.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/DefaultPluginsRepository.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginsRepository.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java
sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java
sonar-batch/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchExtensionDictionnaryTest.java
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/bootstrap/DefaultPluginsRepositoryTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java
sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalContainerTest.java
sonar-batch/src/test/java/org/sonar/batch/deprecated/decorator/DecoratorsSelectorTest.java
sonar-batch/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java
sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest/sonar-checkstyle-plugin-2.8.jar [deleted file]
sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/sonar-checkstyle-plugin-2.8.jar [new file with mode: 0644]
sonar-check-api/src/main/java/org/sonar/check/package-info.java
sonar-colorizer/src/main/java/org/sonar/colorizer/package-info.java
sonar-core/pom.xml
sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java
sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java
sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java
sonar-core/src/main/java/org/sonar/core/notification/package-info.java
sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/platform/package-info.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/plugins/DefaultPluginMetadata.java [deleted file]
sonar-core/src/main/java/org/sonar/core/plugins/PluginClassloaders.java [deleted file]
sonar-core/src/main/java/org/sonar/core/plugins/PluginJarInstaller.java [deleted file]
sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java
sonar-core/src/main/java/org/sonar/core/plugins/ResourcesClassloader.java [deleted file]
sonar-core/src/main/java/org/sonar/core/plugins/package-info.java
sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java
sonar-core/src/test/java/org/sonar/core/i18n/I18nClassloaderTest.java
sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/platform/ComponentKeysTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/plugins/PluginClassloadersTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/plugins/PluginJarInstallerTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/plugins/ResourcesClassloaderTest.java [deleted file]
sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jar [deleted file]
sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/foo.jar [deleted file]
sonar-core/src/test/resources/org/sonar/core/plugins/checkstyle-extension.xml [deleted file]
sonar-core/src/test/resources/org/sonar/core/plugins/fake2-plugin-1.1.jar [deleted file]
sonar-core/src/test/resources/org/sonar/core/plugins/sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar [deleted file]
sonar-core/src/test/resources/org/sonar/core/plugins/sonar-cobertura-plugin-3.1.1.jar [deleted file]
sonar-core/src/test/resources/org/sonar/core/plugins/sonar-switch-off-violations-plugin-1.1.jar [deleted file]
sonar-deprecated/src/main/java/org/sonar/api/charts/package-info.java
sonar-home/src/main/java/org/sonar/home/cache/FileCache.java
sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java
sonar-plugin-api/src/main/java/org/sonar/api/Plugin.java
sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentContainer.java [deleted file]
sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentKeys.java [deleted file]
sonar-plugin-api/src/main/java/org/sonar/api/platform/PicoUtils.java [deleted file]
sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginMetadata.java [deleted file]
sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginRepository.java [deleted file]
sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java [deleted file]
sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentKeysTest.java [deleted file]
sonar-plugin-api/src/test/java/org/sonar/api/platform/PicoUtilsTest.java [deleted file]

index 080a6b643ec43340e39aaaf621b93cb5ccfaf34e..6d9fc80d519a48dd79a9a57f38cc9905916649b7 100644 (file)
@@ -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;
 
index 30550df1482df8b0808738f5c1e91d2fc9ec265b..d901586ae250c01eb862cf9bcf902854da8f65de 100644 (file)
@@ -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;
 
index a2cb7d768c18b7db71a53b1cd8e2091bd0873730..5e28de8ee2c38a852dfa3334151e485a02f5f4f7 100644 (file)
@@ -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;
 
index 03fd8c4635048f77a7d765c03cfdc9afd2757a27..df9ccea5073d94c0ca5b7e2c2ceada69438ad599 100644 (file)
@@ -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;
 
index 870522ec56fd78c7341607349eb54b38a8f30c73..8d487ae95001ca8a9089756c08ec5cff5f43c54a 100644 (file)
@@ -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 2dee8a71b58826227ad7337d78b2f9663b97acf8..3d6388fb8b028629c8b08b5c14411ea489d4c683 100644 (file)
--- a/pom.xml
+++ b/pom.xml
         <artifactId>sonar-channel</artifactId>
         <version>4.1</version>
       </dependency>
+      <dependency>
+        <groupId>org.codehaus.sonar</groupId>
+        <artifactId>sonar-classloader</artifactId>
+        <version>1.0</version>
+      </dependency>
       <dependency>
         <groupId>org.codehaus.sonar</groupId>
         <artifactId>sonar-markdown</artifactId>
           </exclusion>
         </exclusions>
       </dependency>
-      <dependency>
-        <groupId>org.codehaus.plexus</groupId>
-        <artifactId>plexus-classworlds</artifactId>
-        <version>2.5.1</version>
-      </dependency>
       <dependency>
         <groupId>com.tinkerpop.blueprints</groupId>
         <artifactId>blueprints-core</artifactId>
index 78e7f995a1e9a9cee174f9271289ad97eb1ce467..fd3ee697d4fbfd2c7164256193c7b40648442d8d 100644 (file)
         <directory>src/main/resources</directory>
         <filtering>true</filtering>
       </resource>
+
     </resources>
+    <testResources>
+      <testResource>
+        <directory>src/test/resources</directory>
+        <filtering>false</filtering>
+      </testResource>
+      <testResource>
+        <directory>src/test/projects</directory>
+        <filtering>false</filtering>
+      </testResource>
+
+    </testResources>
 
     <plugins>
       <plugin>
index 95ad3ef370e7f089f56b3baf910c61791b47df70..d95084053ae8a621a2fe8d05c2dd0c19bac57ff5 100644 (file)
@@ -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;
index b506fc5244a2e4b847706c9beda1bc6295c6c3ab..7158b0d5e6d0903f7f6c1d9e5e83f84ec4c9d3b7 100644 (file)
@@ -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;
index b0e1cb6bd202fd909d720980d18774fb06aa36bd..da45c47bf68fb81f88e4a41884ade14d776600af 100644 (file)
@@ -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 "<pluginKey>-model.xml".
  * </p>
  */
-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);
         }
       }
     }
index df520c3900a9d0f383ed80411ed696527b84b4dc..5258e753057541fee2963221655a198b2878a681 100644 (file)
@@ -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<File> getCorePlugins() {
-    File corePluginsDir = new File(getHomeDir(), "lib/core-plugins");
-    return getFiles(corePluginsDir, "jar");
-  }
-
-  public List<File> getBundledPlugins() {
-    File corePluginsDir = new File(getHomeDir(), "lib/bundled-plugins");
-    return getFiles(corePluginsDir, "jar");
+  public File getInstalledPluginsDir() {
+    return new File(getHomeDir(), "extensions/plugins");
   }
 
-  public List<File> 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<File> getFiles(File dir, String... fileSuffixes) {
-    List<File> files = new ArrayList<File>();
+    List<File> files = new ArrayList<>();
     if (dir != null && dir.exists()) {
       if (fileSuffixes != null && fileSuffixes.length > 0) {
         files.addAll(FileUtils.listFiles(dir, fileSuffixes, false));
index 5d857910be2605319fd3688bcb7af949543499a7..4f28227339e7fcdc8251739cc99b09d2ec18470a 100644 (file)
  */
 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
  */
index c034a78ee38cfdf123e1f6d010a2f5bb431cb450..8f7cb251897f7fe773f10eaeb06d13fbf52659f3 100644 (file)
@@ -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<String, String>() {
         @Override
index b6347f84e61217fbda7bae0a6728481a4a30ed6d..6844878d9b9cb7b9f635c97d32bc6617181cc541 100644 (file)
@@ -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
index 9ec58dc404493e68f5aadeb4cb5d31a9adc1a61f..591168f2831c226b980e9101361647ce2c0a3301 100644 (file)
@@ -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<String, Object> attributes() {
     LinkedHashMap<String, Object> attributes = new LinkedHashMap<>();
-    for (PluginMetadata plugin : plugins()) {
+    for (PluginInfo plugin : plugins()) {
       LinkedHashMap<String, Object> 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<PluginMetadata> plugins() {
-    return Lists.newArrayList(Iterables.filter(repository.getMetadata(), new Predicate<PluginMetadata>() {
+  private Iterable<PluginInfo> plugins() {
+    return Iterables.filter(repository.getPluginInfos(), new Predicate<PluginInfo>() {
       @Override
-      public boolean apply(PluginMetadata input) {
-        return !input.isCore();
+      public boolean apply(@Nonnull PluginInfo info) {
+        return !info.isCore();
       }
-    }));
+    });
   }
 }
index 257cd22ca05643432ef4da1915edd55fe1084b58..599899c475cb155a228d90ccb20f6855fd575465 100644 (file)
@@ -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());
   }
 
 }
index 1e2dd190f49fc4d5d8e7b9dbbaa36571199a757f..b17ac9773426394891fb11a707190af68b9ed05c 100644 (file)
@@ -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<DefaultPluginMetadata> getDownloadedPlugins() {
-    return newArrayList(transform(listPlugins(this.downloadDir), installer.fileToPlugin()));
+  public Collection<PluginInfo> 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<File> listTempFile(File dir) {
-    return FileUtils.listFiles(dir, new String[]{TMP_SUFFIX}, false);
+    return FileUtils.listFiles(dir, new String[] {TMP_SUFFIX}, false);
   }
 
   private static Collection<File> listPlugins(File dir) {
-    return FileUtils.listFiles(dir, new String[]{PLUGIN_EXTENSION}, false);
+    return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false);
   }
 }
index 30646c02a953d08db0e6ab759f84c4e1f23071b1..70afcb4206899895e81988a9e52460f314071c09 100644 (file)
  */
 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<PluginMetadata> metadata) {
+  public static PluginReferential getInstalledPluginReferential(Collection<PluginInfo> metadata) {
     List<PluginManifest> pluginManifestList = getPluginManifestList(metadata);
     return PluginReferentialManifestConverter.fromPluginManifests(pluginManifestList);
   }
 
-  private static List<PluginManifest> getPluginManifestList(Collection<PluginMetadata> metadata) {
+  private static List<PluginManifest> getPluginManifestList(Collection<PluginInfo> metadata) {
     List<PluginManifest> 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;
   }
 }
index c084470711cb7a44d87b785f66cbff5da6bbc041..e9297de4353aeefaac3a444485fc26dabf0d9b78 100644 (file)
@@ -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<PluginMetadata, Object> installedExtensionsByPlugin = ArrayListMultimap.create();
+    ListMultimap<PluginInfo, Object> 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<PluginMetadata, Object> entry : installedExtensionsByPlugin.entries()) {
-      PluginMetadata plugin = entry.getKey();
+    for (Map.Entry<PluginInfo, Object> 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 (file)
index 777a197..0000000
+++ /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 (file)
index 9b9ce32..0000000
+++ /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<String, PluginMetadata> pluginByKeys = Maps.newHashMap();
-  private final ServerUpgradeStatus serverUpgradeStatus;
-  private static final Set<String> BLACKLISTED_PLUGINS = new HashSet<String>(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<String> 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<DefaultPluginMetadata> 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<PluginMetadata> getMetadata() {
-    return pluginByKeys.values();
-  }
-
-  public PluginMetadata getMetadata(String pluginKey) {
-    return pluginByKeys.get(pluginKey);
-  }
-
-  private PluginReferential getPluginReferential() {
-    return PluginReferentialMetadataConverter.getInstalledPluginReferential(getMetadata());
-  }
-
-  private static Collection<File> listJarFiles(File pluginDir) {
-    return listFiles(pluginDir, new String[] {FILE_EXTENSION_JAR}, false);
-  }
-
-  private enum FileToName implements Function<File, String> {
-    INSTANCE;
-
-    @Override
-    public String apply(@Nonnull File file) {
-      return file.getName();
-    }
-  }
-}
index 1602861796681495bf07167cab5da4351f832371..cb9db1f40424e3e399115d08c2e2a981aa8f96b2 100644 (file)
  */
 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:
+ * <ul>
+ *   <li>installation of bundled plugins on first server startup</li>
+ *   <li>installation of new plugins (effective after server startup)</li>
+ *   <li>un-installation of plugins (effective after server startup)</li>
+ *   <li>cancel pending installations/un-installations</li>
+ *   <li>load plugin bytecode</li>
+ * </ul>
  */
 public class ServerPluginRepository implements PluginRepository, Startable {
 
-  private final ServerPluginJarsInstaller jarsInstaller;
-  private final PluginClassloaders classloaders;
-  private Map<String, Plugin> pluginsByKey;
+  private static final Logger LOG = Loggers.get(ServerPluginRepository.class);
+  private static final String FILE_EXTENSION_JAR = "jar";
+  private static final Set<String> 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<String> 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<String, PluginInfo> pluginInfosByKeys = new HashMap<>();
+  private final Map<String, Plugin> 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<String> keys) {
+    this.blacklistedPluginKeys = keys;
   }
 
   @Override
   public void start() {
-    jarsInstaller.install();
-    Collection<PluginMetadata> 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<String> 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<PluginInfo> 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<String> 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<PluginInfo> 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<String, PluginInfo> getPluginInfosByKeys() {
+    return pluginInfosByKeys;
+  }
+
+  @Override
+  public Collection<PluginInfo> 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<PluginMetadata> 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<File, String> {
+    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<File> 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 (file)
index 0000000..59bc5a8
--- /dev/null
@@ -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);
+    }
+  }
+}
index 8d24a1ae5f07eafeb656c747ffe41b47255d8e55..9a7ab9c36908e93e81b6de98935253e336cc9c6b 100644 (file)
@@ -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);
index 9312425158942f26e72ca8ee0f5cdb986ff7b349..3899e50985de436f9b9678dd7b497ad8780a0e4b 100644 (file)
@@ -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();
   }
index c84b730c96c44f5e242476b36af13399dd819579..dc2f3ff4be7a142a6751649cc09b7aaa41a40dc4 100644 (file)
@@ -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<PluginMetadata> pluginMetadatas = retrieveAndSortPluginMetadata();
+    Collection<PluginInfo> infos = retrieveAndSortPluginMetadata();
 
     JsonWriter jsonWriter = response.newJsonWriter();
     jsonWriter.setSerializeEmptys(false);
     jsonWriter.beginObject();
 
-    writeMetadataList(jsonWriter, pluginMetadatas);
+    writeMetadataList(jsonWriter, infos);
 
     jsonWriter.endObject();
     jsonWriter.close();
   }
 
-  private SortedSet<PluginMetadata> retrieveAndSortPluginMetadata() {
+  private SortedSet<PluginInfo> 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<PluginMetadata> pluginMetadatas) {
+  private void writeMetadataList(JsonWriter jsonWriter, Collection<PluginInfo> 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<PluginMetadata> {
+  private enum NotCorePluginsPredicate implements Predicate<PluginInfo> {
     INSTANCE;
 
     @Override
-    public boolean apply(@Nullable PluginMetadata input) {
+    public boolean apply(@Nullable PluginInfo input) {
       return input != null && !input.isCore();
     }
   }
index 8c55caeaf68dcbca80de30215ec150023f04f161..29eb3cc9b537efb861ca5be37ad5433e98fca14a 100644 (file)
  */
 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<DefaultPluginMetadata> plugins = pluginDownloader.getDownloadedPlugins();
-    for (PluginMetadata pluginMetadata : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) {
+    Collection<PluginInfo> 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<DefaultPluginMetadata> plugins = serverPluginJarsInstaller.getUninstalledPlugins();
-    for (PluginMetadata pluginMetadata : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) {
+    Collection<PluginInfo> plugins = installer.getUninstalledPlugins();
+    for (PluginInfo pluginMetadata : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) {
       pluginWSCommons.writePluginMetadata(jsonWriter, pluginMetadata);
     }
     jsonWriter.endArray();
index 08cc27555b4977c630feffb9653eab9176956a36..c2f8a8b6b63366d658c56121f76504e399a4ba48 100644 (file)
@@ -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<PluginMetadata> NAME_KEY_PLUGIN_METADATA_COMPARATOR = Ordering.natural()
+  public static final Ordering<PluginInfo> NAME_KEY_PLUGIN_METADATA_COMPARATOR = Ordering.natural()
     .onResultOf(PluginMetadataToName.INSTANCE)
     .compound(Ordering.natural().onResultOf(PluginMetadataToKey.INSTANCE));
   public static final Comparator<Plugin> NAME_KEY_PLUGIN_ORDERING = Ordering.from(CASE_INSENSITIVE_ORDER)
@@ -70,23 +71,26 @@ public class PluginWSCommons {
   public static final Comparator<PluginUpdate> 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<PluginMetadata, String> {
+  private enum PluginMetadataToName implements Function<PluginInfo, String> {
     INSTANCE;
 
     @Override
-    public String apply(@Nonnull PluginMetadata input) {
+    public String apply(@Nonnull PluginInfo input) {
       return input.getName();
     }
   }
 
-  private enum PluginMetadataToKey implements Function<PluginMetadata, String> {
+  private enum PluginMetadataToKey implements Function<PluginInfo, String> {
     INSTANCE;
 
     @Override
-    public String apply(@Nonnull PluginMetadata input) {
+    public String apply(@Nonnull PluginInfo input) {
       return input.getKey();
     }
   }
index bb2e9e1c43a02206d4f7b63b21486b2c22ce895a..98399499acd28d16957063b327cd8cbb46314bd7 100644 (file)
  */
 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<PluginMetadata> {
-    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));
     }
   }
 }
index c29cd525cc993caa17930625afe3cf524179eef2..d20d2c4e8250009e839a7842326485c68b97ee45 100644 (file)
@@ -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;
index d79adb553c2f036bd7e8d6a292ce36dc7934b47f..4fe91fb829f706d31862108925653dd36dd2f2a8 100644 (file)
@@ -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();
index 4c2ed9deef114d50293dd83f328e99d1fab2a5d9..71a39f1e1f911e6582c004b1e42a16384a06dbc3 100644 (file)
@@ -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<String> 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<PluginMetadata> getPluginsMetadata() {
-    return get(PluginRepository.class).getMetadata();
+  /**
+   * Used for WS api/updatecenter/installed_plugins, to be replaced by api/plugins/installed.
+   */
+  public Collection<PluginInfo> getPluginInfos() {
+    return get(PluginRepository.class).getPluginInfos();
   }
 
   public List<ViewProxy<Widget>> 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) {
index 942bb973c09152f2bef0578fed8dcf0a1419f57f..730584d829b5a81ef12dd7359de7b29e01a101bf 100644 (file)
@@ -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
index 5c49de4aef1f7ef7cb0e4ca75e40138bf5a9c061..e8df89bb00381476cdb66ed671a3234e98a4168d 100644 (file)
@@ -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;
 
index c06a788702238a212d5ca2a107d87f67604a5a1e..57bf62c771a49ce66737cd5b3b1bc719614d19e2 100644 (file)
@@ -37,20 +37,6 @@ public class DefaultServerFileSystemTest {
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
 
-  @Test
-  public void find_plugins() throws Exception {
-    List<File> 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<File> 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(
index a64486ca9d90d14de3467be56142e92a3906b2bd..d110225210dfeae47157794d600e82e4cd84dde0 100644 (file)
@@ -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.<PluginMetadata>emptyList());
+    when(pluginRepository.getPluginInfos()).thenReturn(Collections.<PluginInfo>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/SonarHomeTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/SonarHomeTest.java
deleted file mode 100644 (file)
index 66a4069..0000000
+++ /dev/null
@@ -1,29 +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.platform;
-
-import org.junit.Test;
-
-public class SonarHomeTest {
-  @Test
-  public void iDontKnowHowToSimplyTestThisClass() {
-
-  }
-}
index ef082c716b230bc76db28724bcdc778d19d91f3f..9e70d80a699e1a4c9dc5df6f2b71b49141ad1349 100644 (file)
 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<String, Object> 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<PluginMetadata> getMetadata() {
-      List<PluginMetadata> 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");
   }
 }
index 40e1ada4f35c8328ecaab6127330fae8962d4e35..58c0e12c4498071371cef55abac1539ee7211fdf 100644 (file)
@@ -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();
   }
 }
index 6c5f441202679b1694cc413829f23a1c9298226f..25b5b4978c47abe0c5be58545704d148410a4795 100644 (file)
@@ -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();
+  }
 }
index bf460fffd2b1b27b39475b36e075f081770aa0e2..a5178f4038343ba3ea6dd39c92ac2e7b3b17ee55 100644 (file)
@@ -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);
 
index a7cc85c96944983bd180ecd00f3d59f50dcce9fd..c7891203caae4bf85a4f4470763c603839274cc5 100644 (file)
 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 (file)
index 8edde70..0000000
+++ /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<DefaultPluginMetadata> 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();
-  }
-}
index 5f1898dae3133ec4919558baac14fc48d2c2d381..ea87cb3af881964d51765dd8f59f5faae4f5ba0d 100644 (file)
  */
 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 (file)
index 0000000..efea25d
--- /dev/null
@@ -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/server/sonar-server/src/test/java/org/sonar/server/plugins/TestProjectUtils.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/TestProjectUtils.java
new file mode 100644 (file)
index 0000000..00d5791
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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 java.io.File;
+import java.util.Collection;
+
+public class TestProjectUtils {
+
+  /**
+   * 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<File> jars = FileUtils.listFiles(target, new String[] {"jar"}, false);
+    if (jars == null || jars.size() != 1) {
+      throw new IllegalArgumentException("Test project is badly defined: " + dirName);
+    }
+    return jars.iterator().next();
+  }
+}
index 4ff9f43f3404b222411c3716a507101c28b2338d..3d2260eba4f6c52794212fb8fd934a8dae4491ff 100644 (file)
@@ -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();
   }
 
index 3780d88af0063890dfe12cf3929c0414d8e1f8b2..11a544e1d2342bf66a8a836787f4ade228f11849 100644 (file)
  */
 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"));
   }
 }
index 558ed57839a4e562159619d3e34ec5a202d6968a..1849151312af7bbe63d497d6017407427942a691 100644 (file)
@@ -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,
index 831bdbd7b78077520e1b6e9a8f3e6cf0f3361f88..e055635b343f9cf1e4d0eb068ed934ca089e6bf6 100644 (file)
@@ -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);
index fa8adaa0f98e7ad544aabd6820c3ccab7f04c9b9..85a3bba714fe4251422808663d038e7f1a33c517 100644 (file)
  */
 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.<PluginMetadata>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();
   }
 
index cc96395bc62fec7bcd15102b69e8e82fa8e05a31..8af9cb1eb7c8a25d34ba84e306387bd76325c8fe 100644 (file)
@@ -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 (file)
index 0000000..a945b85
--- /dev/null
@@ -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 (file)
index 0000000..c53a66d
--- /dev/null
@@ -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 (file)
index 0000000..f244768
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.codehaus.sonar.tests</groupId>
+  <artifactId>parent</artifactId>
+  <version>0.1-SNAPSHOT</version>
+  <packaging>pom</packaging>
+  <modules>
+    <module>test-base-plugin</module>
+    <module>test-base-plugin-v2</module>
+    <module>test-core-plugin</module>
+    <module>test-extend-plugin</module>
+    <module>test-libs-plugin</module>
+    <module>test-require-plugin</module>
+    <module>test-requirenew-plugin</module>
+  </modules>
+
+</project>
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 (file)
index 0000000..21f7ed5
--- /dev/null
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.codehaus.sonar.tests</groupId>
+  <artifactId>test-base-plugin</artifactId>
+  <version>0.2-SNAPSHOT</version>
+  <packaging>sonar-plugin</packaging>
+  <name>Base Plugin</name>
+  <description>Simple standalone plugin. Used by other fake plugins.</description>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-plugin-api</artifactId>
+      <version>4.5.4</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <sourceDirectory>src</sourceDirectory>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.sonar</groupId>
+        <artifactId>sonar-packaging-maven-plugin</artifactId>
+        <version>1.13</version>
+        <extensions>true</extensions>
+        <configuration>
+          <pluginKey>testbase</pluginKey>
+          <pluginClass>BasePlugin</pluginClass>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
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 (file)
index 0000000..57b4a5d
--- /dev/null
@@ -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 (file)
index 0000000..9bd9e0e
Binary files /dev/null and b/server/sonar-server/src/test/projects/test-base-plugin-v2/target/test-base-plugin-0.2-SNAPSHOT.jar differ
diff --git a/server/sonar-server/src/test/projects/test-base-plugin/pom.xml b/server/sonar-server/src/test/projects/test-base-plugin/pom.xml
new file mode 100644 (file)
index 0000000..61b994c
--- /dev/null
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.codehaus.sonar.tests</groupId>
+  <artifactId>test-base-plugin</artifactId>
+  <version>0.1-SNAPSHOT</version>
+  <packaging>sonar-plugin</packaging>
+  <name>Base Plugin</name>
+  <description>Simple standalone plugin. Used by other fake plugins.</description>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-plugin-api</artifactId>
+      <version>4.5.4</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <sourceDirectory>src</sourceDirectory>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.sonar</groupId>
+        <artifactId>sonar-packaging-maven-plugin</artifactId>
+        <version>1.13</version>
+        <extensions>true</extensions>
+        <configuration>
+          <pluginKey>testbase</pluginKey>
+          <pluginClass>BasePlugin</pluginClass>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
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 (file)
index 0000000..57b4a5d
--- /dev/null
@@ -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 (file)
index 0000000..2a6148c
Binary files /dev/null and b/server/sonar-server/src/test/projects/test-base-plugin/target/test-base-plugin-0.1-SNAPSHOT.jar differ
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 (file)
index 0000000..fc3f082
--- /dev/null
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.codehaus.sonar.tests</groupId>
+  <artifactId>test-core-plugin</artifactId>
+  <version>0.1-SNAPSHOT</version>
+  <packaging>sonar-plugin</packaging>
+  <name>Test Core Plugin</name>
+  <description>Fake core plugin used by tests</description>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-plugin-api</artifactId>
+      <version>4.5.4</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <sourceDirectory>src</sourceDirectory>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.sonar</groupId>
+        <artifactId>sonar-packaging-maven-plugin</artifactId>
+        <version>1.13</version>
+        <extensions>true</extensions>
+        <configuration>
+          <pluginKey>core</pluginKey>
+          <pluginClass>CorePlugin</pluginClass>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
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 (file)
index 0000000..910204d
--- /dev/null
@@ -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 (file)
index 0000000..62eba2a
Binary files /dev/null and b/server/sonar-server/src/test/projects/test-core-plugin/target/test-core-plugin-0.1-SNAPSHOT.jar differ
diff --git a/server/sonar-server/src/test/projects/test-extend-plugin/pom.xml b/server/sonar-server/src/test/projects/test-extend-plugin/pom.xml
new file mode 100644 (file)
index 0000000..9b20de1
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.codehaus.sonar.tests</groupId>
+  <artifactId>test-extend-plugin</artifactId>
+  <version>0.1-SNAPSHOT</version>
+  <packaging>sonar-plugin</packaging>
+  <name>Test Extend Plugin</name>
+  <description>Fake plugin that extends the plugin with key "base"</description>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-plugin-api</artifactId>
+      <version>4.5.4</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <sourceDirectory>src</sourceDirectory>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.sonar</groupId>
+        <artifactId>sonar-packaging-maven-plugin</artifactId>
+        <version>1.13</version>
+        <extensions>true</extensions>
+        <configuration>
+          <pluginKey>testextend</pluginKey>
+          <pluginClass>ExtendPlugin</pluginClass>
+          <basePlugin>testbase</basePlugin>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
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 (file)
index 0000000..826e184
--- /dev/null
@@ -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 (file)
index 0000000..7a81fdf
Binary files /dev/null and b/server/sonar-server/src/test/projects/test-extend-plugin/target/test-extend-plugin-0.1-SNAPSHOT.jar differ
diff --git a/server/sonar-server/src/test/projects/test-libs-plugin/pom.xml b/server/sonar-server/src/test/projects/test-libs-plugin/pom.xml
new file mode 100644 (file)
index 0000000..e7d2421
--- /dev/null
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.codehaus.sonar.tests</groupId>
+  <artifactId>test-libs-plugin</artifactId>
+  <version>0.1-SNAPSHOT</version>
+  <packaging>sonar-plugin</packaging>
+  <name>Test Libs Plugin</name>
+  <description>Fake plugin that embeds some libraries</description>
+
+  <dependencies>
+    <!-- embedded libs. Chosen because small ! -->
+    <dependency>
+      <groupId>commons-email</groupId>
+      <artifactId>commons-email</artifactId>
+      <version>20030310.165926</version>
+    </dependency>
+    <dependency>
+      <groupId>commons-daemon</groupId>
+      <artifactId>commons-daemon</artifactId>
+      <version>1.0.15</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-plugin-api</artifactId>
+      <version>4.5.4</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <sourceDirectory>src</sourceDirectory>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.sonar</groupId>
+        <artifactId>sonar-packaging-maven-plugin</artifactId>
+        <version>1.13</version>
+        <extensions>true</extensions>
+        <configuration>
+          <pluginKey>testlibs</pluginKey>
+          <pluginClass>LibsPlugin</pluginClass>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
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 (file)
index 0000000..965c9ac
--- /dev/null
@@ -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 (file)
index 0000000..85e4772
Binary files /dev/null and b/server/sonar-server/src/test/projects/test-libs-plugin/target/test-libs-plugin-0.1-SNAPSHOT.jar differ
diff --git a/server/sonar-server/src/test/projects/test-require-plugin/pom.xml b/server/sonar-server/src/test/projects/test-require-plugin/pom.xml
new file mode 100644 (file)
index 0000000..1f77e23
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.codehaus.sonar.tests</groupId>
+  <artifactId>test-require-plugin</artifactId>
+  <version>0.1-SNAPSHOT</version>
+  <packaging>sonar-plugin</packaging>
+  <name>Test Require Plugin</name>
+  <description>This fake plugin depends on test-base-plugin</description>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-plugin-api</artifactId>
+      <version>4.5.4</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <sourceDirectory>src</sourceDirectory>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.sonar</groupId>
+        <artifactId>sonar-packaging-maven-plugin</artifactId>
+        <version>1.13</version>
+        <extensions>true</extensions>
+        <configuration>
+          <pluginKey>testrequire</pluginKey>
+          <pluginClass>RequirePlugin</pluginClass>
+          <requirePlugins>testbase:0.1</requirePlugins>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
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 (file)
index 0000000..440f73b
--- /dev/null
@@ -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 (file)
index 0000000..ac1f9f6
Binary files /dev/null and b/server/sonar-server/src/test/projects/test-require-plugin/target/test-require-plugin-0.1-SNAPSHOT.jar differ
diff --git a/server/sonar-server/src/test/projects/test-requirenew-plugin/pom.xml b/server/sonar-server/src/test/projects/test-requirenew-plugin/pom.xml
new file mode 100644 (file)
index 0000000..ca207b1
--- /dev/null
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.codehaus.sonar.tests</groupId>
+  <artifactId>test-requirenew-plugin</artifactId>
+  <version>0.1-SNAPSHOT</version>
+  <packaging>sonar-plugin</packaging>
+  <name>Test Require New Plugin</name>
+  <description>This fake plugin requires a version of test-base-plugin that is not installed</description>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-plugin-api</artifactId>
+      <version>4.5.4</version>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <sourceDirectory>src</sourceDirectory>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.sonar</groupId>
+        <artifactId>sonar-packaging-maven-plugin</artifactId>
+        <version>1.13</version>
+        <extensions>true</extensions>
+        <configuration>
+          <pluginKey>testrequire</pluginKey>
+          <pluginClass>RequirePlugin</pluginClass>
+          <requirePlugins>testbase:0.2</requirePlugins>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
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 (file)
index 0000000..440f73b
--- /dev/null
@@ -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 (file)
index 0000000..3437dfe
Binary files /dev/null and b/server/sonar-server/src/test/projects/test-requirenew-plugin/target/test-requirenew-plugin-0.1-SNAPSHOT.jar differ
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/extension.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/extension.jar
deleted file mode 100644 (file)
index e788522..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/extension.jar and /dev/null differ
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/foo-plugin.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/foo-plugin.jar
deleted file mode 100644 (file)
index 7bcf027..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/foo-plugin.jar and /dev/null differ
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 (file)
index 0eb5f40..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/sonar-build-breaker-plugin-0.1.jar and /dev/null differ
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginDownloaderTest/foo-plugin-1.0.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginDownloaderTest/foo-plugin-1.0.jar
deleted file mode 100644 (file)
index 3b3ed4b..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginDownloaderTest/foo-plugin-1.0.jar and /dev/null differ
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version1/extension.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version1/extension.jar
deleted file mode 100644 (file)
index e788522..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version1/extension.jar and /dev/null differ
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version2/extension.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version2/extension.jar
deleted file mode 100644 (file)
index 636176b..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version2/extension.jar and /dev/null differ
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-1.0.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-1.0.jar
deleted file mode 100644 (file)
index b60ea35..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-1.0.jar and /dev/null differ
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-2.0.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-2.0.jar
deleted file mode 100644 (file)
index 2e0488c..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-2.0.jar and /dev/null differ
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 (file)
index f35e771..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/not-a-plugin.jar and /dev/null differ
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 (file)
index abb19c0..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/old-plugin.jar and /dev/null differ
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 (file)
index 7bcf027..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataTest/foo-plugin.jar and /dev/null differ
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 (file)
index acf4fa4..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/bar-plugin-1.0.jar and /dev/null differ
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-1.0.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-1.0.jar
deleted file mode 100644 (file)
index 3b3ed4b..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-1.0.jar and /dev/null differ
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-2.0.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-2.0.jar
deleted file mode 100644 (file)
index b781205..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-2.0.jar and /dev/null differ
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/not-a-plugin.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/not-a-plugin.jar
deleted file mode 100644 (file)
index 11b72f4..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/not-a-plugin.jar and /dev/null differ
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 (file)
index 8044dff..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/require-sq-2.5.jar and /dev/null differ
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 (file)
index 1953323..0000000
Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginRepositoryTest/sonar-artifact-size-plugin-0.2.jar and /dev/null differ
index 555eb77744d7f10955eca522d0fd18ac9056340e..74833832017b14f90490996876d6081c37c6ae6e 100644 (file)
@@ -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
index 0f1eea698ea32035b523362c50ee0410e7e6949e..1575f757006f77d28f754a3426a6ca1f602b47db 100644 (file)
@@ -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/BatchPluginInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java
new file mode 100644 (file)
index 0000000..6e2c588
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * 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.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import org.apache.commons.lang.CharUtils;
+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.PluginInfo;
+import org.sonar.core.plugins.RemotePlugin;
+import org.sonar.core.plugins.RemotePluginFile;
+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;
+
+/**
+ * Downloads the plugins installed on server and stores them in a local user cache
+ * (see {@link FileCacheProvider}).
+ */
+public class BatchPluginInstaller implements PluginInstaller {
+
+  private static final Logger LOG = Loggers.get(BatchPluginInstaller.class);
+
+  private final ServerClient server;
+  private final FileCache fileCache;
+  private final BatchPluginPredicate pluginPredicate;
+
+  public BatchPluginInstaller(ServerClient server, FileCache fileCache, BatchPluginPredicate pluginPredicate) {
+    this.server = server;
+    this.fileCache = fileCache;
+    this.pluginPredicate = pluginPredicate;
+  }
+
+  @Override
+  public Map<String, PluginInfo> installRemotes() {
+    Map<String, PluginInfo> 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<String, Plugin> 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() {
+        @Override
+        public void download(String filename, File toFile) throws IOException {
+          String url = "/deploy/plugins/" + remote.getKey() + "/" + file.getFilename();
+          if (LOG.isDebugEnabled()) {
+            LOG.debug("Download {} to {}", url, toFile.getAbsolutePath());
+          } else {
+            LOG.info("Download {}", file.getFilename());
+          }
+          server.download(url, toFile);
+        }
+      });
+
+    } catch (Exception e) {
+      throw new IllegalStateException("Fail to download plugin: " + remote.getKey(), e);
+    }
+  }
+
+  /**
+   * Gets information about the plugins installed on server (filename, checksum)
+   */
+  @VisibleForTesting
+  List<RemotePlugin> 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<RemotePlugin> result = Lists.newArrayList();
+      for (String row : rows) {
+        result.add(RemotePlugin.unmarshal(row));
+      }
+      return result;
+
+    } catch (Exception e) {
+      throw new IllegalStateException("Fail to download list of plugins: " + url, e);
+    }
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarInstaller.java
deleted file mode 100644 (file)
index 8866cf7..0000000
+++ /dev/null
@@ -1,49 +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.batch.bootstrap;
-
-import org.sonar.api.BatchComponent;
-import org.sonar.core.plugins.DefaultPluginMetadata;
-import org.sonar.core.plugins.PluginJarInstaller;
-import org.sonar.home.cache.FileCache;
-
-import java.io.File;
-import java.io.IOException;
-
-public class BatchPluginJarInstaller extends PluginJarInstaller implements BatchComponent {
-
-  private FileCache cache;
-
-  public BatchPluginJarInstaller(FileCache cache) {
-    this.cache = cache;
-  }
-
-  public DefaultPluginMetadata installToCache(File pluginFile, boolean isCore) {
-    DefaultPluginMetadata metadata = extractMetadata(pluginFile, isCore);
-    install(metadata, null, pluginFile);
-    return metadata;
-  }
-
-  @Override
-  protected File extractPluginDependencies(File pluginFile, File pluginBasedir) throws IOException {
-    return cache.unzip(pluginFile);
-  }
-
-}
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 (file)
index 0000000..f283dcd
--- /dev/null
@@ -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<String>, 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<String> 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<String> mergeList = newArrayList(blacks);
+    mergeList.removeAll(whites);
+    return mergeList.isEmpty() || !mergeList.contains(pluginKey);
+  }
+
+  Set<String> getWhites() {
+    return whites;
+  }
+
+  Set<String> getBlacks() {
+    return blacks;
+  }
+
+  static List<String> propertyValues(Settings settings, String key, String defaultValue) {
+    String s = StringUtils.defaultIfEmpty(settings.getString(key), defaultValue);
+    return Lists.newArrayList(Splitter.on(",").trimResults().split(s));
+  }
+}
index b8a44c0d97cf64b8c1ad75fd89e7f4e2d67b3ba2..b20c85114ed8f19a895c9043a8fbe6e952cac34d 100644 (file)
  */
 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<String, Plugin> pluginInstancesByKeys;
+  private Map<String, PluginInfo> infosByKeys;
 
-  private PluginsRepository pluginsReferential;
-  private Map<String, Plugin> pluginsByKey;
-  private Map<String, PluginMetadata> 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<PluginMetadata, SonarPlugin> localPlugins = pluginsReferential.localPlugins();
-    if (!localPlugins.isEmpty()) {
-      LOG.info("Install local plugins");
-      for (Map.Entry<PluginMetadata, SonarPlugin> 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<RemotePlugin> 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<String, Plugin> 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<PluginMetadata> getMetadata() {
-    return metadataByKey.values();
+  public Collection<PluginInfo> 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<PluginMetadata, Plugin> getPluginsByMetadata() {
-    Map<PluginMetadata, Plugin> result = Maps.newHashMap();
-    for (Map.Entry<String, PluginMetadata> 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<String> 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<String> 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<String> 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 (file)
index 0000000..29f554d
--- /dev/null
@@ -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/DefaultPluginsRepository.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DefaultPluginsRepository.java
deleted file mode 100644 (file)
index 2687ebc..0000000
+++ /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.batch.bootstrap;
-
-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.core.plugins.RemotePlugin;
-import org.sonar.core.plugins.RemotePluginFile;
-import org.sonar.home.cache.FileCache;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A {@link PluginsRepository} implementation that put downloaded plugins in a FS cache.
- */
-public class DefaultPluginsRepository implements PluginsRepository {
-
-  private static final Logger LOG = LoggerFactory.getLogger(DefaultPluginsRepository.class);
-
-  private ServerClient server;
-  private FileCache fileCache;
-
-  public DefaultPluginsRepository(FileCache fileCache, ServerClient server) {
-    this.server = server;
-    this.fileCache = fileCache;
-  }
-
-  @Override
-  public File pluginFile(final RemotePlugin remote) {
-    try {
-      final RemotePluginFile file = remote.file();
-      return fileCache.get(file.getFilename(), file.getHash(), new FileCache.Downloader() {
-        @Override
-        public void download(String filename, File toFile) throws IOException {
-          String url = "/deploy/plugins/" + remote.getKey() + "/" + file.getFilename();
-          if (LOG.isDebugEnabled()) {
-            LOG.debug("Download {} to {}", url, toFile.getAbsolutePath());
-          } else {
-            LOG.info("Download {}", file.getFilename());
-          }
-          server.download(url, toFile);
-        }
-      });
-
-    } catch (Exception e) {
-      throw new IllegalStateException("Fail to download plugin: " + remote.getKey(), e);
-    }
-  }
-
-  @Override
-  public List<RemotePlugin> pluginList() {
-    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<RemotePlugin> remoteLocations = Lists.newArrayList();
-      for (String row : rows) {
-        remoteLocations.add(RemotePlugin.unmarshal(row));
-      }
-      return remoteLocations;
-
-    } catch (Exception e) {
-      throw new IllegalStateException("Fail to download plugins index: " + url, e);
-    }
-  }
-
-  @Override
-  public Map<PluginMetadata, SonarPlugin> localPlugins() {
-    return Collections.emptyMap();
-  }
-
-}
index 86599f96774bd08c263ffd1f5681900f08bc7c58..165aa83e6493ba1fdf174cb0e73dff6813598d3e 100644 (file)
@@ -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<PluginMetadata, Plugin> 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<ExtensionProvider> 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);
     }
   }
 
index 7aaf0f6be3237ceb1d368f691c954d0585d26ebc..09d217122af77d7dfcc8d8407a6b20a7a302069a 100644 (file)
@@ -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<PluginMetadata, Plugin> 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/PluginInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java
new file mode 100644 (file)
index 0000000..97eb513
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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.sonar.api.BatchComponent;
+import org.sonar.api.Plugin;
+import org.sonar.core.platform.PluginInfo;
+
+import java.util.Map;
+
+public interface PluginInstaller extends BatchComponent {
+
+  /**
+   * 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
+   */
+  Map<String, PluginInfo> installRemotes();
+
+  /**
+   * Used only by tests.
+   * @see org.sonar.batch.mediumtest.BatchMediumTester
+   */
+  Map<String, Plugin> installLocals();
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginsRepository.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginsRepository.java
deleted file mode 100644 (file)
index 58473fd..0000000
+++ /dev/null
@@ -1,51 +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.batch.bootstrap;
-
-import org.sonar.api.SonarPlugin;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.core.plugins.RemotePlugin;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Plugin referential.
- * @since 4.4
- */
-public interface PluginsRepository {
-
-  /**
-   * Return list of remote plugins to be installed
-   */
-  List<RemotePlugin> pluginList();
-
-  /**
-   * Return location of a given plugin on the local FS.
-   */
-  File pluginFile(RemotePlugin remote);
-
-  /**
-   * Return the list of local plugins to be installed
-   */
-  Map<PluginMetadata, SonarPlugin> localPlugins();
-
-}
index b3fd7e46392271cad6f22b71ec9e4ea738f69b93..fce09a6cd1cb56a9bce4b659d523a0cfbd09d04a 100644 (file)
@@ -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;
index e274375076b3486a05897fcaca42f96c732d5681..09871955a382949c06d5e44b746175b4277456b0 100644 (file)
@@ -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<String, String> bootstrapProperties = new HashMap<String, String>();
+    private final Map<String, String> 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<RemotePlugin> pluginList = new ArrayList<RemotePlugin>();
-    private Map<RemotePlugin, File> pluginFiles = new HashMap<RemotePlugin, File>();
-    Map<PluginMetadata, SonarPlugin> localPlugins = new HashMap<PluginMetadata, SonarPlugin>();
-
-    @Override
-    public List<RemotePlugin> 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<PluginMetadata, SonarPlugin> localPlugins() {
-      return localPlugins;
-    }
-
-  }
-
   private static class FakeServerIssuesLoader implements ServerIssuesLoader {
 
     private List<ServerIssue> serverIssues = new ArrayList<>();
diff --git a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java
new file mode 100644 (file)
index 0000000..cbd837c
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * 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.mediumtest;
+
+import org.sonar.api.Plugin;
+import org.sonar.batch.bootstrap.PluginInstaller;
+import org.sonar.core.platform.PluginInfo;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class FakePluginInstaller implements PluginInstaller {
+
+  private final Map<String, PluginInfo> infosByKeys = new HashMap<>();
+  private final Map<String, Plugin> instancesByKeys = new HashMap<>();
+
+  public FakePluginInstaller add(String pluginKey, File jarFile) {
+    infosByKeys.put(pluginKey, PluginInfo.create(jarFile));
+    return this;
+  }
+
+  public FakePluginInstaller add(String pluginKey, Plugin instance) {
+    instancesByKeys.put(pluginKey, instance);
+    return this;
+  }
+
+  @Override
+  public Map<String, PluginInfo> installRemotes() {
+    return infosByKeys;
+  }
+
+  @Override
+  public Map<String, Plugin> installLocals() {
+    return instancesByKeys;
+  }
+}
index 75281a9e961bae4304293a13abf7758af51601db..f8312429543e42117525dc75003d4f67be880151 100644 (file)
@@ -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;
index ebf3d5c2d876140f35e5342fc5ff0f6bf1ab141f..12a23bf62e419d807c93d9b979ef81e2027b32a5 100644 (file)
@@ -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;
index 0064d028ea8a53c426d7387e8f579b82c8d6a13d..bb3f0f8a021996fb7f5c49e1e68170899180c740 100644 (file)
@@ -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;
index 14fde3895efe1bbf46e89a6127c421613be4c599..6f7e0c11e3245605f2d24887b697e8ecb9127a2e 100644 (file)
@@ -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/BatchPluginInstallerTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java
new file mode 100644 (file)
index 0000000..03c3707
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * 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.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.core.plugins.RemotePlugin;
+import org.sonar.home.cache.FileCache;
+
+import java.io.File;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class BatchPluginInstallerTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  FileCache fileCache = mock(FileCache.class);
+  BatchPluginPredicate pluginPredicate = mock(BatchPluginPredicate.class);
+
+  @Test
+  public void listRemotePlugins() {
+
+    ServerClient server = mock(ServerClient.class);
+    when(server.request("/deploy/plugins/index.txt")).thenReturn("checkstyle,false\nsqale,false");
+    BatchPluginInstaller installer = new BatchPluginInstaller(server, fileCache, pluginPredicate);
+
+    List<RemotePlugin> remotePlugins = installer.listRemotePlugins();
+    assertThat(remotePlugins).extracting("key").containsOnly("checkstyle", "sqale");
+  }
+
+  @Test
+  public void should_download_plugin() throws Exception {
+    File pluginJar = temp.newFile();
+    when(fileCache.get(eq("checkstyle-plugin.jar"), eq("fakemd5_1"), any(FileCache.Downloader.class))).thenReturn(pluginJar);
+
+    ServerClient server = mock(ServerClient.class);
+    BatchPluginInstaller installer = new BatchPluginInstaller(server, fileCache, pluginPredicate);
+
+    RemotePlugin remote = new RemotePlugin("checkstyle", true).setFile("checkstyle-plugin.jar", "fakemd5_1");
+    File file = installer.download(remote);
+
+    assertThat(file).isEqualTo(pluginJar);
+  }
+
+  @Test
+  public void should_fail_to_get_plugin_index() {
+    thrown.expect(IllegalStateException.class);
+
+    ServerClient server = mock(ServerClient.class);
+    doThrow(new IllegalStateException()).when(server).request("/deploy/plugins/index.txt");
+
+    new BatchPluginInstaller(server, fileCache, pluginPredicate).installRemotes();
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest.java
deleted file mode 100644 (file)
index 2209c57..0000000
+++ /dev/null
@@ -1,77 +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.batch.bootstrap;
-
-import org.apache.commons.io.FileUtils;
-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.home.cache.FileCacheBuilder;
-
-import java.io.File;
-import java.io.IOException;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class BatchPluginJarInstallerTest {
-
-  private BatchPluginJarInstaller extractor;
-
-  @ClassRule
-  public static TemporaryFolder temporaryFolder = new TemporaryFolder();
-
-  private File userHome;
-
-  @Before
-  public void setUp() throws IOException {
-    userHome = temporaryFolder.newFolder();
-    extractor = new BatchPluginJarInstaller(new FileCacheBuilder().setUserHome(userHome).build());
-  }
-
-  @Test
-  public void should_copy_and_extract_dependencies() throws IOException {
-    File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar");
-    DefaultPluginMetadata metadata = extractor.installToCache(fileFromCache, true);
-
-    assertThat(metadata.getKey()).isEqualTo("checkstyle");
-    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 {
-    File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar");
-    extractor.installToCache(fileFromCache, true);
-
-    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();
-    assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/org/sonar/plugins/checkstyle/CheckstyleVersion.class")).doesNotExist();
-  }
-
-  File getFileFromCache(String filename) throws IOException {
-    File src = FileUtils.toFile(BatchPluginJarInstallerTest.class.getResource("/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest/" + 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/BatchPluginPredicateTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java
new file mode 100644 (file)
index 0000000..9dcebd6
--- /dev/null
@@ -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();
+  }
+
+}
index 66a499b01c079e2c845a185e0fee499467f2b845..7c82edbb64f7a349a58e1f4ad5912d0bfa0b9f6b 100644 (file)
  * 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/BatchPluginUnzipperTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java
new file mode 100644 (file)
index 0000000..06a2514
--- /dev/null
@@ -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.batch.bootstrap;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+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;
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class BatchPluginUnzipperTest {
+
+  @ClassRule
+  public static TemporaryFolder temp = new TemporaryFolder();
+
+  File userHome;
+  BatchPluginUnzipper underTest;
+
+  @Before
+  public void setUp() throws IOException {
+    userHome = temp.newFolder();
+    FileCache fileCache = new FileCacheBuilder().setUserHome(userHome).build();
+    underTest = new BatchPluginUnzipper(fileCache);
+  }
+
+  @Test
+  public void copy_and_extract_libs() throws IOException {
+    File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar");
+    UnzippedPlugin unzipped = underTest.unzip(PluginInfo.create(fileFromCache));
+
+    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 extract_only_libs() throws IOException {
+    File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar");
+    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();
+    assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/org/sonar/plugins/checkstyle/CheckstyleVersion.class")).doesNotExist();
+  }
+
+  File getFileFromCache(String filename) throws IOException {
+    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/DefaultPluginsRepositoryTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DefaultPluginsRepositoryTest.java
deleted file mode 100644 (file)
index 57e940a..0000000
+++ /dev/null
@@ -1,88 +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.batch.bootstrap;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.core.plugins.RemotePlugin;
-import org.sonar.home.cache.FileCache;
-
-import java.io.File;
-import java.util.List;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class DefaultPluginsRepositoryTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  @Test
-  public void should_request_list_of_plugins() {
-    FileCache cache = mock(FileCache.class);
-    ServerClient server = mock(ServerClient.class);
-    when(server.request("/deploy/plugins/index.txt")).thenReturn("checkstyle,true\nsqale,false");
-    DefaultPluginsRepository downloader = new DefaultPluginsRepository(cache, server);
-
-    List<RemotePlugin> 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();
-  }
-
-  @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);
-
-    ServerClient server = mock(ServerClient.class);
-    DefaultPluginsRepository downloader = new DefaultPluginsRepository(cache, server);
-
-    RemotePlugin plugin = new RemotePlugin("checkstyle", true)
-      .setFile("checkstyle-plugin.jar", "fakemd5_1");
-    File file = downloader.pluginFile(plugin);
-
-    assertThat(file).isEqualTo(pluginJar);
-  }
-
-  @Test
-  public void should_fail_to_get_plugin_index() {
-    thrown.expect(IllegalStateException.class);
-
-    ServerClient server = mock(ServerClient.class);
-    doThrow(new IllegalStateException()).when(server).request("/deploy/plugins/index.txt");
-
-    new DefaultPluginsRepository(mock(FileCache.class), server).pluginList();
-  }
-}
index 0647b0e22ec32fa53b71b510f49326e8f5eb66a0..b01040b29cd41bc8023d17e143196a71804032ec 100644 (file)
@@ -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<PluginMetadata, Plugin> newPlugin(final Object... extensions) {
-    Map<PluginMetadata, Plugin> 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);
 
index 9fce2c7a95124ba1ff601ff6145a681a680e6531..ac87c52fa51ea4937a862594758acc18d6fbb167 100644 (file)
  */
 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.<PluginMetadata, Plugin>of(
-      metadata, plugin
-      ));
-
-    GlobalContainer container = spy(GlobalContainer.create(Collections.<String, String>emptyMap(), Lists.<Object>newArrayList(pluginRepository)));
-    doNothing().when(container).executeTask(Collections.<String, String>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);
-    }
-  }
 }
index fd9a9ed37272a31b6fc511ed5aa71748e9bba76e..3b7340689db6c4ecb3b487b1253cc8bb534b43ae 100644 (file)
@@ -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;
index 5c98be19187258143de569792c84444954fd6e5e..b4f7dc49ce38f836d54ac38f59f05ca40a189c6d 100644 (file)
@@ -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/BatchPluginJarInstallerTest/sonar-checkstyle-plugin-2.8.jar
deleted file mode 100644 (file)
index f937399..0000000
Binary files a/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest/sonar-checkstyle-plugin-2.8.jar and /dev/null differ
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/sonar-checkstyle-plugin-2.8.jar b/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/sonar-checkstyle-plugin-2.8.jar
new file mode 100644 (file)
index 0000000..f937399
Binary files /dev/null and b/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/sonar-checkstyle-plugin-2.8.jar differ
index b1f3232a62f7d686410cb507e9c47252cba71d54..bd348edb5c09d4beadc4f2b3152980e8966c6096 100644 (file)
@@ -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;
 
index 551a67c296fc6e51050f9ab987fb292dc5c3f5a3..d0abbe987a45af5485219436470b5dd31000720f 100644 (file)
@@ -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;
 
index 41ae4b71bffc24d011423cf81aa0e0ce6d1b0dd4..ea0fd0be8f8e97c492f39946b162800dbb5e421f 100644 (file)
       <artifactId>jsr305</artifactId>
       <scope>provided</scope>
     </dependency>
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-classloader</artifactId>
+    </dependency>
     <dependency>
       <groupId>${project.groupId}</groupId>
       <artifactId>sonar-plugin-api</artifactId>
-      <exclusions>
-        <exclusion>
-          <groupId>classworlds</groupId>
-          <artifactId>classworlds</artifactId>
-        </exclusion>
-      </exclusions>
     </dependency>
     <dependency>
       <groupId>org.mybatis</groupId>
       <groupId>commons-dbutils</groupId>
       <artifactId>commons-dbutils</artifactId>
     </dependency>
-    <dependency>
-      <groupId>org.codehaus.plexus</groupId>
-      <artifactId>plexus-classworlds</artifactId>
-    </dependency>
     <dependency>
       <groupId>com.googlecode.json-simple</groupId>
       <artifactId>json-simple</artifactId>
index 02281c51f9ee105ce8379a8cf85eda7e8f279a3f..8190b0f6e3ec5a95f1674b1375a8bc6fea53cb6a 100644 (file)
 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<String, String> 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<PluginMetadata> metadata = pluginRepository.getMetadata();
-    if (metadata.isEmpty()) {
+    this.propertyToBundles = new HashMap<>();
+    Collection<PluginInfo> 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;
   }
index d0eb05107634f6d4ab2e13f9b7eb4df0e871933a..9647eb784ca433633c8c418328871a412fc8eca0 100644 (file)
  */
 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<ClassLoader> classLoadersFromPlugin(PluginRepository pluginRepository) {
-    List<ClassLoader> 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<ClassLoader> 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<ClassLoader> allPluginClassloaders(PluginRepository pluginRepository) {
+    // accepted limitation: some plugins extend base plugins, sharing the same classloader, so
+    // there may be duplicated classloaders in the list.
+    List<ClassLoader> list = Lists.newArrayList();
+    for (PluginInfo info : pluginRepository.getPluginInfos()) {
+      Plugin plugin = pluginRepository.getPluginInstance(info.getKey());
+      list.add(plugin.getClass().getClassLoader());
+    }
+    return list;
+  }
 }
index 2d51c65fac8e99d0a03d7dcf7cc60dfda10c2152..f4dc7f69eca8b78b575869f6ea0868bdc3903a2d 100644 (file)
@@ -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;
 
index de0f088a8f36c4aca3c726b547d9fdbece5eacd9..c53b6d4b1869772da070774bc8b06dfa1c7224ae 100644 (file)
@@ -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-core/src/main/java/org/sonar/core/platform/ComponentContainer.java b/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java
new file mode 100644 (file)
index 0000000..868208b
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * 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.Iterables;
+import org.picocontainer.Characteristics;
+import org.picocontainer.ComponentAdapter;
+import org.picocontainer.DefaultPicoContainer;
+import org.picocontainer.MutablePicoContainer;
+import org.picocontainer.behaviors.OptInCaching;
+import org.picocontainer.lifecycle.ReflectionLifecycleStrategy;
+import org.picocontainer.monitors.NullComponentMonitor;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.config.PropertyDefinitions;
+
+import javax.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.List;
+
+public class ComponentContainer implements BatchComponent, ServerComponent {
+
+  // no need for multiple children
+  ComponentContainer parent, child;
+  MutablePicoContainer pico;
+  PropertyDefinitions propertyDefinitions;
+  ComponentKeys componentKeys;
+
+  /**
+   * Create root container
+   */
+  public ComponentContainer() {
+    this.parent = null;
+    this.child = null;
+    this.pico = createPicoContainer();
+    this.componentKeys = new ComponentKeys();
+    propertyDefinitions = new PropertyDefinitions();
+    addSingleton(propertyDefinitions);
+    addSingleton(this);
+  }
+
+  /**
+   * Create child container
+   */
+  protected ComponentContainer(ComponentContainer parent) {
+    this.parent = parent;
+    this.pico = parent.pico.makeChildContainer();
+    this.parent.child = this;
+    this.propertyDefinitions = parent.propertyDefinitions;
+    this.componentKeys = new ComponentKeys();
+    addSingleton(this);
+  }
+
+  public void execute() {
+    boolean threw = true;
+    try {
+      startComponents();
+      threw = false;
+    } finally {
+      stopComponents(threw);
+    }
+  }
+
+  /**
+   * This method MUST NOT be renamed start() because the container is registered itself in picocontainer. Starting
+   * a component twice is not authorized.
+   */
+  public ComponentContainer startComponents() {
+    try {
+      doBeforeStart();
+      pico.start();
+      doAfterStart();
+      return this;
+    } catch (Exception e) {
+      throw PicoUtils.propagate(e);
+    }
+  }
+
+  /**
+   * This method aims to be overridden
+   */
+  protected void doBeforeStart() {
+    // nothing
+  }
+
+  /**
+   * This method aims to be overridden
+   */
+  protected void doAfterStart() {
+    // nothing
+  }
+
+  /**
+   * This method MUST NOT be renamed stop() because the container is registered itself in picocontainer. Starting
+   * a component twice is not authorized.
+   */
+  public ComponentContainer stopComponents() {
+    return stopComponents(false);
+  }
+
+  public ComponentContainer stopComponents(boolean swallowException) {
+    try {
+      pico.stop();
+      pico.dispose();
+
+    } catch (RuntimeException e) {
+      if (!swallowException) {
+        throw PicoUtils.propagate(e);
+      }
+    } finally {
+      removeChild();
+      if (parent != null) {
+        parent.removeChild();
+      }
+    }
+    return this;
+  }
+
+  /**
+   * @since 3.5
+   */
+  public ComponentContainer add(Object... objects) {
+    for (Object object : objects) {
+      if (object instanceof ComponentAdapter) {
+        addPicoAdapter((ComponentAdapter) object);
+      } else if (object instanceof Iterable) {
+        add(Iterables.toArray((Iterable) object, Object.class));
+      } else {
+        addSingleton(object);
+      }
+    }
+    return this;
+  }
+
+  public ComponentContainer addSingletons(Collection components) {
+    for (Object component : components) {
+      addSingleton(component);
+    }
+    return this;
+  }
+
+  public ComponentContainer addSingleton(Object component) {
+    return addComponent(component, true);
+  }
+
+  /**
+   * @param singleton return always the same instance if true, else a new instance
+   *                  is returned each time the component is requested
+   */
+  public ComponentContainer addComponent(Object component, boolean singleton) {
+    Object key = componentKeys.of(component);
+    if (component instanceof ComponentAdapter) {
+      pico.addAdapter((ComponentAdapter) component);
+    } else {
+      try {
+        pico.as(singleton ? Characteristics.CACHE : Characteristics.NO_CACHE).addComponent(key, component);
+      } catch (Throwable t) {
+        throw new IllegalStateException("Unable to register component " + getName(component), t);
+      }
+      declareExtension(null, component);
+    }
+    return this;
+  }
+
+  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(pluginInfo, extension);
+    return this;
+  }
+
+  private String getName(Object extension) {
+    if (extension instanceof Class) {
+      return ((Class) extension).getName();
+    }
+    return getName(extension.getClass());
+  }
+
+  public void declareExtension(@Nullable PluginInfo pluginInfo, Object extension) {
+    propertyDefinitions.addComponent(extension, pluginInfo != null ? pluginInfo.getName() : "");
+  }
+
+  public ComponentContainer addPicoAdapter(ComponentAdapter adapter) {
+    pico.addAdapter(adapter);
+    return this;
+  }
+
+  public <T> T getComponentByType(Class<T> tClass) {
+    return pico.getComponent(tClass);
+  }
+
+  public Object getComponentByKey(Object key) {
+    return pico.getComponent(key);
+  }
+
+  public <T> List<T> getComponentsByType(Class<T> tClass) {
+    return pico.getComponents(tClass);
+  }
+
+  public ComponentContainer removeChild() {
+    if (child != null) {
+      pico.removeChildContainer(child.pico);
+      child = null;
+    }
+    return this;
+  }
+
+  public ComponentContainer createChild() {
+    return new ComponentContainer(this);
+  }
+
+  public static MutablePicoContainer createPicoContainer() {
+    ReflectionLifecycleStrategy lifecycleStrategy = new ReflectionLifecycleStrategy(new NullComponentMonitor(), "start", "stop", "close");
+    return new DefaultPicoContainer(new OptInCaching(), lifecycleStrategy, null);
+  }
+
+  public ComponentContainer getParent() {
+    return parent;
+  }
+
+  public ComponentContainer getChild() {
+    return child;
+  }
+
+  public MutablePicoContainer getPicoContainer() {
+    return pico;
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java b/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java
new file mode 100644 (file)
index 0000000..be315cd
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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.sonar.api.utils.internal.Uuids;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+class ComponentKeys {
+
+  private static final Pattern IDENTITY_HASH_PATTERN = Pattern.compile(".+@[a-f0-9]+");
+  private final Set<Class> objectsWithoutToString = new HashSet<>();
+
+  Object of(Object component) {
+    return of(component, Loggers.get(ComponentKeys.class));
+  }
+
+  Object of(Object component, Logger log) {
+    if (component instanceof Class) {
+      return component;
+    }
+    String key = component.toString();
+    if (IDENTITY_HASH_PATTERN.matcher(key).matches()) {
+      if (!objectsWithoutToString.add(component.getClass())) {
+        log.warn(String.format("Bad component key: %s. Please implement toString() method on class %s", key, component.getClass().getName()));
+      }
+      key += Uuids.create();
+    }
+    return new StringBuilder().append(component.getClass().getCanonicalName()).append("-").append(key).toString();
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java b/sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java
new file mode 100644 (file)
index 0000000..ee4e1ae
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.Throwables;
+import org.picocontainer.PicoLifecycleException;
+
+class PicoUtils {
+
+  private PicoUtils() {
+    // only static methods
+  }
+
+  static Throwable sanitize(Throwable t) {
+    Throwable result = t;
+    Throwable cause = t.getCause();
+    if (t instanceof PicoLifecycleException && cause != null) {
+      if ("wrapper".equals(cause.getMessage()) && cause.getCause() != null) {
+        result = cause.getCause();
+      } else {
+        result = cause;
+      }
+    }
+    return result;
+  }
+
+  static RuntimeException propagate(Throwable t) {
+    throw Throwables.propagate(sanitize(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 (file)
index 0000000..e5509bb
--- /dev/null
@@ -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<PluginInfo> {
+
+  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<RequiredPlugin> 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<RequiredPlugin> 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<File, PluginInfo> {
+    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 (file)
index 0000000..336c490
--- /dev/null
@@ -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, ...).
+ * <p/>
+ * 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.
+ * <p/>
+ * 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<String, String> mainClassesByPluginKey = new HashMap<>();
+    final List<File> 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<String, Plugin> load(Map<String, PluginInfo> infoByKeys) {
+    Collection<ClassloaderDef> 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<String, ClassloaderDef> defineClassloaders(Map<String, PluginInfo> infoByKeys) {
+    Map<String, ClassloaderDef> 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<ClassloaderDef> 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<String, ClassLoader> 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<String, Plugin> instantiatePluginInstances(Collection<ClassloaderDef> defs) {
+    // instantiate plugins
+    Map<String, Plugin> instancesByPluginKey = new HashMap<>();
+    for (ClassloaderDef def : defs) {
+      // the same classloader can be used by multiple plugins
+      for (Map.Entry<String, String> 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<Plugin> 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<String, PluginInfo> allPluginsPerKey) {
+    String base = plugin.getKey();
+    String parentKey = plugin.getBasePlugin();
+    while (!Strings.isNullOrEmpty(parentKey)) {
+      PluginInfo parentPlugin = allPluginsPerKey.get(parentKey);
+      base = parentPlugin.getKey();
+      parentKey = parentPlugin.getBasePlugin();
+    }
+    return base;
+  }
+
+  private URL fileToUrl(File file) {
+    try {
+      return file.toURI().toURL();
+    } catch (MalformedURLException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java
new file mode 100644 (file)
index 0000000..1b3b170
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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.sonar.api.BatchComponent;
+import org.sonar.api.Plugin;
+import org.sonar.api.ServerComponent;
+
+import java.util.Collection;
+
+/**
+ * Provides information about the plugins installed in the dependency injection container
+ */
+public interface PluginRepository extends BatchComponent, ServerComponent {
+
+  Collection<PluginInfo> getPluginInfos();
+
+  PluginInfo getPluginInfo(String key);
+
+  /**
+   * @return the instance of {@link Plugin} for the given plugin key. Never return null.
+   */
+  Plugin getPluginInstance(String key);
+
+  boolean hasPlugin(String key);
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java
new file mode 100644 (file)
index 0000000..5ce1ca0
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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.sonar.api.utils.ZipUtils;
+
+import java.util.zip.ZipEntry;
+
+public abstract class PluginUnzipper {
+
+  protected static final String LIB_RELATIVE_PATH_IN_JAR = "META-INF/lib";
+
+  public abstract UnzippedPlugin unzip(PluginInfo info);
+
+  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 (file)
index 0000000..3c73b8d
--- /dev/null
@@ -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<File> libs;
+
+  public UnzippedPlugin(String key, File main, Collection<File> libs) {
+    this.key = key;
+    this.main = main;
+    this.libs = libs;
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  public File getMain() {
+    return main;
+  }
+
+  public Collection<File> 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<File> libs;
+    if (libDir.isDirectory() && libDir.exists()) {
+      libs = listFiles(libDir, null, false);
+    } else {
+      libs = Collections.emptyList();
+    }
+    return new UnzippedPlugin(pluginKey, jarFile, libs);
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/package-info.java b/sonar-core/src/main/java/org/sonar/core/platform/package-info.java
new file mode 100644 (file)
index 0000000..d93af63
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/**
+ * 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 (file)
index a65036b..0000000
+++ /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<PluginMetadata> {
-  private File file;
-  private List<File> deployedFiles;
-  private List<String> 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<String> 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<File> getDeployedFiles() {
-    return deployedFiles;
-  }
-
-  public DefaultPluginMetadata addDeployedFile(File f) {
-    this.deployedFiles.add(f);
-    return this;
-  }
-
-  public List<String> getPathsToInternalDeps() {
-    return ImmutableList.copyOf(pathsToInternalDeps);
-  }
-
-  public DefaultPluginMetadata setPathsToInternalDeps(List<String> 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<String> getRequiredPlugins() {
-    return ImmutableList.copyOf(requiredPlugins);
-  }
-
-  public DefaultPluginMetadata setRequiredPlugins(List<String> 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 <code>true</code> 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 (file)
index 8a6b4b8..0000000
+++ /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}.
- * <p/>
- * <h3>IMPORTANT</h3>
- * <p>
- * If we have pluginA , then all classes and resources from package and subpackages of <b>org.sonar.plugins.pluginA.api</b> will be visible
- * for all other plugins even if they are located in dependent library.
- * </p>
- * <p/>
- * <h4>Search order for {@link ClassRealm} :</h4>
- * <ul>
- * <li>parent class loader (passed via the constructor) if there is one</li>
- * <li>imports</li>
- * <li>realm's constituents</li>
- * <li>parent realm</li>
- * </ul>
- */
-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<String, Plugin> init(Collection<PluginMetadata> plugins) {
-    List<PluginMetadata> 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<String, Plugin> 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<URL> resources = Lists.newArrayList();
-      List<URL> 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 (file)
index 7c5114a..0000000
+++ /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<File, DefaultPluginMetadata> fileToPlugin() {
-    return jarFileToPlugin;
-  }
-
-  public Function<File, DefaultPluginMetadata> fileToCorePlugin() {
-    return jarFileToCorePlugin;
-  }
-
-  private final Function<File, DefaultPluginMetadata> jarFileToCorePlugin = new Function<File, DefaultPluginMetadata>() {
-    @Override
-    public DefaultPluginMetadata apply(@Nonnull File file) {
-      return extractMetadata(file, true);
-    }
-  };
-  private final Function<File, DefaultPluginMetadata> jarFileToPlugin = new Function<File, DefaultPluginMetadata>() {
-    @Override
-    public DefaultPluginMetadata apply(@Nonnull File file) {
-      return extractMetadata(file, false);
-    }
-  };
-}
index 3bc254a3ff187f55be7eeccf11e079424a4f87c4..6fecfe0ae91897a1f01defb82895ab34419c7a90 100644 (file)
@@ -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/ResourcesClassloader.java b/sonar-core/src/main/java/org/sonar/core/plugins/ResourcesClassloader.java
deleted file mode 100644 (file)
index 04aea1f..0000000
+++ /dev/null
@@ -1,49 +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.Lists;
-import org.apache.commons.lang.StringUtils;
-
-import java.net.URL;
-import java.net.URLClassLoader;
-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<URL> urls;
-
-  public ResourcesClassloader(Collection<URL> urls, ClassLoader parent) {
-    super(new URL[] {}, parent);
-    this.urls = Lists.newArrayList(urls);
-  }
-
-  @Override
-  public URL findResource(String name) {
-    for (URL url : urls) {
-      if (StringUtils.endsWith(url.getPath(), name)) {
-        return url;
-      }
-    }
-    return null;
-  }
-}
index d3208e7de692e6fc66fcdc71f316ca4758c36dd2..e95a48da83b244f82fcdba35dacebf2460ccbd38 100644 (file)
@@ -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;
 
index b3acbde594af53ce62059c0c11760d26e2bc349d..110694aff65e8a73434f05868a737c0fa0686c3b 100644 (file)
@@ -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<PluginMetadata> plugins = Arrays.asList(newPlugin("core"), newPlugin("sqale"), newPlugin("frpack"), newPlugin("checkstyle"), newPlugin("other"));
-    when(pluginRepository.getMetadata()).thenReturn(plugins);
+    List<PluginInfo> 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;
   }
index 971a7573965b15e6e2a1f650061b03c8cbba5e5a..19f9add77ccf81f42f107fd72cab062abe879bbf 100644 (file)
@@ -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-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java b/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java
new file mode 100644 (file)
index 0000000..5d7923d
--- /dev/null
@@ -0,0 +1,392 @@
+/*
+ * 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.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.picocontainer.injectors.ProviderAdapter;
+import org.sonar.api.Property;
+import org.sonar.api.config.PropertyDefinitions;
+
+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.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+public class ComponentContainerTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void shouldRegisterItself() {
+    ComponentContainer container = new ComponentContainer();
+    assertThat(container.getComponentByType(ComponentContainer.class)).isSameAs(container);
+  }
+
+  @Test
+  public void should_start_and_stop() {
+    ComponentContainer container = spy(new ComponentContainer());
+    container.addSingleton(StartableComponent.class);
+    container.startComponents();
+
+    assertThat(container.getComponentByType(StartableComponent.class).started).isTrue();
+    assertThat(container.getComponentByType(StartableComponent.class).stopped).isFalse();
+    verify(container).doBeforeStart();
+    verify(container).doAfterStart();
+
+    container.stopComponents();
+    assertThat(container.getComponentByType(StartableComponent.class).stopped).isTrue();
+  }
+
+  @Test
+  public void should_start_and_stop_hierarchy_of_containers() {
+    StartableComponent parentComponent = new StartableComponent();
+    final StartableComponent childComponent = new StartableComponent();
+    ComponentContainer parentContainer = new ComponentContainer() {
+      @Override
+      public void doAfterStart() {
+        ComponentContainer childContainer = new ComponentContainer(this);
+        childContainer.add(childComponent);
+        childContainer.execute();
+      }
+    };
+    parentContainer.add(parentComponent);
+    parentContainer.execute();
+    assertThat(parentComponent.started).isTrue();
+    assertThat(parentComponent.stopped).isTrue();
+    assertThat(childComponent.started).isTrue();
+    assertThat(childComponent.stopped).isTrue();
+  }
+
+  @Test
+  public void should_stop_hierarchy_of_containers_on_failure() {
+    StartableComponent parentComponent = new StartableComponent();
+    final StartableComponent childComponent1 = new StartableComponent();
+    final UnstartableComponent childComponent2 = new UnstartableComponent();
+    ComponentContainer parentContainer = new ComponentContainer() {
+      @Override
+      public void doAfterStart() {
+        ComponentContainer childContainer = new ComponentContainer(this);
+        childContainer.add(childComponent1);
+        childContainer.add(childComponent2);
+        childContainer.execute();
+      }
+    };
+    parentContainer.add(parentComponent);
+    try {
+      parentContainer.execute();
+      fail();
+    } catch (Exception e) {
+      assertThat(parentComponent.started).isTrue();
+      assertThat(parentComponent.stopped).isTrue();
+      assertThat(childComponent1.started).isTrue();
+      assertThat(childComponent1.stopped).isTrue();
+    }
+  }
+
+  @Test
+  public void testChild() {
+    ComponentContainer parent = new ComponentContainer();
+    parent.startComponents();
+
+    ComponentContainer child = parent.createChild();
+    child.addSingleton(StartableComponent.class);
+    child.startComponents();
+
+    assertThat(child.getParent()).isSameAs(parent);
+    assertThat(parent.getChild()).isSameAs(child);
+    assertThat(child.getComponentByType(ComponentContainer.class)).isSameAs(child);
+    assertThat(parent.getComponentByType(ComponentContainer.class)).isSameAs(parent);
+    assertThat(child.getComponentByType(StartableComponent.class)).isNotNull();
+    assertThat(parent.getComponentByType(StartableComponent.class)).isNull();
+
+    parent.stopComponents();
+  }
+
+  @Test
+  public void testRemoveChild() {
+    ComponentContainer parent = new ComponentContainer();
+    parent.startComponents();
+
+    ComponentContainer child = parent.createChild();
+    assertThat(parent.getChild()).isSameAs(child);
+
+    parent.removeChild();
+    assertThat(parent.getChild()).isNull();
+  }
+
+  @Test
+  public void shouldForwardStartAndStopToDescendants() {
+    ComponentContainer grandParent = new ComponentContainer();
+    ComponentContainer parent = grandParent.createChild();
+    ComponentContainer child = parent.createChild();
+    child.addSingleton(StartableComponent.class);
+
+    grandParent.startComponents();
+
+    StartableComponent component = child.getComponentByType(StartableComponent.class);
+    assertTrue(component.started);
+
+    parent.stopComponents();
+    assertTrue(component.stopped);
+  }
+
+  @Test
+  public void shouldDeclareComponentProperties() {
+    ComponentContainer container = new ComponentContainer();
+    container.addSingleton(ComponentWithProperty.class);
+
+    PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
+    assertThat(propertyDefinitions.get("foo")).isNotNull();
+    assertThat(propertyDefinitions.get("foo").defaultValue()).isEqualTo("bar");
+  }
+
+  @Test
+  public void shouldDeclareExtensionWithoutAddingIt() {
+    ComponentContainer container = new ComponentContainer();
+    PluginInfo plugin = mock(PluginInfo.class);
+    container.declareExtension(plugin, ComponentWithProperty.class);
+
+    PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
+    assertThat(propertyDefinitions.get("foo")).isNotNull();
+    assertThat(container.getComponentByType(ComponentWithProperty.class)).isNull();
+  }
+
+  @Test
+  public void shouldDeclareExtensionWhenAdding() {
+    ComponentContainer container = new ComponentContainer();
+    PluginInfo plugin = mock(PluginInfo.class);
+    container.addExtension(plugin, ComponentWithProperty.class);
+
+    PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
+    assertThat(propertyDefinitions.get("foo")).isNotNull();
+    assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull();
+    assertThat(container.getComponentByKey(ComponentWithProperty.class)).isNotNull();
+  }
+
+  @Test
+  public void test_add_class() {
+    ComponentContainer container = new ComponentContainer();
+    container.add(ComponentWithProperty.class, SimpleComponent.class);
+    assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull();
+    assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull();
+  }
+
+  @Test
+  public void test_add_collection() {
+    ComponentContainer container = new ComponentContainer();
+    container.add(Arrays.asList(ComponentWithProperty.class, SimpleComponent.class));
+    assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull();
+    assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull();
+  }
+
+  @Test
+  public void test_add_adapter() {
+    ComponentContainer container = new ComponentContainer();
+    container.add(new SimpleComponentProvider());
+    assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull();
+  }
+
+  @Test
+  public void should_sanitize_pico_exception_on_start_failure() {
+    ComponentContainer container = new ComponentContainer();
+    container.add(UnstartableComponent.class);
+
+    // do not expect a PicoException
+    thrown.expect(IllegalStateException.class);
+    container.startComponents();
+  }
+
+  @Test
+  public void display_plugin_name_when_failing_to_add_extension() {
+    ComponentContainer container = new ComponentContainer();
+    PluginInfo plugin = mock(PluginInfo.class);
+
+    container.startComponents();
+
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Unable to register extension org.sonar.core.platform.ComponentContainerTest$UnstartableComponent");
+
+    container.addExtension(plugin, UnstartableComponent.class);
+
+  }
+
+  @Test
+  public void test_start_failure() {
+    ComponentContainer container = new ComponentContainer();
+    StartableComponent startable = new StartableComponent();
+    container.add(startable, UnstartableComponent.class);
+
+    try {
+      container.execute();
+      fail();
+    } catch (Exception e) {
+      assertThat(startable.started).isTrue();
+
+      // container stops the components that have already been started
+      assertThat(startable.stopped).isTrue();
+    }
+  }
+
+  @Test
+  public void test_stop_failure() {
+    ComponentContainer container = new ComponentContainer();
+    StartableComponent startable = new StartableComponent();
+    container.add(startable, UnstoppableComponent.class);
+
+    try {
+      container.execute();
+      fail();
+    } catch (Exception e) {
+      assertThat(startable.started).isTrue();
+
+      // container should stop the components that have already been started
+      // ... but that's not the case
+    }
+  }
+
+  @Test
+  public void stop_exception_should_not_hide_start_exception() {
+    ComponentContainer container = new ComponentContainer();
+    container.add(UnstartableComponent.class, UnstoppableComponent.class);
+
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Fail to start");
+    container.execute();
+  }
+
+  @Test
+  public void should_execute_components() {
+    ComponentContainer container = new ComponentContainer();
+    StartableComponent component = new StartableComponent();
+    container.add(component);
+
+    container.execute();
+
+    assertThat(component.started).isTrue();
+    assertThat(component.stopped).isTrue();
+  }
+
+  /**
+   * Method close() must be called even if the methods start() or stop()
+   * are not defined.
+   */
+  @Test
+  public void should_close_components_without_lifecycle() {
+    ComponentContainer container = new ComponentContainer();
+    CloseableComponent component = new CloseableComponent();
+    container.add(component);
+
+    container.execute();
+
+    assertThat(component.isClosed).isTrue();
+  }
+
+  /**
+   * Method close() must be executed after stop()
+   */
+  @Test
+  public void should_close_components_with_lifecycle() {
+    ComponentContainer container = new ComponentContainer();
+    StartableCloseableComponent component = new StartableCloseableComponent();
+    container.add(component);
+
+    container.execute();
+
+    assertThat(component.isStopped).isTrue();
+    assertThat(component.isClosed).isTrue();
+    assertThat(component.isClosedAfterStop).isTrue();
+  }
+
+  public static class StartableComponent {
+    public boolean started = false, stopped = false;
+
+    public void start() {
+      started = true;
+    }
+
+    public void stop() {
+      stopped = true;
+    }
+  }
+
+  public static class UnstartableComponent {
+    public void start() {
+      throw new IllegalStateException("Fail to start");
+    }
+
+    public void stop() {
+
+    }
+  }
+
+  public static class UnstoppableComponent {
+    public void start() {
+    }
+
+    public void stop() {
+      throw new IllegalStateException("Fail to stop");
+    }
+  }
+
+  @Property(key = "foo", defaultValue = "bar", name = "Foo")
+  public static class ComponentWithProperty {
+
+  }
+
+  public static class SimpleComponent {
+
+  }
+
+  public static class SimpleComponentProvider extends ProviderAdapter {
+    public SimpleComponent provide() {
+      return new SimpleComponent();
+    }
+  }
+
+  public static class CloseableComponent implements AutoCloseable {
+    public boolean isClosed = false;
+
+    @Override
+    public void close() throws Exception {
+      isClosed = true;
+    }
+  }
+
+  public static class StartableCloseableComponent implements AutoCloseable {
+    public boolean isClosed = false, isStopped = false, isClosedAfterStop = false;
+
+    public void stop() {
+      isStopped = true;
+    }
+
+    @Override
+    public void close() throws Exception {
+      isClosed = true;
+      isClosedAfterStop = isStopped;
+    }
+  }
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/ComponentKeysTest.java b/sonar-core/src/test/java/org/sonar/core/platform/ComponentKeysTest.java
new file mode 100644 (file)
index 0000000..431890b
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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.junit.Test;
+import org.sonar.api.utils.log.Logger;
+
+import static org.assertj.core.api.Assertions.assertThat;
+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 {
+
+  ComponentKeys keys = new ComponentKeys();
+
+  @Test
+  public void generate_key_of_class() {
+    assertThat(keys.of(FakeComponent.class)).isEqualTo(FakeComponent.class);
+  }
+
+  @Test
+  public void generate_key_of_object() throws Exception {
+    assertThat(keys.of(new FakeComponent())).isEqualTo("org.sonar.core.platform.ComponentKeysTest.FakeComponent-fake");
+  }
+
+  @Test
+  public void should_log_warning_if_toString_is_not_overridden() {
+    Logger log = mock(Logger.class);
+    keys.of(new Object(), log);
+    verifyZeroInteractions(log);
+
+    // only on non-first runs, to avoid false-positives on singletons
+    keys.of(new Object(), log);
+    verify(log).warn(startsWith("Bad component key"));
+  }
+
+  @Test
+  public void should_generate_unique_key_when_toString_is_not_overridden() {
+    Object key = keys.of(new WrongToStringImpl());
+    assertThat(key).isNotEqualTo(WrongToStringImpl.KEY);
+
+    Object key2 = keys.of(new WrongToStringImpl());
+    assertThat(key2).isNotEqualTo(key);
+  }
+
+  static class FakeComponent {
+    @Override
+    public String toString() {
+      return "fake";
+    }
+  }
+
+  static class WrongToStringImpl {
+    static final String KEY = "my.Component@123a";
+
+    @Override
+    public String toString() {
+      return KEY;
+    }
+  }
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java
new file mode 100644 (file)
index 0000000..67b4885
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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.junit.Test;
+import org.picocontainer.Characteristics;
+import org.picocontainer.MutablePicoContainer;
+import org.picocontainer.PicoLifecycleException;
+
+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));
+
+    assertThat(th).isInstanceOf(IllegalStateException.class);
+    assertThat(th.getMessage()).isEqualTo("A good reason to fail");
+  }
+
+  @Test
+  public void shouldSanitizePicoLifecycleException_no_wrapper_message() {
+    Throwable th = PicoUtils.sanitize(new PicoLifecycleException(null, null, new IllegalStateException("msg")));
+
+    assertThat(th).isInstanceOf(IllegalStateException.class);
+    assertThat(th.getMessage()).isEqualTo("msg");
+  }
+
+  @Test
+  public void shouldNotSanitizeOtherExceptions() {
+    Throwable th = PicoUtils.sanitize(new IllegalArgumentException("foo"));
+
+    assertThat(th).isInstanceOf(IllegalArgumentException.class);
+    assertThat(th.getMessage()).isEqualTo("foo");
+  }
+
+  @Test
+  public void shouldPropagateInitialUncheckedException() {
+    try {
+      PicoUtils.propagate(newPicoLifecycleException(false));
+      fail();
+    } catch (RuntimeException e) {
+      assertThat(e).isInstanceOf(IllegalStateException.class);
+    }
+  }
+
+  @Test
+  public void shouldThrowUncheckedExceptionWhenPropagatingCheckedException() {
+    try {
+      PicoUtils.propagate(newPicoLifecycleException(true));
+      fail();
+    } catch (RuntimeException e) {
+      assertThat(e.getCause()).isInstanceOf(IOException.class);
+      assertThat(e.getCause().getMessage()).isEqualTo("Checked");
+    }
+  }
+
+  private PicoLifecycleException newPicoLifecycleException(boolean initialCheckedException) {
+    MutablePicoContainer container = ComponentContainer.createPicoContainer().as(Characteristics.CACHE);
+    if (initialCheckedException) {
+      container.addComponent(CheckedFailureComponent.class);
+    } else {
+      container.addComponent(UncheckedFailureComponent.class);
+    }
+    try {
+      container.start();
+      return null;
+
+    } catch (PicoLifecycleException e) {
+      return e;
+    }
+  }
+
+  public static class UncheckedFailureComponent {
+    public void start() {
+      throw new IllegalStateException("A good reason to fail");
+    }
+  }
+
+  public static class CheckedFailureComponent {
+    public void start() throws IOException {
+      throw new IOException("Checked");
+    }
+  }
+}
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 (file)
index 0000000..2b70274
--- /dev/null
@@ -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<PluginInfo> plugins = Arrays.asList(java1, java2, cobol);
+    Collections.shuffle(plugins);
+
+    List<PluginInfo> 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 (file)
index 0000000..7cb984b
--- /dev/null
@@ -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<String, Plugin> 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<String, PluginLoader.ClassloaderDef> 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<String, PluginLoader.ClassloaderDef> 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.<File>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 (file)
index 0000000..cbdd9c2
--- /dev/null
@@ -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 (file)
index 98cdf26..0000000
+++ /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<DefaultPluginMetadata> 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 (file)
index d9b3ada..0000000
+++ /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<String, Plugin> map = classloaders.init(Arrays.<PluginMetadata>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<String, Plugin> map = classloaders.init(Arrays.<PluginMetadata>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.<PluginMetadata>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 (file)
index b66bf86..0000000
+++ /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/java/org/sonar/core/plugins/ResourcesClassloaderTest.java b/sonar-core/src/test/java/org/sonar/core/plugins/ResourcesClassloaderTest.java
deleted file mode 100644 (file)
index e2f6dad..0000000
+++ /dev/null
@@ -1,40 +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 java.net.URL;
-import java.util.Arrays;
-import java.util.List;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.notNullValue;
-
-public class ResourcesClassloaderTest {
-
-  @Test
-  public void test() throws Exception {
-    List<URL> 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());
-  }
-}
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 (file)
index 343ad65..0000000
Binary files a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jar and /dev/null differ
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 (file)
index 505311c..0000000
Binary files a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/foo.jar and /dev/null differ
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 (file)
index 75a263d..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<fake/>
\ 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 (file)
index f4b8b79..0000000
Binary files a/sonar-core/src/test/resources/org/sonar/core/plugins/fake2-plugin-1.1.jar and /dev/null differ
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 (file)
index 4ae5393..0000000
Binary files a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar and /dev/null differ
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 (file)
index 6a74b55..0000000
Binary files a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-cobertura-plugin-3.1.1.jar and /dev/null differ
diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-switch-off-violations-plugin-1.1.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-switch-off-violations-plugin-1.1.jar
deleted file mode 100644 (file)
index 8044dff..0000000
Binary files a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-switch-off-violations-plugin-1.1.jar and /dev/null differ
index e5922bf75f0c88460dc2d5b2be7321b619d6b7be..358d4bbef407010a4b6872a5839ddde7ef365d9b 100644 (file)
@@ -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;
 
index 3588a908b4be540295c971761495ce1ef5ab51eb..63a1168639d8bb357e97cf663531881a2e0f6cc4 100644 (file)
 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");
-    }
-  }
 }
index be82711e85e2b5f7281a497e72e919e8bef3f202..2d90503598d906053cd6794ca081ffabbe6768d0 100644 (file)
@@ -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);
-  }
 }
index ea7bbc755e9272b9d7887fa6896b76c1bb63757b..23f5021d3fbb60a470ce9ed1dce383c17da786ea 100644 (file)
@@ -24,9 +24,9 @@ import java.util.List;
 /**
  * A plugin is a group of extensions. See <code>org.sonar.api.Extension</code> interface to browse
  * available extension points.
- * <p/>
  * <p>The manifest property <code>Plugin-Class</code> must declare the name of the implementation class.
  * It is automatically set by sonar-packaging-maven-plugin when building plugins.</p>
+ * <p>Implementation must declare a public constructor with no-parameters.</p>
  *
  * @see org.sonar.api.Extension
  * @since 1.10
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentContainer.java b/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentContainer.java
deleted file mode 100644 (file)
index e8555ed..0000000
+++ /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.api.platform;
-
-import com.google.common.collect.Iterables;
-import org.picocontainer.Characteristics;
-import org.picocontainer.ComponentAdapter;
-import org.picocontainer.DefaultPicoContainer;
-import org.picocontainer.MutablePicoContainer;
-import org.picocontainer.behaviors.OptInCaching;
-import org.picocontainer.lifecycle.ReflectionLifecycleStrategy;
-import org.picocontainer.monitors.NullComponentMonitor;
-import org.sonar.api.BatchComponent;
-import org.sonar.api.ServerComponent;
-import org.sonar.api.config.PropertyDefinitions;
-
-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
-  ComponentContainer parent, child;
-  MutablePicoContainer pico;
-  PropertyDefinitions propertyDefinitions;
-  ComponentKeys componentKeys;
-
-  /**
-   * Create root container
-   */
-  public ComponentContainer() {
-    this.parent = null;
-    this.child = null;
-    this.pico = createPicoContainer();
-    this.componentKeys = new ComponentKeys();
-    propertyDefinitions = new PropertyDefinitions();
-    addSingleton(propertyDefinitions);
-    addSingleton(this);
-  }
-
-  /**
-   * Create child container
-   */
-  protected ComponentContainer(ComponentContainer parent) {
-    this.parent = parent;
-    this.pico = parent.pico.makeChildContainer();
-    this.parent.child = this;
-    this.propertyDefinitions = parent.propertyDefinitions;
-    this.componentKeys = new ComponentKeys();
-    addSingleton(this);
-  }
-
-  public void execute() {
-    boolean threw = true;
-    try {
-      startComponents();
-      threw = false;
-    } finally {
-      stopComponents(threw);
-    }
-  }
-
-  /**
-   * This method MUST NOT be renamed start() because the container is registered itself in picocontainer. Starting
-   * a component twice is not authorized.
-   */
-  public ComponentContainer startComponents() {
-    try {
-      doBeforeStart();
-      pico.start();
-      doAfterStart();
-      return this;
-    } catch (Exception e) {
-      throw PicoUtils.propagate(e);
-    }
-  }
-
-  /**
-   * This method aims to be overridden
-   */
-  protected void doBeforeStart() {
-    // nothing
-  }
-
-  /**
-   * This method aims to be overridden
-   */
-  protected void doAfterStart() {
-    // nothing
-  }
-
-  /**
-   * This method MUST NOT be renamed stop() because the container is registered itself in picocontainer. Starting
-   * a component twice is not authorized.
-   */
-  public ComponentContainer stopComponents() {
-    return stopComponents(false);
-  }
-
-  public ComponentContainer stopComponents(boolean swallowException) {
-    try {
-      pico.stop();
-      pico.dispose();
-
-    } catch (RuntimeException e) {
-      if (!swallowException) {
-        throw PicoUtils.propagate(e);
-      }
-    } finally {
-      removeChild();
-      if (parent != null) {
-        parent.removeChild();
-      }
-    }
-    return this;
-  }
-
-  /**
-   * @since 3.5
-   */
-  public ComponentContainer add(Object... objects) {
-    for (Object object : objects) {
-      if (object instanceof ComponentAdapter) {
-        addPicoAdapter((ComponentAdapter) object);
-      } else if (object instanceof Iterable) {
-        add(Iterables.toArray((Iterable) object, Object.class));
-      } else {
-        addSingleton(object);
-      }
-    }
-    return this;
-  }
-
-  public ComponentContainer addSingletons(Collection components) {
-    for (Object component : components) {
-      addSingleton(component);
-    }
-    return this;
-  }
-
-  public ComponentContainer addSingleton(Object component) {
-    return addComponent(component, true);
-  }
-
-  /**
-   * @param singleton return always the same instance if true, else a new instance
-   *                  is returned each time the component is requested
-   */
-  public ComponentContainer addComponent(Object component, boolean singleton) {
-    Object key = componentKeys.of(component);
-    if (component instanceof ComponentAdapter) {
-      pico.addAdapter((ComponentAdapter) component);
-    } else {
-      try {
-        pico.as(singleton ? Characteristics.CACHE : Characteristics.NO_CACHE).addComponent(key, component);
-      } catch (Throwable t) {
-        throw new IllegalStateException("Unable to register component " + getName(component), t);
-      }
-      declareExtension(null, component);
-    }
-    return this;
-  }
-
-  public ComponentContainer addExtension(@Nullable PluginMetadata plugin, 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);
-    return this;
-  }
-
-  private String getName(Object extension) {
-    if (extension instanceof Class) {
-      return ((Class) extension).getName();
-    }
-    return getName(extension.getClass());
-  }
-
-  public void declareExtension(@Nullable PluginMetadata plugin, Object extension) {
-    propertyDefinitions.addComponent(extension, plugin != null ? plugin.getName() : "");
-  }
-
-  public ComponentContainer addPicoAdapter(ComponentAdapter adapter) {
-    pico.addAdapter(adapter);
-    return this;
-  }
-
-  public <T> T getComponentByType(Class<T> tClass) {
-    return pico.getComponent(tClass);
-  }
-
-  public Object getComponentByKey(Object key) {
-    return pico.getComponent(key);
-  }
-
-  public <T> List<T> getComponentsByType(Class<T> tClass) {
-    return pico.getComponents(tClass);
-  }
-
-  public ComponentContainer removeChild() {
-    if (child != null) {
-      pico.removeChildContainer(child.pico);
-      child = null;
-    }
-    return this;
-  }
-
-  public ComponentContainer createChild() {
-    return new ComponentContainer(this);
-  }
-
-  static MutablePicoContainer createPicoContainer() {
-    ReflectionLifecycleStrategy lifecycleStrategy = new ReflectionLifecycleStrategy(new NullComponentMonitor(), "start", "stop", "close");
-    return new DefaultPicoContainer(new OptInCaching(), lifecycleStrategy, null);
-  }
-
-  public ComponentContainer getParent() {
-    return parent;
-  }
-
-  public ComponentContainer getChild() {
-    return child;
-  }
-
-  public MutablePicoContainer getPicoContainer() {
-    return pico;
-  }
-}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentKeys.java b/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentKeys.java
deleted file mode 100644 (file)
index 56de51f..0000000
+++ /dev/null
@@ -1,57 +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 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;
-
-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<Class> objectsWithoutToString = new HashSet<Class>();
-
-  Object of(Object component) {
-    return of(component, Loggers.get(ComponentKeys.class));
-  }
-
-  @VisibleForTesting
-  Object of(Object component, Logger log) {
-    if (component instanceof Class) {
-      return component;
-    }
-    String key = component.toString();
-    if (IDENTITY_HASH_PATTERN.matcher(key).matches()) {
-      if (!objectsWithoutToString.add(component.getClass())) {
-        log.warn(String.format("Bad component key: %s. Please implement toString() method on class %s", key, component.getClass().getName()));
-      }
-      key += Uuids.create();
-    }
-    return new StringBuilder().append(component.getClass().getCanonicalName()).append("-").append(key).toString();
-  }
-}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PicoUtils.java b/sonar-plugin-api/src/main/java/org/sonar/api/platform/PicoUtils.java
deleted file mode 100644 (file)
index b14d886..0000000
+++ /dev/null
@@ -1,46 +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 com.google.common.base.Throwables;
-import org.picocontainer.PicoLifecycleException;
-
-class PicoUtils {
-
-  private PicoUtils() {
-  }
-
-  static Throwable sanitize(Throwable t) {
-    Throwable result = t;
-    Throwable cause = t.getCause();
-    if (t instanceof PicoLifecycleException && cause != null) {
-      if ("wrapper".equals(cause.getMessage()) && cause.getCause() != null) {
-        result = cause.getCause();
-      } else {
-        result = cause;
-      }
-    }
-    return result;
-  }
-
-  static RuntimeException propagate(Throwable t) {
-    throw Throwables.propagate(sanitize(t));
-  }
-}
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 (file)
index 97ab71b..0000000
+++ /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<File> 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 <code>null</code> 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<String> getRequiredPlugins();
-
-  boolean isCore();
-
-  String getImplementationBuild();
-}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginRepository.java b/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginRepository.java
deleted file mode 100644 (file)
index ad26ec2..0000000
+++ /dev/null
@@ -1,48 +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 org.sonar.api.BatchComponent;
-import org.sonar.api.Plugin;
-import org.sonar.api.ServerComponent;
-
-import javax.annotation.CheckForNull;
-import java.util.Collection;
-
-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<PluginMetadata> getMetadata();
-
-  /**
-   * Search for an installed plugin. Returns null if the plugin is not installed.
-   * @since 2.9
-   */
-  @CheckForNull
-  PluginMetadata getMetadata(String pluginKey);
-}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java
deleted file mode 100644 (file)
index 8cbe1f5..0000000
+++ /dev/null
@@ -1,390 +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 org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.picocontainer.injectors.ProviderAdapter;
-import org.sonar.api.Property;
-import org.sonar.api.config.PropertyDefinitions;
-
-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.*;
-
-public class ComponentContainerTest {
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  @Test
-  public void shouldRegisterItself() {
-    ComponentContainer container = new ComponentContainer();
-    assertThat(container.getComponentByType(ComponentContainer.class)).isSameAs(container);
-  }
-
-  @Test
-  public void should_start_and_stop() {
-    ComponentContainer container = spy(new ComponentContainer());
-    container.addSingleton(StartableComponent.class);
-    container.startComponents();
-
-    assertThat(container.getComponentByType(StartableComponent.class).started).isTrue();
-    assertThat(container.getComponentByType(StartableComponent.class).stopped).isFalse();
-    verify(container).doBeforeStart();
-    verify(container).doAfterStart();
-
-    container.stopComponents();
-    assertThat(container.getComponentByType(StartableComponent.class).stopped).isTrue();
-  }
-
-  @Test
-  public void should_start_and_stop_hierarchy_of_containers() {
-    StartableComponent parentComponent = new StartableComponent();
-    final StartableComponent childComponent = new StartableComponent();
-    ComponentContainer parentContainer = new ComponentContainer() {
-      @Override
-      public void doAfterStart() {
-        ComponentContainer childContainer = new ComponentContainer(this);
-        childContainer.add(childComponent);
-        childContainer.execute();
-      }
-    };
-    parentContainer.add(parentComponent);
-    parentContainer.execute();
-    assertThat(parentComponent.started).isTrue();
-    assertThat(parentComponent.stopped).isTrue();
-    assertThat(childComponent.started).isTrue();
-    assertThat(childComponent.stopped).isTrue();
-  }
-
-  @Test
-  public void should_stop_hierarchy_of_containers_on_failure() {
-    StartableComponent parentComponent = new StartableComponent();
-    final StartableComponent childComponent1 = new StartableComponent();
-    final UnstartableComponent childComponent2 = new UnstartableComponent();
-    ComponentContainer parentContainer = new ComponentContainer() {
-      @Override
-      public void doAfterStart() {
-        ComponentContainer childContainer = new ComponentContainer(this);
-        childContainer.add(childComponent1);
-        childContainer.add(childComponent2);
-        childContainer.execute();
-      }
-    };
-    parentContainer.add(parentComponent);
-    try {
-      parentContainer.execute();
-      fail();
-    } catch (Exception e) {
-      assertThat(parentComponent.started).isTrue();
-      assertThat(parentComponent.stopped).isTrue();
-      assertThat(childComponent1.started).isTrue();
-      assertThat(childComponent1.stopped).isTrue();
-    }
-  }
-
-  @Test
-  public void testChild() {
-    ComponentContainer parent = new ComponentContainer();
-    parent.startComponents();
-
-    ComponentContainer child = parent.createChild();
-    child.addSingleton(StartableComponent.class);
-    child.startComponents();
-
-    assertThat(child.getParent()).isSameAs(parent);
-    assertThat(parent.getChild()).isSameAs(child);
-    assertThat(child.getComponentByType(ComponentContainer.class)).isSameAs(child);
-    assertThat(parent.getComponentByType(ComponentContainer.class)).isSameAs(parent);
-    assertThat(child.getComponentByType(StartableComponent.class)).isNotNull();
-    assertThat(parent.getComponentByType(StartableComponent.class)).isNull();
-
-    parent.stopComponents();
-  }
-
-  @Test
-  public void testRemoveChild() {
-    ComponentContainer parent = new ComponentContainer();
-    parent.startComponents();
-
-    ComponentContainer child = parent.createChild();
-    assertThat(parent.getChild()).isSameAs(child);
-
-    parent.removeChild();
-    assertThat(parent.getChild()).isNull();
-  }
-
-  @Test
-  public void shouldForwardStartAndStopToDescendants() {
-    ComponentContainer grandParent = new ComponentContainer();
-    ComponentContainer parent = grandParent.createChild();
-    ComponentContainer child = parent.createChild();
-    child.addSingleton(StartableComponent.class);
-
-    grandParent.startComponents();
-
-    StartableComponent component = child.getComponentByType(StartableComponent.class);
-    assertTrue(component.started);
-
-    parent.stopComponents();
-    assertTrue(component.stopped);
-  }
-
-  @Test
-  public void shouldDeclareComponentProperties() {
-    ComponentContainer container = new ComponentContainer();
-    container.addSingleton(ComponentWithProperty.class);
-
-    PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
-    assertThat(propertyDefinitions.get("foo")).isNotNull();
-    assertThat(propertyDefinitions.get("foo").defaultValue()).isEqualTo("bar");
-  }
-
-  @Test
-  public void shouldDeclareExtensionWithoutAddingIt() {
-    ComponentContainer container = new ComponentContainer();
-    PluginMetadata plugin = mock(PluginMetadata.class);
-    container.declareExtension(plugin, ComponentWithProperty.class);
-
-    PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
-    assertThat(propertyDefinitions.get("foo")).isNotNull();
-    assertThat(container.getComponentByType(ComponentWithProperty.class)).isNull();
-  }
-
-  @Test
-  public void shouldDeclareExtensionWhenAdding() {
-    ComponentContainer container = new ComponentContainer();
-    PluginMetadata plugin = mock(PluginMetadata.class);
-    container.addExtension(plugin, ComponentWithProperty.class);
-
-    PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class);
-    assertThat(propertyDefinitions.get("foo")).isNotNull();
-    assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull();
-    assertThat(container.getComponentByKey(ComponentWithProperty.class)).isNotNull();
-  }
-
-  @Test
-  public void test_add_class() {
-    ComponentContainer container = new ComponentContainer();
-    container.add(ComponentWithProperty.class, SimpleComponent.class);
-    assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull();
-    assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull();
-  }
-
-  @Test
-  public void test_add_collection() {
-    ComponentContainer container = new ComponentContainer();
-    container.add(Arrays.asList(ComponentWithProperty.class, SimpleComponent.class));
-    assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull();
-    assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull();
-  }
-
-  @Test
-  public void test_add_adapter() {
-    ComponentContainer container = new ComponentContainer();
-    container.add(new SimpleComponentProvider());
-    assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull();
-  }
-
-  @Test
-  public void should_sanitize_pico_exception_on_start_failure() {
-    ComponentContainer container = new ComponentContainer();
-    container.add(UnstartableComponent.class);
-
-    // do not expect a PicoException
-    thrown.expect(IllegalStateException.class);
-    container.startComponents();
-  }
-
-  @Test
-  public void display_plugin_name_when_failing_to_add_extension() {
-    ComponentContainer container = new ComponentContainer();
-    PluginMetadata plugin = mock(PluginMetadata.class);
-
-    container.startComponents();
-
-    thrown.expect(IllegalStateException.class);
-    thrown.expectMessage("Unable to register extension org.sonar.api.platform.ComponentContainerTest$UnstartableComponent");
-
-    container.addExtension(plugin, UnstartableComponent.class);
-
-  }
-
-  @Test
-  public void test_start_failure() {
-    ComponentContainer container = new ComponentContainer();
-    StartableComponent startable = new StartableComponent();
-    container.add(startable, UnstartableComponent.class);
-
-    try {
-      container.execute();
-      fail();
-    } catch (Exception e) {
-      assertThat(startable.started).isTrue();
-
-      // container stops the components that have already been started
-      assertThat(startable.stopped).isTrue();
-    }
-  }
-
-  @Test
-  public void test_stop_failure() {
-    ComponentContainer container = new ComponentContainer();
-    StartableComponent startable = new StartableComponent();
-    container.add(startable, UnstoppableComponent.class);
-
-    try {
-      container.execute();
-      fail();
-    } catch (Exception e) {
-      assertThat(startable.started).isTrue();
-
-      // container should stop the components that have already been started
-      // ... but that's not the case
-    }
-  }
-
-  @Test
-  public void stop_exception_should_not_hide_start_exception() {
-    ComponentContainer container = new ComponentContainer();
-    container.add(UnstartableComponent.class, UnstoppableComponent.class);
-
-    thrown.expect(IllegalStateException.class);
-    thrown.expectMessage("Fail to start");
-    container.execute();
-  }
-
-  @Test
-  public void should_execute_components() {
-    ComponentContainer container = new ComponentContainer();
-    StartableComponent component = new StartableComponent();
-    container.add(component);
-
-    container.execute();
-
-    assertThat(component.started).isTrue();
-    assertThat(component.stopped).isTrue();
-  }
-
-  /**
-   * Method close() must be called even if the methods start() or stop()
-   * are not defined.
-   */
-  @Test
-  public void should_close_components_without_lifecycle() {
-    ComponentContainer container = new ComponentContainer();
-    CloseableComponent component = new CloseableComponent();
-    container.add(component);
-
-    container.execute();
-
-    assertThat(component.isClosed).isTrue();
-  }
-
-  /**
-   * Method close() must be executed after stop()
-   */
-  @Test
-  public void should_close_components_with_lifecycle() {
-    ComponentContainer container = new ComponentContainer();
-    StartableCloseableComponent component = new StartableCloseableComponent();
-    container.add(component);
-
-    container.execute();
-
-    assertThat(component.isStopped).isTrue();
-    assertThat(component.isClosed).isTrue();
-    assertThat(component.isClosedAfterStop).isTrue();
-  }
-
-  public static class StartableComponent {
-    public boolean started = false, stopped = false;
-
-    public void start() {
-      started = true;
-    }
-
-    public void stop() {
-      stopped = true;
-    }
-  }
-
-  public static class UnstartableComponent {
-    public void start() {
-      throw new IllegalStateException("Fail to start");
-    }
-
-    public void stop() {
-
-    }
-  }
-
-  public static class UnstoppableComponent {
-    public void start() {
-    }
-
-    public void stop() {
-      throw new IllegalStateException("Fail to stop");
-    }
-  }
-
-  @Property(key = "foo", defaultValue = "bar", name = "Foo")
-  public static class ComponentWithProperty {
-
-  }
-
-  public static class SimpleComponent {
-
-  }
-
-  public static class SimpleComponentProvider extends ProviderAdapter {
-    public SimpleComponent provide() {
-      return new SimpleComponent();
-    }
-  }
-
-  public static class CloseableComponent implements AutoCloseable {
-    public boolean isClosed = false;
-
-    @Override
-    public void close() throws Exception {
-      isClosed = true;
-    }
-  }
-
-  public static class StartableCloseableComponent implements AutoCloseable {
-    public boolean isClosed = false, isStopped = false, isClosedAfterStop = false;
-
-    public void stop() {
-      isStopped = true;
-    }
-
-    @Override
-    public void close() throws Exception {
-      isClosed = true;
-      isClosedAfterStop = isStopped;
-    }
-  }
-}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentKeysTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentKeysTest.java
deleted file mode 100644 (file)
index f2b36a6..0000000
+++ /dev/null
@@ -1,77 +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 org.junit.Test;
-import org.sonar.api.utils.log.Logger;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.*;
-
-public class ComponentKeysTest {
-
-  ComponentKeys keys = new ComponentKeys();
-
-  @Test
-  public void generate_key_of_class() {
-    assertThat(keys.of(FakeComponent.class)).isEqualTo(FakeComponent.class);
-  }
-
-  @Test
-  public void generate_key_of_object() {
-    assertThat(keys.of(new FakeComponent())).isEqualTo("org.sonar.api.platform.ComponentKeysTest.FakeComponent-fake");
-  }
-
-  @Test
-  public void should_log_warning_if_toString_is_not_overridden() {
-    Logger log = mock(Logger.class);
-    keys.of(new Object(), log);
-    verifyZeroInteractions(log);
-
-    // only on non-first runs, to avoid false-positives on singletons
-    keys.of(new Object(), log);
-    verify(log).warn(startsWith("Bad component key"));
-  }
-
-  @Test
-  public void should_generate_unique_key_when_toString_is_not_overridden() {
-    Object key = keys.of(new WrongToStringImpl());
-    assertThat(key).isNotEqualTo(WrongToStringImpl.KEY);
-
-    Object key2 = keys.of(new WrongToStringImpl());
-    assertThat(key2).isNotEqualTo(key);
-  }
-
-  static class FakeComponent {
-    @Override
-    public String toString() {
-      return "fake";
-    }
-  }
-
-  static class WrongToStringImpl {
-    static final String KEY = "my.Component@123a";
-
-    @Override
-    public String toString() {
-      return KEY;
-    }
-  }
-}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/platform/PicoUtilsTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/platform/PicoUtilsTest.java
deleted file mode 100644 (file)
index d5b7434..0000000
+++ /dev/null
@@ -1,106 +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 org.junit.Test;
-import org.picocontainer.Characteristics;
-import org.picocontainer.MutablePicoContainer;
-import org.picocontainer.PicoLifecycleException;
-
-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));
-
-    assertThat(th).isInstanceOf(IllegalStateException.class);
-    assertThat(th.getMessage()).isEqualTo("A good reason to fail");
-  }
-
-  @Test
-  public void shouldSanitizePicoLifecycleException_no_wrapper_message() {
-    Throwable th = PicoUtils.sanitize(new PicoLifecycleException(null, null, new IllegalStateException("msg")));
-
-    assertThat(th).isInstanceOf(IllegalStateException.class);
-    assertThat(th.getMessage()).isEqualTo("msg");
-  }
-
-  @Test
-  public void shouldNotSanitizeOtherExceptions() {
-    Throwable th = PicoUtils.sanitize(new IllegalArgumentException("foo"));
-
-    assertThat(th).isInstanceOf(IllegalArgumentException.class);
-    assertThat(th.getMessage()).isEqualTo("foo");
-  }
-
-  @Test
-  public void shouldPropagateInitialUncheckedException() {
-    try {
-      PicoUtils.propagate(newPicoLifecycleException(false));
-      fail();
-    } catch (RuntimeException e) {
-      assertThat(e).isInstanceOf(IllegalStateException.class);
-    }
-  }
-
-  @Test
-  public void shouldThrowUncheckedExceptionWhenPropagatingCheckedException() {
-    try {
-      PicoUtils.propagate(newPicoLifecycleException(true));
-      fail();
-    } catch (RuntimeException e) {
-      assertThat(e.getCause()).isInstanceOf(IOException.class);
-      assertThat(e.getCause().getMessage()).isEqualTo("Checked");
-    }
-  }
-
-  private PicoLifecycleException newPicoLifecycleException(boolean initialCheckedException) {
-    MutablePicoContainer container = ComponentContainer.createPicoContainer().as(Characteristics.CACHE);
-    if (initialCheckedException) {
-      container.addComponent(CheckedFailureComponent.class);
-    } else {
-      container.addComponent(UncheckedFailureComponent.class);
-    }
-    try {
-      container.start();
-      return null;
-
-    } catch (PicoLifecycleException e) {
-      return e;
-    }
-  }
-
-  public static class UncheckedFailureComponent {
-    public void start() {
-      throw new IllegalStateException("A good reason to fail");
-    }
-  }
-
-  public static class CheckedFailureComponent {
-    public void start() throws IOException {
-      throw new IOException("Checked");
-    }
-  }
-}