From 0956511c8c9d6aa6639d4378f47d73877cdc18de Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Fri, 29 May 2015 10:29:04 +0200 Subject: [PATCH] SONAR-6370 isolate plugin classloader from core classes --- plugins/sonar-xoo-plugin/pom.xml | 20 ++- .../xoo/coverage/AbstractCoverageSensor.java | 2 +- .../org/sonar/xoo/lang/MeasureSensor.java | 2 +- .../xoo/lang/SymbolReferencesSensor.java | 2 +- .../xoo/lang/SyntaxHighlightingSensor.java | 2 +- .../org/sonar/xoo/scm/XooBlameCommand.java | 2 +- .../sonar/xoo/test/CoveragePerTestSensor.java | 2 +- .../sonar/xoo/test/TestExecutionSensor.java | 2 +- pom.xml | 2 +- .../sonar/server/charts/ChartsServlet.java | 27 ++- .../platformlevel/PlatformLevel2.java | 6 +- .../plugins/ServerExtensionInstaller.java | 6 +- ...oder.java => ServerPluginJarExploder.java} | 6 +- .../plugins/ServerPluginRepository.java | 4 +- ....java => ServerPluginJarExploderTest.java} | 4 +- .../plugins/ServerPluginRepositoryTest.java | 13 +- sonar-batch/pom.xml | 4 + ...loder.java => BatchPluginJarExploder.java} | 6 +- .../batch/bootstrap/BatchPluginLoader.java | 41 ----- .../batch/bootstrap/GlobalContainer.java | 7 +- ...t.java => BatchPluginJarExploderTest.java} | 8 +- sonar-core/pom.xml | 3 +- ...aderDef.java => PluginClassloaderDef.java} | 71 +++++--- .../platform/PluginClassloaderFactory.java | 163 ++++++++++++++++++ ...inExploder.java => PluginJarExploder.java} | 4 +- .../org/sonar/core/platform/PluginLoader.java | 137 ++++++--------- .../PluginClassloaderFactoryTest.java | 132 ++++++++++++++ ...erTest.java => PluginJarExploderTest.java} | 6 +- .../sonar/core/platform/PluginLoaderTest.java | 116 ++++++++----- sonar-core/src/test/projects/.gitignore | 7 + sonar-core/src/test/projects/README.txt | 3 + .../src/test/projects/base-plugin/pom.xml | 36 ++++ .../org/sonar/plugins/base/BasePlugin.java | 13 ++ .../org/sonar/plugins/base/api/BaseApi.java | 6 + .../target/base-plugin-0.1-SNAPSHOT.jar | Bin 0 -> 3421 bytes .../test/projects/dependent-plugin/pom.xml | 43 +++++ .../plugins/dependent/DependentPlugin.java | 18 ++ .../target/dependent-plugin-0.1-SNAPSHOT.jar | Bin 0 -> 3077 bytes sonar-core/src/test/projects/pom.xml | 14 ++ sonar-plugin-api-deps/pom.xml | 45 ++++- sonar-plugin-api/pom.xml | 97 ++++++----- 41 files changed, 772 insertions(+), 310 deletions(-) rename server/sonar-server/src/main/java/org/sonar/server/plugins/{ServerPluginExploder.java => ServerPluginJarExploder.java} (93%) rename server/sonar-server/src/test/java/org/sonar/server/plugins/{ServerPluginExploderTest.java => ServerPluginJarExploderTest.java} (95%) rename sonar-batch/src/main/java/org/sonar/batch/bootstrap/{BatchPluginExploder.java => BatchPluginJarExploder.java} (93%) delete mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginLoader.java rename sonar-batch/src/test/java/org/sonar/batch/bootstrap/{BatchPluginExploderTest.java => BatchPluginJarExploderTest.java} (91%) rename sonar-core/src/main/java/org/sonar/core/platform/{ClassloaderDef.java => PluginClassloaderDef.java} (68%) create mode 100644 sonar-core/src/main/java/org/sonar/core/platform/PluginClassloaderFactory.java rename sonar-core/src/main/java/org/sonar/core/platform/{PluginExploder.java => PluginJarExploder.java} (93%) create mode 100644 sonar-core/src/test/java/org/sonar/core/platform/PluginClassloaderFactoryTest.java rename sonar-core/src/test/java/org/sonar/core/platform/{PluginExploderTest.java => PluginJarExploderTest.java} (94%) create mode 100644 sonar-core/src/test/projects/.gitignore create mode 100644 sonar-core/src/test/projects/README.txt create mode 100644 sonar-core/src/test/projects/base-plugin/pom.xml create mode 100644 sonar-core/src/test/projects/base-plugin/src/org/sonar/plugins/base/BasePlugin.java create mode 100644 sonar-core/src/test/projects/base-plugin/src/org/sonar/plugins/base/api/BaseApi.java create mode 100644 sonar-core/src/test/projects/base-plugin/target/base-plugin-0.1-SNAPSHOT.jar create mode 100644 sonar-core/src/test/projects/dependent-plugin/pom.xml create mode 100644 sonar-core/src/test/projects/dependent-plugin/src/org/sonar/plugins/dependent/DependentPlugin.java create mode 100644 sonar-core/src/test/projects/dependent-plugin/target/dependent-plugin-0.1-SNAPSHOT.jar create mode 100644 sonar-core/src/test/projects/pom.xml diff --git a/plugins/sonar-xoo-plugin/pom.xml b/plugins/sonar-xoo-plugin/pom.xml index f447c845372..1e43aaa48c2 100644 --- a/plugins/sonar-xoo-plugin/pom.xml +++ b/plugins/sonar-xoo-plugin/pom.xml @@ -17,24 +17,28 @@ com.google.guava guava - - - com.google.code.findbugs - jsr305 - 3.0.0 - provided + 18.0 commons-io commons-io + 2.4 - commons-lang - commons-lang + org.apache.commons + commons-lang3 + 3.3.2 + + + com.google.code.findbugs + jsr305 + 3.0.0 + provided org.codehaus.sonar sonar-plugin-api + ${project.version} provided diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/AbstractCoverageSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/AbstractCoverageSensor.java index 2930df6c32a..b312d287956 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/AbstractCoverageSensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/coverage/AbstractCoverageSensor.java @@ -21,7 +21,7 @@ package org.sonar.xoo.coverage; import com.google.common.base.Splitter; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.Sensor; import org.sonar.api.batch.sensor.SensorContext; diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/MeasureSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/MeasureSensor.java index 7469d9ff2cf..ca06e245d68 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/MeasureSensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/MeasureSensor.java @@ -20,7 +20,7 @@ package org.sonar.xoo.lang; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.measure.MetricFinder; import org.sonar.api.batch.sensor.Sensor; diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SymbolReferencesSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SymbolReferencesSensor.java index e90feb4310f..e69aa54c210 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SymbolReferencesSensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SymbolReferencesSensor.java @@ -21,7 +21,7 @@ package org.sonar.xoo.lang; import com.google.common.base.Splitter; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.Sensor; import org.sonar.api.batch.sensor.SensorContext; diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SyntaxHighlightingSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SyntaxHighlightingSensor.java index 8daffb64026..bd273dec4ff 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SyntaxHighlightingSensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SyntaxHighlightingSensor.java @@ -21,7 +21,7 @@ package org.sonar.xoo.lang; import com.google.common.base.Splitter; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.Sensor; import org.sonar.api.batch.sensor.SensorContext; diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooBlameCommand.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooBlameCommand.java index dcf5258413b..7dfd7c10de3 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooBlameCommand.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/scm/XooBlameCommand.java @@ -21,7 +21,7 @@ package org.sonar.xoo.scm; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.scm.BlameCommand; import org.sonar.api.batch.scm.BlameLine; diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/test/CoveragePerTestSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/test/CoveragePerTestSensor.java index 369b9d3fd6a..46baf5c15c7 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/test/CoveragePerTestSensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/test/CoveragePerTestSensor.java @@ -21,7 +21,7 @@ package org.sonar.xoo.test; import com.google.common.base.Splitter; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.sonar.api.batch.DependsUpon; import org.sonar.api.batch.fs.FilePredicates; import org.sonar.api.batch.fs.FileSystem; diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/test/TestExecutionSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/test/TestExecutionSensor.java index c3446daac3a..30bd04e8a0d 100644 --- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/test/TestExecutionSensor.java +++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/test/TestExecutionSensor.java @@ -21,7 +21,7 @@ package org.sonar.xoo.test; import com.google.common.base.Splitter; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.sonar.api.batch.DependedUpon; import org.sonar.api.batch.fs.FilePredicates; import org.sonar.api.batch.fs.FileSystem; diff --git a/pom.xml b/pom.xml index e67ab2307eb..fb2539ee041 100644 --- a/pom.xml +++ b/pom.xml @@ -721,7 +721,7 @@ com.google.guava guava - 10.0.1 + 17.0 diff --git a/server/sonar-server/src/main/java/org/sonar/server/charts/ChartsServlet.java b/server/sonar-server/src/main/java/org/sonar/server/charts/ChartsServlet.java index 5a6cc9ae262..76fc2cd4af4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/charts/ChartsServlet.java +++ b/server/sonar-server/src/main/java/org/sonar/server/charts/ChartsServlet.java @@ -20,7 +20,15 @@ package org.sonar.server.charts; import com.google.common.collect.Maps; -import com.google.common.io.Closeables; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Enumeration; +import java.util.Map; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.jfree.chart.encoders.KeypointPNGEncoderAdapter; import org.sonar.api.charts.Chart; import org.sonar.api.charts.ChartParameters; @@ -34,17 +42,6 @@ import org.sonar.server.charts.deprecated.PieChart; import org.sonar.server.charts.deprecated.SparkLinesChart; import org.sonar.server.platform.Platform; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Enumeration; -import java.util.Map; - public class ChartsServlet extends HttpServlet { private static final Logger LOG = Loggers.get(ChartsServlet.class); @@ -145,15 +142,11 @@ public class ChartsServlet extends HttpServlet { } if (chart != null) { - OutputStream out = null; - try { - out = response.getOutputStream(); + try (OutputStream out = response.getOutputStream()) { response.setContentType("image/png"); chart.exportChartAsPNG(out); } catch (Exception e) { LOG.error("Generating chart " + chart.getClass().getName(), e); - } finally { - Closeables.closeQuietly(out); } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java index 062e796de67..38810de3849 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java @@ -22,6 +22,7 @@ package org.sonar.server.platform.platformlevel; import org.sonar.api.utils.Durations; import org.sonar.core.i18n.DefaultI18n; import org.sonar.core.i18n.RuleI18nManager; +import org.sonar.core.platform.PluginClassloaderFactory; import org.sonar.core.platform.PluginLoader; import org.sonar.server.db.migrations.DatabaseMigrator; import org.sonar.server.db.migrations.PlatformDatabaseMigration; @@ -30,7 +31,7 @@ import org.sonar.server.platform.DefaultServerUpgradeStatus; import org.sonar.server.platform.RailsAppsDeployer; import org.sonar.server.plugins.InstalledPluginReferentialFactory; import org.sonar.server.plugins.ServerExtensionInstaller; -import org.sonar.server.plugins.ServerPluginExploder; +import org.sonar.server.plugins.ServerPluginJarExploder; import org.sonar.server.plugins.ServerPluginRepository; import org.sonar.server.ruby.PlatformRubyBridge; import org.sonar.server.ui.JRubyI18n; @@ -51,8 +52,9 @@ public class PlatformLevel2 extends PlatformLevel { // plugins ServerPluginRepository.class, - ServerPluginExploder.class, + ServerPluginJarExploder.class, PluginLoader.class, + PluginClassloaderFactory.class, InstalledPluginReferentialFactory.class, ServerExtensionInstaller.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java index 54a733341c2..1bd64568383 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java @@ -59,7 +59,8 @@ public class ServerExtensionInstaller { container.declareExtension(pluginInfo, extension); } } - } catch (Exception e) { + } catch (Throwable e) { + // catch Throwable because we want to catch Error too (IncompatibleClassChangeError, ...) throw new IllegalStateException(String.format("Fail to load plugin %s [%s]", pluginInfo.getName(), pluginInfo.getKey()), e); } } @@ -71,7 +72,8 @@ public class ServerExtensionInstaller { ExtensionProvider provider = (ExtensionProvider) container.getComponentByKey(extension); installProvider(container, pluginInfo, provider); } - } catch (Exception e) { + } catch (Throwable e) { + // catch Throwable because we want to catch Error too (IncompatibleClassChangeError, ...) throw new IllegalStateException(String.format("Fail to load plugin %s [%s]", pluginInfo.getName(), pluginInfo.getKey()), e); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginExploder.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java similarity index 93% rename from server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginExploder.java rename to server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java index e312120c2b0..aba34718a38 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginExploder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java @@ -23,7 +23,7 @@ import org.apache.commons.io.FileUtils; import org.sonar.api.server.ServerSide; import org.sonar.api.utils.ZipUtils; import org.sonar.core.platform.ExplodedPlugin; -import org.sonar.core.platform.PluginExploder; +import org.sonar.core.platform.PluginJarExploder; import org.sonar.core.platform.PluginInfo; import org.sonar.server.platform.DefaultServerFileSystem; @@ -33,11 +33,11 @@ import static org.apache.commons.io.FileUtils.cleanDirectory; import static org.apache.commons.io.FileUtils.forceMkdir; @ServerSide -public class ServerPluginExploder extends PluginExploder { +public class ServerPluginJarExploder extends PluginJarExploder { private final DefaultServerFileSystem fs; - public ServerPluginExploder(DefaultServerFileSystem fs) { + public ServerPluginJarExploder(DefaultServerFileSystem fs) { this.fs = fs; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java index 6f5a97a8c6c..83b4f060fc2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java @@ -61,13 +61,13 @@ import static org.apache.commons.io.FileUtils.moveFileToDirectory; import static org.sonar.core.platform.PluginInfo.jarToPluginInfo; /** - * Manages installation and loading of plugins: + * Entry point to install and load plugins on server startup. It manages *
    *
  • installation of bundled plugins on first server startup
  • *
  • installation of new plugins (effective after server startup)
  • *
  • un-installation of plugins (effective after server startup)
  • *
  • cancel pending installations/un-installations
  • - *
  • load plugin bytecode
  • + *
  • instantiation of plugin entry-points
  • *
*/ public class ServerPluginRepository implements PluginRepository, Startable { diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginExploderTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java similarity index 95% rename from server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginExploderTest.java rename to server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java index d67cbdeeed0..d782662942b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginExploderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java @@ -32,13 +32,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class ServerPluginExploderTest { +public class ServerPluginJarExploderTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class); - ServerPluginExploder underTest = new ServerPluginExploder(fs); + ServerPluginJarExploder underTest = new ServerPluginJarExploder(fs); @Test public void copy_all_classloader_files_to_dedicated_directory() throws Exception { diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java index 41e3523344c..9381a722e9b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java @@ -21,6 +21,8 @@ package org.sonar.server.plugins; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import java.io.File; +import java.io.IOException; import java.util.Collections; import java.util.Map; import org.apache.commons.io.FileUtils; @@ -39,9 +41,6 @@ import org.sonar.core.platform.PluginLoader; import org.sonar.server.platform.DefaultServerFileSystem; import org.sonar.updatecenter.common.Version; -import java.io.File; -import java.io.IOException; - import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; @@ -58,7 +57,7 @@ public class ServerPluginRepositoryTest { Server server = mock(Server.class); ServerUpgradeStatus upgradeStatus = mock(ServerUpgradeStatus.class); DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class, Mockito.RETURNS_DEEP_STUBS); - PluginLoader pluginLoader = new PluginLoader(new ServerPluginExploder(fs)); + PluginLoader pluginLoader = mock(PluginLoader.class); ServerPluginRepository underTest = new ServerPluginRepository(server, upgradeStatus, fs, pluginLoader); @Before @@ -91,9 +90,6 @@ public class ServerPluginRepositoryTest { // 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 @@ -115,8 +111,6 @@ public class ServerPluginRepositoryTest { // 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"); } /** @@ -160,7 +154,6 @@ public class ServerPluginRepositoryTest { 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 diff --git a/sonar-batch/pom.xml b/sonar-batch/pom.xml index 668d8fd90fe..ee8f1407c83 100644 --- a/sonar-batch/pom.xml +++ b/sonar-batch/pom.xml @@ -28,6 +28,10 @@ org.codehaus.sonar sonar-persistit
+ + com.google.guava + guava + org.codehaus.sonar diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginExploder.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarExploder.java similarity index 93% rename from sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginExploder.java rename to sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarExploder.java index c08b9578786..e68dcb51a13 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginExploder.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarExploder.java @@ -23,7 +23,7 @@ import org.apache.commons.io.FileUtils; import org.sonar.api.batch.BatchSide; import org.sonar.api.utils.ZipUtils; import org.sonar.core.platform.ExplodedPlugin; -import org.sonar.core.platform.PluginExploder; +import org.sonar.core.platform.PluginJarExploder; import org.sonar.core.platform.PluginInfo; import org.sonar.home.cache.FileCache; @@ -32,11 +32,11 @@ import java.io.FileOutputStream; import java.io.IOException; @BatchSide -public class BatchPluginExploder extends PluginExploder { +public class BatchPluginJarExploder extends PluginJarExploder { private final FileCache fileCache; - public BatchPluginExploder(FileCache fileCache) { + public BatchPluginJarExploder(FileCache fileCache) { this.fileCache = fileCache; } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginLoader.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginLoader.java deleted file mode 100644 index 8ffbc98b410..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginLoader.java +++ /dev/null @@ -1,41 +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.batch.BatchSide; -import org.sonar.core.platform.PluginExploder; -import org.sonar.core.platform.PluginLoader; - -/** - * The {@link PluginLoader} on batch side requires to use thread context - * classloader as base classloader in order to support plugins like Groovy - * (at least its version 1.1). - */ -@BatchSide -public class BatchPluginLoader extends PluginLoader { - public BatchPluginLoader(PluginExploder exploder) { - super(exploder); - } - - @Override - protected ClassLoader baseClassloader() { - return Thread.currentThread().getContextClassLoader(); - } -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java index facf6c58a3a..2e1542ada81 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java @@ -54,7 +54,9 @@ 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.PluginClassloaderFactory; 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; @@ -98,8 +100,9 @@ public class GlobalContainer extends ComponentContainer { add( // plugins BatchPluginRepository.class, - BatchPluginLoader.class, - BatchPluginExploder.class, + PluginLoader.class, + PluginClassloaderFactory.class, + BatchPluginJarExploder.class, BatchPluginPredicate.class, ExtensionInstaller.class, diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginExploderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarExploderTest.java similarity index 91% rename from sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginExploderTest.java rename to sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarExploderTest.java index f2c2f6354a3..bb678103fe2 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginExploderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarExploderTest.java @@ -34,19 +34,19 @@ import java.io.IOException; import static org.assertj.core.api.Assertions.assertThat; -public class BatchPluginExploderTest { +public class BatchPluginJarExploderTest { @ClassRule public static TemporaryFolder temp = new TemporaryFolder(); File userHome; - BatchPluginExploder underTest; + BatchPluginJarExploder underTest; @Before public void setUp() throws IOException { userHome = temp.newFolder(); FileCache fileCache = new FileCacheBuilder().setUserHome(userHome).build(); - underTest = new BatchPluginExploder(fileCache); + underTest = new BatchPluginJarExploder(fileCache); } @Test @@ -72,7 +72,7 @@ public class BatchPluginExploderTest { } File getFileFromCache(String filename) throws IOException { - File src = FileUtils.toFile(BatchPluginExploderTest.class.getResource("/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/" + filename)); + File src = FileUtils.toFile(BatchPluginJarExploderTest.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-core/pom.xml b/sonar-core/pom.xml index 39383aa7962..069d94c38ff 100644 --- a/sonar-core/pom.xml +++ b/sonar-core/pom.xml @@ -118,6 +118,7 @@ org.codehaus.sonar sonar-plugin-api-deps ${project.version} + true runtime @@ -199,7 +200,7 @@ copy-deprecated-api-deps - process-resources + generate-resources copy diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ClassloaderDef.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginClassloaderDef.java similarity index 68% rename from sonar-core/src/main/java/org/sonar/core/platform/ClassloaderDef.java rename to sonar-core/src/main/java/org/sonar/core/platform/PluginClassloaderDef.java index 3ebe5ccb0fb..9939c1a1c42 100644 --- a/sonar-core/src/main/java/org/sonar/core/platform/ClassloaderDef.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginClassloaderDef.java @@ -21,30 +21,33 @@ package org.sonar.core.platform; import com.google.common.base.Preconditions; import com.google.common.base.Strings; -import java.util.Collection; -import javax.annotation.Nullable; -import org.sonar.classloader.Mask; - import java.io.File; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; +import org.sonar.classloader.Mask; /** - * Information about the classloader to be created for a set of plugins. + * Temporary information about the classloader to be created for a plugin (or a group of plugins). */ -class ClassloaderDef { +class PluginClassloaderDef { private final String basePluginKey; private final Map mainClassesByPluginKey = new HashMap<>(); private final List files = new ArrayList<>(); private final Mask mask = new Mask(); private boolean selfFirstStrategy = false; - private ClassLoader classloader = null; - ClassloaderDef(String basePluginKey) { - Preconditions.checkNotNull(basePluginKey); + /** + * Compatibility with API classloader as defined before version 5.2 + */ + private boolean compatibilityMode = false; + + PluginClassloaderDef(String basePluginKey) { + Preconditions.checkArgument(!Strings.isNullOrEmpty(basePluginKey)); this.basePluginKey = basePluginKey; } @@ -52,15 +55,15 @@ class ClassloaderDef { return basePluginKey; } - Map getMainClassesByPluginKey() { - return mainClassesByPluginKey; - } - List getFiles() { return files; } - Mask getMask() { + void addFiles(Collection f) { + this.files.addAll(f); + } + + Mask getExportMask() { return mask; } @@ -72,26 +75,38 @@ class ClassloaderDef { this.selfFirstStrategy = selfFirstStrategy; } - /** - * Returns the newly created classloader. Throws an exception - * if null, for example because called before {@link #setBuiltClassloader(ClassLoader)} - */ - ClassLoader getBuiltClassloader() { - Preconditions.checkState(classloader != null); - return classloader; + Map getMainClassesByPluginKey() { + return mainClassesByPluginKey; + } + + void addMainClass(String pluginKey, @Nullable String mainClass) { + if (!Strings.isNullOrEmpty(mainClass)) { + mainClassesByPluginKey.put(pluginKey, mainClass); + } } - void setBuiltClassloader(ClassLoader c) { - this.classloader = c; + boolean isCompatibilityMode() { + return compatibilityMode; } - void addFiles(Collection c) { - this.files.addAll(c); + void setCompatibilityMode(boolean b) { + this.compatibilityMode = b; } - void addMainClass(String pluginKey, @Nullable String mainClass) { - if (!Strings.isNullOrEmpty(mainClass)) { - mainClassesByPluginKey.put(pluginKey, mainClass); + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; } + PluginClassloaderDef that = (PluginClassloaderDef) o; + return basePluginKey.equals(that.basePluginKey); + } + + @Override + public int hashCode() { + return basePluginKey.hashCode(); } } diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginClassloaderFactory.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginClassloaderFactory.java new file mode 100644 index 00000000000..a79ba09910d --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginClassloaderFactory.java @@ -0,0 +1,163 @@ +/* + * 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.net.MalformedURLException; +import java.net.URL; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.sonar.api.batch.BatchSide; +import org.sonar.api.server.ServerSide; +import org.sonar.api.utils.TempFolder; +import org.sonar.classloader.ClassloaderBuilder; +import org.sonar.classloader.Mask; + +import static org.sonar.classloader.ClassloaderBuilder.LoadingOrder.PARENT_FIRST; +import static org.sonar.classloader.ClassloaderBuilder.LoadingOrder.SELF_FIRST; + +/** + * Builds the graph of classloaders to be used to instantiate plugins. It deals with: + *
    + *
  • isolation of plugins against core classes (except api)
  • + *
  • backward-compatibility with plugins built for versions of SQ lower than 5.2. At that time + * API declared transitive dependencies that were automatically available to plugins
  • + *
  • sharing of some packages between plugins
  • + *
  • loading of the libraries embedded in plugin JAR files (directory META-INF/libs)
  • + *
+ */ +@BatchSide +@ServerSide +public class PluginClassloaderFactory { + + // underscores are used to not conflict with plugin keys (if someday a plugin key is "api") + private static final String API_CLASSLOADER_KEY = "_api_"; + + private final TempFolder temp; + private URL compatibilityModeJar; + + public PluginClassloaderFactory(TempFolder temp) { + this.temp = temp; + } + + /** + * Creates as many classloaders as requested by the input parameter. + */ + public Map create(Collection defs) { + ClassloaderBuilder builder = new ClassloaderBuilder(); + builder.newClassloader(API_CLASSLOADER_KEY, baseClassloader()); + builder.setMask(API_CLASSLOADER_KEY, apiMask()); + + for (PluginClassloaderDef def : defs) { + builder.newClassloader(def.getBasePluginKey()); + builder.setParent(def.getBasePluginKey(), API_CLASSLOADER_KEY, new Mask()); + builder.setLoadingOrder(def.getBasePluginKey(), def.isSelfFirstStrategy() ? SELF_FIRST : PARENT_FIRST); + for (File jar : def.getFiles()) { + builder.addURL(def.getBasePluginKey(), fileToUrl(jar)); + } + if (def.isCompatibilityMode()) { + builder.addURL(def.getBasePluginKey(), extractCompatibilityModeJar()); + } + exportResources(def, builder, defs); + } + + return build(defs, builder); + } + + /** + * A plugin can export some resources to other plugins + */ + private void exportResources(PluginClassloaderDef def, ClassloaderBuilder builder, Collection allPlugins) { + // export the resources to all other plugins + builder.setExportMask(def.getBasePluginKey(), def.getExportMask()); + for (PluginClassloaderDef other : allPlugins) { + if (!other.getBasePluginKey().equals(def.getBasePluginKey())) { + builder.addSibling(def.getBasePluginKey(), other.getBasePluginKey(), new Mask()); + } + } + } + + /** + * Builds classloaders and verifies that all of them are correctly defined + */ + private Map build(Collection defs, ClassloaderBuilder builder) { + Map result = new HashMap<>(); + Map classloadersByBasePluginKey = builder.build(); + for (PluginClassloaderDef def : defs) { + ClassLoader classloader = classloadersByBasePluginKey.get(def.getBasePluginKey()); + if (classloader == null) { + throw new IllegalStateException(String.format("Fail to create classloader for plugin [%s]", def.getBasePluginKey())); + } + result.put(def, classloader); + } + return result; + } + + ClassLoader baseClassloader() { + return getClass().getClassLoader(); + } + + private URL extractCompatibilityModeJar() { + if (compatibilityModeJar == null) { + File jar = temp.newFile("sonar-plugin-api-deps", "jar"); + try { + FileUtils.copyURLToFile(getClass().getResource("/sonar-plugin-api-deps.jar"), jar); + compatibilityModeJar = jar.toURI().toURL(); + } catch (Exception e) { + throw new IllegalStateException("Can not extract sonar-plugin-api-deps.jar to " + jar.getAbsolutePath(), e); + } + } + return compatibilityModeJar; + } + + private static URL fileToUrl(File file) { + try { + return file.toURI().toURL(); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * The resources (packages) that API exposes to plugins. Other core classes (SonarQube, MyBatis, ...) + * can't be accessed. + *

To sum-up, these are the classes packaged in sonar-plugin-api.jar or available as + * a transitive dependency of sonar-plugin-api

+ */ + private static Mask apiMask() { + return new Mask() + // inclusions + .addInclusion("org/sonar/api/") + .addInclusion("org/sonar/channel/") + .addInclusion("org/sonar/check/") + .addInclusion("org/sonar/colorizer/") + .addInclusion("org/sonar/duplications/") + .addInclusion("org/sonar/graph/") + .addInclusion("org/sonar/plugins/emailnotifications/api/") + .addInclusion("net/sourceforge/pmd/") + .addInclusion("org/apache/maven/") + .addInclusion("org/slf4j/") + + // exclusions + .addExclusion("org/sonar/api/internal/"); + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginExploder.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginJarExploder.java similarity index 93% rename from sonar-core/src/main/java/org/sonar/core/platform/PluginExploder.java rename to sonar-core/src/main/java/org/sonar/core/platform/PluginJarExploder.java index 50681f13e3d..fb96c72ae9e 100644 --- a/sonar-core/src/main/java/org/sonar/core/platform/PluginExploder.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginJarExploder.java @@ -28,7 +28,7 @@ import java.util.zip.ZipEntry; import static org.apache.commons.io.FileUtils.listFiles; -public abstract class PluginExploder { +public abstract class PluginJarExploder { protected static final String LIB_RELATIVE_PATH_IN_JAR = "META-INF/lib"; @@ -39,7 +39,7 @@ public abstract class PluginExploder { } protected ExplodedPlugin explodeFromUnzippedDir(String pluginKey, File jarFile, File unzippedDir) { - File libDir = new File(unzippedDir, PluginExploder.LIB_RELATIVE_PATH_IN_JAR); + File libDir = new File(unzippedDir, PluginJarExploder.LIB_RELATIVE_PATH_IN_JAR); Collection libs; if (libDir.isDirectory() && libDir.exists()) { libs = listFiles(libDir, null, false); 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 index f0cbc1defe3..1012c8ca438 100644 --- a/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java @@ -21,23 +21,16 @@ package org.sonar.core.platform; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; -import org.apache.commons.lang.SystemUtils; -import org.sonar.api.Plugin; -import org.sonar.api.utils.log.Loggers; -import org.sonar.classloader.ClassloaderBuilder; -import org.sonar.classloader.Mask; - import java.io.Closeable; -import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import org.apache.commons.lang.SystemUtils; +import org.sonar.api.Plugin; +import org.sonar.api.utils.log.Loggers; +import org.sonar.updatecenter.common.Version; import static java.util.Arrays.asList; -import static org.sonar.classloader.ClassloaderBuilder.LoadingOrder.PARENT_FIRST; -import static org.sonar.classloader.ClassloaderBuilder.LoadingOrder.SELF_FIRST; /** * Loads the plugin JAR files by creating the appropriate classloaders and by instantiating @@ -45,117 +38,98 @@ import static org.sonar.classloader.ClassloaderBuilder.LoadingOrder.SELF_FIRST; * environment (minimal sonarqube version, compatibility between plugins, ...): *
    *
  • server verifies compatibility of JARs before deploying them at startup (see ServerPluginRepository)
  • - *
  • batch loads only the plugins deployed on server
  • + *
  • batch loads only the plugins deployed on server (see BatchPluginRepository)
  • *
*

- * 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. + * Plugins have their own isolated classloader, inheriting only from API classes. + * Some plugins can extend a "base" plugin, sharing the same classloader. *

- * This class is stateless. It does not keep classloaders and {@link Plugin} in memory. + * This class is stateless. It does not keep pointers to classloaders and {@link Plugin}. */ public class PluginLoader { private static final String[] DEFAULT_SHARED_RESOURCES = {"org/sonar/plugins", "com/sonar/plugins", "com/sonarsource/plugins"}; + public static final Version COMPATIBILITY_MODE_MAX_VERSION = Version.create("5.2"); - // underscores are used to not conflict with plugin keys (if someday a plugin key is "api") - private static final String API_CLASSLOADER_KEY = "_api_"; + private final PluginJarExploder jarExploder; + private final PluginClassloaderFactory classloaderFactory; - private final PluginExploder exploder; - - public PluginLoader(PluginExploder exploder) { - this.exploder = exploder; + public PluginLoader(PluginJarExploder jarExploder, PluginClassloaderFactory classloaderFactory) { + this.jarExploder = jarExploder; + this.classloaderFactory = classloaderFactory; } public Map load(Map infoByKeys) { - Collection defs = defineClassloaders(infoByKeys); - buildClassloaders(defs); - return instantiatePluginInstances(defs); + Collection defs = defineClassloaders(infoByKeys); + Map classloaders = classloaderFactory.create(defs); + return instantiatePluginClasses(classloaders); } /** - * Step 1 - define the different classloaders to be created. Number of classloaders can be + * Defines the different classloaders to be created. Number of classloaders can be * different than number of plugins. */ @VisibleForTesting - Collection defineClassloaders(Map infoByKeys) { - Map classloadersByBasePlugin = new HashMap<>(); + Collection defineClassloaders(Map infoByKeys) { + Map classloadersByBasePlugin = new HashMap<>(); for (PluginInfo info : infoByKeys.values()) { String baseKey = basePluginKey(info, infoByKeys); - ClassloaderDef def = classloadersByBasePlugin.get(baseKey); + PluginClassloaderDef def = classloadersByBasePlugin.get(baseKey); if (def == null) { - def = new ClassloaderDef(baseKey); + def = new PluginClassloaderDef(baseKey); classloadersByBasePlugin.put(baseKey, def); } - ExplodedPlugin explodedPlugin = exploder.explode(info); + ExplodedPlugin explodedPlugin = jarExploder.explode(info); def.addFiles(asList(explodedPlugin.getMain())); def.addFiles(explodedPlugin.getLibs()); def.addMainClass(info.getKey(), info.getMainClass()); for (String defaultSharedResource : DEFAULT_SHARED_RESOURCES) { - def.getMask().addInclusion(String.format("%s/%s/api/", defaultSharedResource, info.getKey())); + def.getExportMask().addInclusion(String.format("%s/%s/api/", defaultSharedResource, info.getKey())); } + + // The plugins that extend other plugins can only add some files to classloader. + // They can't change metadata like ordering strategy or compatibility mode. if (Strings.isNullOrEmpty(info.getBasePlugin())) { - // The plugins that extend other plugins can only add some files to classloader. - // They can't change ordering strategy. def.setSelfFirstStrategy(info.isUseChildFirstClassLoader()); - } - } - return classloadersByBasePlugin.values(); - } - - /** - * Step 2 - create classloaders with appropriate constituents and metadata - */ - private void buildClassloaders(Collection defs) { - ClassloaderBuilder builder = new ClassloaderBuilder(); - builder.newClassloader(API_CLASSLOADER_KEY, baseClassloader()); - for (ClassloaderDef def : defs) { - builder - .newClassloader(def.getBasePluginKey()) - .setParent(def.getBasePluginKey(), API_CLASSLOADER_KEY, new Mask()) - // resources to be exported to other plugin classloaders (siblings) - .setExportMask(def.getBasePluginKey(), def.getMask()) - .setLoadingOrder(def.getBasePluginKey(), def.isSelfFirstStrategy() ? SELF_FIRST : PARENT_FIRST); - for (File file : def.getFiles()) { - builder.addURL(def.getBasePluginKey(), fileToUrl(file)); - } - for (ClassloaderDef sibling : defs) { - if (!sibling.getBasePluginKey().equals(def.getBasePluginKey())) { - builder.addSibling(def.getBasePluginKey(), sibling.getBasePluginKey(), new Mask()); + Version minSqVersion = info.getMinimalSqVersion(); + boolean compatibilityMode = (minSqVersion != null && minSqVersion.compareToIgnoreQualifier(COMPATIBILITY_MODE_MAX_VERSION) < 0); + def.setCompatibilityMode(compatibilityMode); + if (compatibilityMode) { + Loggers.get(getClass()).info("API compatibility mode is enabled on plugin {} [{}] " + + "(built with API lower than {})", + info.getName(), info.getKey(), COMPATIBILITY_MODE_MAX_VERSION); } } } - Map classloadersByBasePluginKey = builder.build(); - for (ClassloaderDef def : defs) { - ClassLoader builtClassloader = classloadersByBasePluginKey.get(def.getBasePluginKey()); - if (builtClassloader == null) { - throw new IllegalStateException(String.format("Fail to create classloader for plugin [%s]", def.getBasePluginKey())); - } - def.setBuiltClassloader(builtClassloader); - } + return classloadersByBasePlugin.values(); } /** - * Step 3 - instantiate plugin instances ({@link Plugin} + * Instantiates collection of ({@link Plugin} according to given metadata and classloaders * * @return the instances grouped by plugin key * @throws IllegalStateException if at least one plugin can't be correctly loaded */ - private Map instantiatePluginInstances(Collection defs) { + @VisibleForTesting + Map instantiatePluginClasses(Map classloaders) { // instantiate plugins Map instancesByPluginKey = new HashMap<>(); - for (ClassloaderDef def : defs) { + for (Map.Entry entry : classloaders.entrySet()) { + PluginClassloaderDef def = entry.getKey(); + ClassLoader classLoader = entry.getValue(); + // the same classloader can be used by multiple plugins - for (Map.Entry entry : def.getMainClassesByPluginKey().entrySet()) { - String pluginKey = entry.getKey(); - String mainClass = entry.getValue(); + for (Map.Entry mainClassEntry : def.getMainClassesByPluginKey().entrySet()) { + String pluginKey = mainClassEntry.getKey(); + String mainClass = mainClassEntry.getValue(); try { - instancesByPluginKey.put(pluginKey, (Plugin) def.getBuiltClassloader().loadClass(mainClass).newInstance()); + instancesByPluginKey.put(pluginKey, (Plugin) classLoader.loadClass(mainClass).newInstance()); } catch (UnsupportedClassVersionError e) { throw new IllegalStateException(String.format("The plugin [%s] does not support Java %s", pluginKey, SystemUtils.JAVA_VERSION_TRIMMED), e); - } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + } catch (Throwable e) { throw new IllegalStateException(String.format( "Fail to instantiate class [%s] of plugin [%s]", mainClass, pluginKey), e); } @@ -167,7 +141,7 @@ public class PluginLoader { public void unload(Collection plugins) { for (Plugin plugin : plugins) { ClassLoader classLoader = plugin.getClass().getClassLoader(); - if (classLoader instanceof Closeable && classLoader != baseClassloader()) { + if (classLoader instanceof Closeable && classLoader != classloaderFactory.baseClassloader()) { try { ((Closeable) classLoader).close(); } catch (Exception e) { @@ -177,13 +151,6 @@ public class PluginLoader { } } - /** - * This method can be overridden to change the base classloader. - */ - protected ClassLoader baseClassloader() { - return getClass().getClassLoader(); - } - /** * 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. @@ -198,12 +165,4 @@ public class PluginLoader { } return base; } - - private static URL fileToUrl(File file) { - try { - return file.toURI().toURL(); - } catch (MalformedURLException e) { - throw new IllegalStateException(e); - } - } } diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginClassloaderFactoryTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginClassloaderFactoryTest.java new file mode 100644 index 00000000000..9b5f6cf2dc5 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginClassloaderFactoryTest.java @@ -0,0 +1,132 @@ +/* + * 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.Map; +import org.apache.commons.lang.StringUtils; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.server.rule.RulesDefinition; +import org.sonar.api.utils.internal.JUnitTempFolder; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + +public class PluginClassloaderFactoryTest { + + static final String BASE_PLUGIN_CLASSNAME = "org.sonar.plugins.base.BasePlugin"; + static final String DEPENDENT_PLUGIN_CLASSNAME = "org.sonar.plugins.dependent.DependentPlugin"; + static final String BASE_PLUGIN_KEY = "base"; + static final String DEPENDENT_PLUGIN_KEY = "dependent"; + + @Rule + public JUnitTempFolder temp = new JUnitTempFolder(); + + PluginClassloaderFactory factory = new PluginClassloaderFactory(temp); + + @Test + public void create_isolated_classloader() throws Exception { + PluginClassloaderDef def = basePluginDef(); + Map map = factory.create(asList(def)); + + assertThat(map).containsOnlyKeys(def); + ClassLoader classLoader = map.get(def); + + // plugin can access to API classes, and of course to its own classes ! + assertThat(canLoadClass(classLoader, RulesDefinition.class.getCanonicalName())).isTrue(); + assertThat(canLoadClass(classLoader, BASE_PLUGIN_CLASSNAME)).isTrue(); + + // plugin can not access to core classes + assertThat(canLoadClass(classLoader, PluginClassloaderFactory.class.getCanonicalName())).isFalse(); + assertThat(canLoadClass(classLoader, Test.class.getCanonicalName())).isFalse(); + assertThat(canLoadClass(classLoader, StringUtils.class.getCanonicalName())).isFalse(); + } + + @Test + public void create_classloader_compatible_with_with_old_api_dependencies() throws Exception { + PluginClassloaderDef def = basePluginDef(); + def.setCompatibilityMode(true); + ClassLoader classLoader = factory.create(asList(def)).get(def); + + // Plugin can access to API and its transitive dependencies as defined in version 5.1. + // It can not access to core classes though, even if it was possible in previous versions. + assertThat(canLoadClass(classLoader, RulesDefinition.class.getCanonicalName())).isTrue(); + assertThat(canLoadClass(classLoader, StringUtils.class.getCanonicalName())).isTrue(); + assertThat(canLoadClass(classLoader, BASE_PLUGIN_CLASSNAME)).isTrue(); + assertThat(canLoadClass(classLoader, PluginClassloaderFactory.class.getCanonicalName())).isFalse(); + } + + @Test + public void classloader_exports_resources_to_other_classloaders() throws Exception { + PluginClassloaderDef baseDef = basePluginDef(); + PluginClassloaderDef dependentDef = dependentPluginDef(); + Map map = factory.create(asList(baseDef, dependentDef)); + ClassLoader baseClassloader = map.get(baseDef); + ClassLoader dependentClassloader = map.get(dependentDef); + + // base-plugin exports its API package to other plugins + assertThat(canLoadClass(dependentClassloader, "org.sonar.plugins.base.api.BaseApi")).isTrue(); + assertThat(canLoadClass(dependentClassloader, BASE_PLUGIN_CLASSNAME)).isFalse(); + assertThat(canLoadClass(dependentClassloader, DEPENDENT_PLUGIN_CLASSNAME)).isTrue(); + + // dependent-plugin does not export its classes + assertThat(canLoadClass(baseClassloader, DEPENDENT_PLUGIN_CLASSNAME)).isFalse(); + assertThat(canLoadClass(baseClassloader, BASE_PLUGIN_CLASSNAME)).isTrue(); + } + + private static PluginClassloaderDef basePluginDef() { + PluginClassloaderDef def = new PluginClassloaderDef(BASE_PLUGIN_KEY); + def.addMainClass(BASE_PLUGIN_KEY, BASE_PLUGIN_CLASSNAME); + def.getExportMask().addInclusion("org/sonar/plugins/base/api/"); + def.addFiles(asList(fakePluginJar("base-plugin/target/base-plugin-0.1-SNAPSHOT.jar"))); + return def; + } + + private static PluginClassloaderDef dependentPluginDef() { + PluginClassloaderDef def = new PluginClassloaderDef(DEPENDENT_PLUGIN_KEY); + def.addMainClass(DEPENDENT_PLUGIN_KEY, DEPENDENT_PLUGIN_CLASSNAME); + def.getExportMask().addInclusion("org/sonar/plugins/dependent/api/"); + def.addFiles(asList(fakePluginJar("dependent-plugin/target/dependent-plugin-0.1-SNAPSHOT.jar"))); + return def; + } + + private static File fakePluginJar(String path) { + // Maven way + File file = new File("src/test/projects/" + path); + if (!file.exists()) { + // Intellij way + file = new File("sonar-core/src/test/projects/" + path); + if (!file.exists()) { + throw new IllegalArgumentException("Fake projects are not built: " + path); + } + } + return file; + } + + private static boolean canLoadClass(ClassLoader classloader, String classname) { + try { + classloader.loadClass(classname); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginExploderTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginJarExploderTest.java similarity index 94% rename from sonar-core/src/test/java/org/sonar/core/platform/PluginExploderTest.java rename to sonar-core/src/test/java/org/sonar/core/platform/PluginJarExploderTest.java index 85906b1e8ce..6e760f551e3 100644 --- a/sonar-core/src/test/java/org/sonar/core/platform/PluginExploderTest.java +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginJarExploderTest.java @@ -29,7 +29,7 @@ import java.io.File; import static org.assertj.core.api.Assertions.assertThat; -public class PluginExploderTest { +public class PluginJarExploderTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); @@ -40,7 +40,7 @@ public class PluginExploderTest { final File toDir = temp.newFolder(); PluginInfo pluginInfo = new PluginInfo("checkstyle").setJarFile(jarFile); - PluginExploder exploder = new PluginExploder() { + PluginJarExploder exploder = new PluginJarExploder() { @Override public ExplodedPlugin explode(PluginInfo info) { try { @@ -63,7 +63,7 @@ public class PluginExploderTest { final File toDir = temp.newFolder(); PluginInfo pluginInfo = new PluginInfo("foo").setJarFile(jarFile); - PluginExploder exploder = new PluginExploder() { + PluginJarExploder exploder = new PluginJarExploder() { @Override public ExplodedPlugin explode(PluginInfo info) { return explodeFromUnzippedDir("foo", info.getNonNullJarFile(), toDir); 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 index e2da9f4c85f..76ddb9f2c3d 100644 --- a/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java @@ -20,43 +20,57 @@ package org.sonar.core.platform; import com.google.common.collect.ImmutableMap; -import org.apache.commons.io.FileUtils; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; 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.SonarPlugin; import org.sonar.api.utils.ZipUtils; +import org.sonar.updatecenter.common.Version; -import java.io.File; -import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class PluginLoaderTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); - @Test - public void load_and_unload_plugins() { - File checkstyleJar = FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/sonar-checkstyle-plugin-2.8.jar")); - PluginInfo checkstyleInfo = PluginInfo.create(checkstyleJar); + PluginClassloaderFactory classloaderFactory = mock(PluginClassloaderFactory.class); + PluginLoader loader = new PluginLoader(new FakePluginExploder(), classloaderFactory); - PluginLoader loader = new PluginLoader(new TempPluginExploder()); - Map instances = loader.load(ImmutableMap.of("checkstyle", checkstyleInfo)); + @Test + public void instantiate_plugin_entry_point() { + PluginClassloaderDef def = new PluginClassloaderDef("fake"); + def.addMainClass("fake", FakePlugin.class.getName()); - assertThat(instances).containsOnlyKeys("checkstyle"); - Plugin checkstyleInstance = instances.get("checkstyle"); - assertThat(checkstyleInstance.getClass().getName()).isEqualTo("org.sonar.plugins.checkstyle.CheckstylePlugin"); + Map instances = loader.instantiatePluginClasses(ImmutableMap.of(def, getClass().getClassLoader())); + assertThat(instances).containsOnlyKeys("fake"); + assertThat(instances.get("fake")).isInstanceOf(FakePlugin.class); + } - loader.unload(instances.values()); - // TODO test that classloaders are closed. Two strategies: - // + @Test + public void plugin_entry_point_must_be_no_arg_public() { + PluginClassloaderDef def = new PluginClassloaderDef("fake"); + def.addMainClass("fake", IncorrectPlugin.class.getName()); + + try { + loader.instantiatePluginClasses(ImmutableMap.of(def, getClass().getClassLoader())); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("Fail to instantiate class [org.sonar.core.platform.PluginLoaderTest$IncorrectPlugin] of plugin [fake]"); + } } @Test @@ -64,26 +78,40 @@ public class PluginLoaderTest { File jarFile = temp.newFile(); PluginInfo info = new PluginInfo("foo") .setJarFile(jarFile) - .setMainClass("org.foo.FooPlugin"); + .setMainClass("org.foo.FooPlugin") + .setMinimalSqVersion(Version.create("5.2")); - PluginLoader loader = new PluginLoader(new FakePluginExploder()); - Collection defs = loader.defineClassloaders(ImmutableMap.of("foo", info)); + Collection defs = loader.defineClassloaders(ImmutableMap.of("foo", info)); assertThat(defs).hasSize(1); - ClassloaderDef def = defs.iterator().next(); + PluginClassloaderDef def = defs.iterator().next(); assertThat(def.getBasePluginKey()).isEqualTo("foo"); assertThat(def.isSelfFirstStrategy()).isFalse(); assertThat(def.getFiles()).containsOnly(jarFile); assertThat(def.getMainClassesByPluginKey()).containsOnly(MapEntry.entry("foo", "org.foo.FooPlugin")); // TODO test mask - require change in sonar-classloader + + // built with SQ 5.2+ -> does not need API compatibility mode + assertThat(def.isCompatibilityMode()).isFalse(); + } + + @Test + public void enable_compatibility_mode_if_plugin_is_built_before_5_2() throws Exception { + File jarFile = temp.newFile(); + PluginInfo info = new PluginInfo("foo") + .setJarFile(jarFile) + .setMainClass("org.foo.FooPlugin") + .setMinimalSqVersion(Version.create("4.5.2")); + + Collection defs = loader.defineClassloaders(ImmutableMap.of("foo", info)); + assertThat(defs.iterator().next().isCompatibilityMode()).isTrue(); } /** - * A plugin can be extended by other plugins. In this case they share the same classloader. - * The first plugin is named "base plugin". + * A plugin (the "base" plugin) can be extended by other plugins. In this case they share the same classloader. */ @Test - public void define_same_classloader_for_multiple_plugins() throws Exception { + public void test_plugins_sharing_the_same_classloader() throws Exception { File baseJarFile = temp.newFile(), extensionJar1 = temp.newFile(), extensionJar2 = temp.newFile(); PluginInfo base = new PluginInfo("foo") .setJarFile(baseJarFile) @@ -105,13 +133,11 @@ public class PluginLoaderTest { .setBasePlugin("foo") .setUseChildFirstClassLoader(true); - PluginLoader loader = new PluginLoader(new FakePluginExploder()); - - Collection defs = loader.defineClassloaders(ImmutableMap.of( + Collection defs = loader.defineClassloaders(ImmutableMap.of( base.getKey(), base, extension1.getKey(), extension1, extension2.getKey(), extension2)); assertThat(defs).hasSize(1); - ClassloaderDef def = defs.iterator().next(); + PluginClassloaderDef def = defs.iterator().next(); assertThat(def.getBasePluginKey()).isEqualTo("foo"); assertThat(def.isSelfFirstStrategy()).isFalse(); assertThat(def.getFiles()).containsOnly(baseJarFile, extensionJar1, extensionJar2); @@ -122,27 +148,35 @@ public class PluginLoaderTest { // TODO test mask - require change in sonar-classloader } + + /** * Does not unzip jar file. It directly returns the JAR file defined on PluginInfo. */ - private static class FakePluginExploder extends PluginExploder { + private static class FakePluginExploder extends PluginJarExploder { @Override public ExplodedPlugin explode(PluginInfo info) { return new ExplodedPlugin(info.getKey(), info.getNonNullJarFile(), Collections.emptyList()); } } - private class TempPluginExploder extends PluginExploder { + public static class FakePlugin extends SonarPlugin { @Override - public ExplodedPlugin explode(PluginInfo info) { - try { - File tempDir = temp.newFolder(); - ZipUtils.unzip(info.getNonNullJarFile(), tempDir, newLibFilter()); - return explodeFromUnzippedDir(info.getKey(), info.getNonNullJarFile(), tempDir); - - } catch (IOException e) { - throw new IllegalStateException(e); - } + public List getExtensions() { + return Collections.emptyList(); + } + } + + /** + * No public empty-param constructor + */ + public static class IncorrectPlugin extends SonarPlugin { + public IncorrectPlugin(String s) { + } + + @Override + public List getExtensions() { + return Collections.emptyList(); } } } diff --git a/sonar-core/src/test/projects/.gitignore b/sonar-core/src/test/projects/.gitignore new file mode 100644 index 00000000000..a945b8525e6 --- /dev/null +++ b/sonar-core/src/test/projects/.gitignore @@ -0,0 +1,7 @@ +# see README.txt +!*/target/ +*/target/classes/ +*/target/maven-archiver/ +*/target/maven-status/ +*/target/test-*/ + diff --git a/sonar-core/src/test/projects/README.txt b/sonar-core/src/test/projects/README.txt new file mode 100644 index 00000000000..c53a66d52f2 --- /dev/null +++ b/sonar-core/src/test/projects/README.txt @@ -0,0 +1,3 @@ +This directory provides the fake plugins used by tests. These tests are rarely changed, so generated +artifacts are stored in Git repository (see .gitignore). It avoids from adding unnecessary modules +to build. diff --git a/sonar-core/src/test/projects/base-plugin/pom.xml b/sonar-core/src/test/projects/base-plugin/pom.xml new file mode 100644 index 00000000000..1f15c59228c --- /dev/null +++ b/sonar-core/src/test/projects/base-plugin/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + org.codehaus.sonar.tests + base-plugin + 0.1-SNAPSHOT + sonar-plugin + Base Plugin + Fake plugin used to verify building of plugin classloaders + + + + org.codehaus.sonar + sonar-plugin-api + 5.2-SNAPSHOT + provided + + + + src + + + org.codehaus.sonar + sonar-packaging-maven-plugin + 1.13 + true + + base + org.sonar.plugins.base.BasePlugin + + + + + + diff --git a/sonar-core/src/test/projects/base-plugin/src/org/sonar/plugins/base/BasePlugin.java b/sonar-core/src/test/projects/base-plugin/src/org/sonar/plugins/base/BasePlugin.java new file mode 100644 index 00000000000..e4c41e585e5 --- /dev/null +++ b/sonar-core/src/test/projects/base-plugin/src/org/sonar/plugins/base/BasePlugin.java @@ -0,0 +1,13 @@ +package org.sonar.plugins.base; + +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/sonar-core/src/test/projects/base-plugin/src/org/sonar/plugins/base/api/BaseApi.java b/sonar-core/src/test/projects/base-plugin/src/org/sonar/plugins/base/api/BaseApi.java new file mode 100644 index 00000000000..6bc9947358c --- /dev/null +++ b/sonar-core/src/test/projects/base-plugin/src/org/sonar/plugins/base/api/BaseApi.java @@ -0,0 +1,6 @@ +package org.sonar.plugins.base.api; + +public class BaseApi { + public void doNothing() { + } +} diff --git a/sonar-core/src/test/projects/base-plugin/target/base-plugin-0.1-SNAPSHOT.jar b/sonar-core/src/test/projects/base-plugin/target/base-plugin-0.1-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..c2bdb08c95c887fd0c56304430c48b873f38468f GIT binary patch literal 3421 zcmbtXdpy&7A0I=cmRqeXB+pJ!&7>o-usybl+^3?B`&=^Hj!h&IxkRPr8bU5RB{?Wk zL_#}mU8r=$saO()#xmmh{c>5wyk6%#-@Ufq{`kD#pM5{q_bg4JVv8Z*sr#dn@a@a= zhZOKM!xOQac9|12EvL;SA+>;6P zxXpTzvTv)()YEY9onjO0Y@c|%w(FCxdCc zw$+Z86zxTh&veJ_eng>s?@(w&$!-=?b=Wz`A%t(@P%!rY84(s55C!7>Hof_1hkGGbG z2a`BoDt5X`6Z%uH9D%#|z|+ceyt$Pr_#F-z#bz4Jewm~vxWc&jf#Nj>LfAQ++*V(=*os%q?g^r&64K7qFB2!H!1u^YEh3 z7qExVYwzMrCoKR%2nRXSyk>!t-)a%sbsXLg0DcDwZT-SP9N-p9^Fq0jo$2%li+d(@ z=#}TGWAEB}|Evmqb4LXouxABvfY^5YDz5}_Fex>mWVjL|r5prNF&Vc`#t%5bv{M?I zjSOyg7(xy>Y2uHyi^YrC;DT7EKe~ti{lyI{N$t0m{pF_4!45-sXM;HU9bq2qF{%_eAhI)g6}sFWek4nRZ6 zTJDA-r;j;Xe;pvE3 z-Z2-|QaE2zQzM$4?}?Gq{5zC=jeXqM0^u6=+M&qKJ1ME}(CLZ9j4jIJ5W~hV743L00!*2c3j+V;6YDP6^_t1_c^4{3+x<8RK}l_vLAm zwy;^lj)5`j)8U7gV7bi zyufa5@h-V-W%+kgeTz%ZP*k~AEEW1iQO+OoeB$pegai6PB1cd!mevHS>OA}pxp0Z( z!?o^2|F+jHdVQ7IL)>*Q`H90vuOcGy7izy{K#XTcq>u9<5@oiA=XN4k!#^1Vjfrxl zx{*Ac{phG~9g1R*=!}K>Zp%Ml0lRV2R4dQ^E=dRm7~pk%`MnTecn<;tME_qh(x^VD z03UKY7wXRNO{=N*G^9 zTg7A4!!7Vy=VMQ4wn+iUF7)CmQ6xj%_SXkT4LUd~R}nf{r&~gdbi2Gr@1DQ7=}xGR zq+*>rXvGp&%=noAFjO;FyePy$eieg~xLf4MX{ z8l{cM@wcbJD~?>#;KPf{NWL6qZO8p{nuM+8;rzXdnRizr*HuAm31#=n;zoBrGlhD0 z*po}&Naz>cw{DdW<**x;!|$hAyux)n_CYR3s)bbR-FvP8A07JZue(DZYSoZ0t?W7{ zw0RaAR(LOsj-4=4uM!z)fa6Z0l((xA!hXRGNeYQj-g zc->N53pyusJh;6bT2}626c)b6JT0g?21|qwA-6tPcFP~5JpE9KNx!%6F~bG#qEdUo z)+X)Upbw?cN;l^4&6lciHuRD&5zR5K5NoMz#SoJTC|%=$*s0;6ru&Nq7g?H$h$=vZ z%Ol`|z>$IUW5MTaa|>Ye%O%hlJh`FZke-EoC!PchbLR-S^QR8zI2Rc8Qwl)%Po25| zhyfAoX~c{o5RL;i!PEh9AAi6J6oPO(s1XEsWnkV2l*kNzS}_PG0X2hw#6~#Dl!BNc znqNcAQ00c6TA1huMIp$>{A*UgdK)VAjz2G50qNWngL`G_$Ur71)x^Iml9??tbI-Y8 zaL)+9mUF=iY#rgO;NB6iCjA4}AGgqqhxzx=jE~&Vo`30KdMgQ{JC(AZ=aRq?R&q{s fg$C&RtALIKQ_9j*Tms~T0WS@pLzTdPPmuosqKmSq literal 0 HcmV?d00001 diff --git a/sonar-core/src/test/projects/dependent-plugin/pom.xml b/sonar-core/src/test/projects/dependent-plugin/pom.xml new file mode 100644 index 00000000000..d414af74fe6 --- /dev/null +++ b/sonar-core/src/test/projects/dependent-plugin/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + org.codehaus.sonar.tests + dependent-plugin + 0.1-SNAPSHOT + sonar-plugin + Dependent Plugin + Fake plugin used to verify that plugins can export some resources to other plugins + + + + org.codehaus.sonar + sonar-plugin-api + 5.2-SNAPSHOT + provided + + + org.codehaus.sonar.tests + base-plugin + 0.1-SNAPSHOT + sonar-plugin + provided + + + + src + + + org.codehaus.sonar + sonar-packaging-maven-plugin + 1.13 + true + + dependent + org.sonar.plugins.dependent.DependentPlugin + + + + + + diff --git a/sonar-core/src/test/projects/dependent-plugin/src/org/sonar/plugins/dependent/DependentPlugin.java b/sonar-core/src/test/projects/dependent-plugin/src/org/sonar/plugins/dependent/DependentPlugin.java new file mode 100644 index 00000000000..5d320db62f9 --- /dev/null +++ b/sonar-core/src/test/projects/dependent-plugin/src/org/sonar/plugins/dependent/DependentPlugin.java @@ -0,0 +1,18 @@ +package org.sonar.plugins.dependent; + +import org.sonar.api.SonarPlugin; +import org.sonar.plugins.base.api.BaseApi; +import java.util.Collections; +import java.util.List; + +public class DependentPlugin extends SonarPlugin { + + public DependentPlugin() { + // uses a class that is exported by base-plugin + new BaseApi().doNothing(); + } + + public List getExtensions() { + return Collections.emptyList(); + } +} diff --git a/sonar-core/src/test/projects/dependent-plugin/target/dependent-plugin-0.1-SNAPSHOT.jar b/sonar-core/src/test/projects/dependent-plugin/target/dependent-plugin-0.1-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..b828a8c638600636a6be0da3fab06313fe1917ab GIT binary patch literal 3077 zcmWIWW@h1H0D&XR_PT)?P=XUk`?`iW>U#RQ=?6fSaWEVPD)YX5Q9YcIfuVtkfk6aW znXjXtr<-eVh@P)oYS8JtLk0qS+)vrR+1r&Gv}oH-#z0s30F_H|+U9XW-3jj7nis^! zvv)|}n!VL{x3TqW*2ziD$K2#s^adEU3NB3CusjM8 z!`2ynlRDG_|4cY;;(JVU@9u?$rE%)JRv)C`S$czY7)VU$2dS}uMmFGQ`U(Rg5E51wpOYxkFy{fl& zn*8;6k=}NY9~Kdq@x=m+i2R~-cvNAk0L3#%MR9&!Vi6v7LMZAAa!S)P^NR7Pm%*w& zCAAL^_d+-{w<4* zjnxgkzRTvr6(O^Eoh^?tl_zx_dA!AXPFlz-sfX}sFC!sukp^KWyD zpPi|!uCHU=(3;Axw=Bk|S|j1qp`P~0GdpLLUrf+=>!Es9&w1lbtK@=PRw6vYjUkUL zcE9R+GNoMk(-r2sk1tC4FWJiOcRc3AltkqVp7;H?KI)WrkBnb+h2K>tL}SWF{<%de0zqLW%k|GZ>ozfH2j&SNt{40MxHKS&8?#aHZZ5xYL^qbZ;y)r&i(|_Sw zpi0%V;$53|be&y0mrGmYqS+hGhMU)gt5V)RH8a@wjpJL>p`XfK`~~ldA4xp4=xULZ z3GrAj)RZ^R>6(S-Ey=9rqS-3BXDSb!xEtOZ^ltUv6Q%o#)QiLZGUHBN{J@xkmj=0s zWvO}aXvIuu8kmZK=}s>>KP5FIv9wqZoB;JoQj1IQB`Zr3Oo1mhT}X0;JC1`P2v}fE zXxaZ$2bkX2*cccbC^5DmKUc3JH)n3h>Ac&(!tJ*<$6eQ3p4=jAXBT7$Y{|GJQ4sXy zR6zFZUSF=5Q~mq(UA;EFxuF|$*TVYuth=K7Z>|kbVdmRvIyuLsU6OHe&cd@vlWgtp zKApcw+VH{B&HEiIg>JB^-@Np@=**3%yaNG|URznR?l$?#%imzPTC>>YSw;J<fCr(sj>3O#SiCP zJdR!8)^PRwm3e4jF<9cH(Xst2(~F<;ek}dc%h@@p?ZCP%=Y+RpEAU+i z^JBcmcTNA`){t|*KC){ju5!(E5L{l>@NJRg=lB-eNhU?rr@l@-k-E}9wf9fhk^V2Q z_H3EhdTN@&p|boEM%MI~bGg!%i|$|FYP`Gf)`@PH9UG+FZDV5kPkj0v@abcj%M1Ie zH+yFv{5aif{j9EvsD|jbht?n1wbx;}(+**Sz}Z({ba~`ol>JeiR$un(?Z4+Q*Xggm zak( zu{)(Hv7jiwAhoC@Gqsra)M>863IYxn_r@!x$QmwOXfnZ7vsU+4!xQ$LIS~tcjQSe? z)ZV?zynPoFlO&tA{p^|qsM z!~-BbARU*WI$+f$ZhfE%69Pc`exc|?RG+x@gDOyjel1`@g{Y=s`r#!UuxNvsguOzA znYE-bg@8#|t5<~am_Z7v1t36x;s1X=U5=RCtIY>I|4Q*lP`#MN1lQP+|?V4nc%7_L2c%TM|1)IO7Z|3{Qb + + 4.0.0 + org.codehaus.sonar.tests + parent + 0.1-SNAPSHOT + pom + + base-plugin + dependent-plugin + + + diff --git a/sonar-plugin-api-deps/pom.xml b/sonar-plugin-api-deps/pom.xml index 2a349a76f2a..30caea24fb3 100644 --- a/sonar-plugin-api-deps/pom.xml +++ b/sonar-plugin-api-deps/pom.xml @@ -9,35 +9,76 @@ sonar-plugin-api-deps - jar SonarQube :: Plugin API Dependencies Deprecated transitive dependencies of sonar-plugin-api + + + com.google.code.gson gson + 2.3.1 com.google.guava guava + 10.0.1 commons-codec commons-codec + 1.8 commons-collections commons-collections + 3.2.1 commons-io commons-io + 2.4 commons-lang commons-lang + 2.6 + + + dom4j + dom4j + 1.6.1 + + + xml-apis + xml-apis + 1.4.01 + + + org.picocontainer + picocontainer + 2.14.3 + + + org.slf4j + slf4j-api + 1.7.10 + + + ch.qos.logback + logback-classic + 1.1.2 + + + ch.qos.logback + logback-core + 1.1.2 @@ -63,7 +104,7 @@ false - true + diff --git a/sonar-plugin-api/pom.xml b/sonar-plugin-api/pom.xml index 2930c541710..e200932a303 100644 --- a/sonar-plugin-api/pom.xml +++ b/sonar-plugin-api/pom.xml @@ -18,6 +18,11 @@ + + com.google.code.gson gson @@ -26,6 +31,30 @@ com.google.guava guava + + commons-codec + commons-codec + + + commons-collections + commons-collections + + + commons-io + commons-io + + + commons-lang + commons-lang + + + org.codehaus.sonar sonar-check-api @@ -50,23 +79,45 @@ + + org.codehaus.sonar + sonar-graph + + + + + org.codehaus.woodstox + woodstox-core-lgpl + + + stax + stax-api + + + + + org.codehaus.woodstox + stax2-api + + + org.codehaus.staxmate + staxmate + jfree jfreechart provided + + com.google.code.findbugs jsr305 provided - - org.codehaus.sonar - sonar-graph - - provided - + + @@ -87,22 +138,6 @@ - - commons-codec - commons-codec - - - commons-collections - commons-collections - - - commons-io - commons-io - - - commons-lang - commons-lang - org.slf4j slf4j-api @@ -114,24 +149,6 @@ xpp3 provided - - org.codehaus.woodstox - woodstox-core-lgpl - - - stax - stax-api - - - - - org.codehaus.woodstox - stax2-api - - - org.codehaus.staxmate - staxmate - javax.servlet javax.servlet-api -- 2.39.5