diff options
author | Duarte Meneses <duarte.meneses@sonarsource.com> | 2018-07-19 11:42:54 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-07-19 20:21:26 +0200 |
commit | e51cb49945d4f3a1d39f74234de49be8ca925fff (patch) | |
tree | 6dc4181f26a0f4895874e1c6fb1f83669a213839 /sonar-core | |
parent | 35428013060d9fb81e7119426e9bfe9858521d4a (diff) | |
download | sonarqube-e51cb49945d4f3a1d39f74234de49be8ca925fff.tar.gz sonarqube-e51cb49945d4f3a1d39f74234de49be8ca925fff.zip |
Revert "SONAR-10541, SONAR-10331 Drop compatibility mode and clean plugin classloader"
This reverts commit 4dcb5245f1147048d122ff2d335a6b5b7364c565.
Diffstat (limited to 'sonar-core')
7 files changed, 126 insertions, 32 deletions
diff --git a/sonar-core/build.gradle b/sonar-core/build.gradle index aad84e6dbfe..a2d0f1bb189 100644 --- a/sonar-core/build.gradle +++ b/sonar-core/build.gradle @@ -4,6 +4,10 @@ sonarqube { } } +configurations { + includeInResources +} + dependencies { // please keep list ordered @@ -20,6 +24,8 @@ dependencies { compileOnly 'com.google.code.findbugs:jsr305' + includeInResources project(path: ':sonar-plugin-api-deps', configuration: 'shadow') + testCompile 'com.tngtech.java:junit-dataprovider' testCompile 'junit:junit' testCompile 'org.assertj:assertj-core' @@ -30,6 +36,14 @@ dependencies { testCompileOnly 'com.google.code.findbugs:jsr305' } +// sonar-plugin-api.jar is copied into target JAR file +processResources { + into('/') { + from configurations.includeInResources + rename '(.*)-' + project.version + '-all.jar', '$1.jar' + } +} + // Used by sonar-db-core to run DB Unit Tests artifactoryPublish.skip = false publishing { diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginClassLoaderDef.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginClassLoaderDef.java index d228ee088bd..c1c3584b58a 100644 --- a/sonar-core/src/main/java/org/sonar/core/platform/PluginClassLoaderDef.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginClassLoaderDef.java @@ -41,6 +41,11 @@ class PluginClassLoaderDef { private final Mask mask = new Mask(); private boolean selfFirstStrategy = false; + /** + * 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; @@ -80,6 +85,14 @@ class PluginClassLoaderDef { } } + boolean isCompatibilityMode() { + return compatibilityMode; + } + + void setCompatibilityMode(boolean b) { + this.compatibilityMode = b; + } + @Override public boolean equals(@Nullable Object o) { if (this == o) { 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 index bc584778d68..0c69f978a09 100644 --- a/sonar-core/src/main/java/org/sonar/core/platform/PluginClassloaderFactory.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginClassloaderFactory.java @@ -25,9 +25,11 @@ 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.ScannerSide; import org.sonar.api.ce.ComputeEngineSide; import org.sonar.api.server.ServerSide; +import org.sonar.api.utils.TempFolder; import org.sonar.classloader.ClassloaderBuilder; import org.sonar.classloader.Mask; @@ -38,6 +40,8 @@ import static org.sonar.classloader.ClassloaderBuilder.LoadingOrder.SELF_FIRST; * Builds the graph of classloaders to be used to instantiate plugins. It deals with: * <ul> * <li>isolation of plugins against core classes (except api)</li> + * <li>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</li> * <li>sharing of some packages between plugins</li> * <li>loading of the libraries embedded in plugin JAR files (directory META-INF/libs)</li> * </ul> @@ -50,6 +54,13 @@ 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. */ @@ -67,6 +78,9 @@ public class PluginClassloaderFactory { for (File jar : def.getFiles()) { builder.addURL(def.getBasePluginKey(), fileToUrl(jar)); } + if (def.isCompatibilityMode()) { + builder.addURL(def.getBasePluginKey(), extractCompatibilityModeJar()); + } exportResources(def, builder, defs); } @@ -106,6 +120,19 @@ public class PluginClassloaderFactory { 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(); @@ -122,25 +149,31 @@ public class PluginClassloaderFactory { */ private static Mask apiMask() { return new Mask() - .addInclusion("org/sonar/api/") + .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/codehaus/stax2/") .addInclusion("org/codehaus/staxmate/") .addInclusion("com/ctc/wstx/") .addInclusion("org/slf4j/") + .addInclusion("javax/servlet/") // SLF4J bridges. Do not let plugins re-initialize and configure their logging system .addInclusion("org/apache/commons/logging/") .addInclusion("org/apache/log4j/") .addInclusion("ch/qos/logback/") - // Exposed by org.sonar.api.server.authentication.IdentityProvider - .addInclusion("javax/servlet/") - - // required for some internal SonarSource plugins (billing, orchestrator, ...) + // required for internal libs at SonarSource .addInclusion("org/sonar/server/platform/") - - // required for commercial plugins at SonarSource + .addInclusion("org/sonar/core/persistence/") + .addInclusion("org/sonar/core/properties/") + .addInclusion("org/sonar/server/views/") .addInclusion("com/sonarsource/plugins/license/api/") // API exclusions 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 7b045fe6961..f44a346336e 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 @@ -50,7 +50,7 @@ public class PluginLoader { private static final String[] DEFAULT_SHARED_RESOURCES = {"org/sonar/plugins", "com/sonar/plugins", "com/sonarsource/plugins"}; - private static final Version COMPATIBILITY_MODE_MAX_VERSION = Version.create("5.2"); + public static final Version COMPATIBILITY_MODE_MAX_VERSION = Version.create("5.2"); private final PluginJarExploder jarExploder; private final PluginClassloaderFactory classloaderFactory; @@ -96,14 +96,15 @@ public class PluginLoader { def.setSelfFirstStrategy(info.isUseChildFirstClassLoader()); Version minSqVersion = info.getMinimalSqVersion(); boolean compatibilityMode = minSqVersion != null && minSqVersion.compareToIgnoreQualifier(COMPATIBILITY_MODE_MAX_VERSION) < 0; + def.setCompatibilityMode(compatibilityMode); if (compatibilityMode) { - Loggers.get(getClass()).warn("API compatibility mode is no longer supported. In case of error, plugin {} [{}] should package its dependencies.", - info.getName(), info.getKey()); + Loggers.get(getClass()).debug("API compatibility mode is enabled on plugin {} [{}] " + + "(built with API lower than {})", + info.getName(), info.getKey(), COMPATIBILITY_MODE_MAX_VERSION); } } } return classloadersByBasePlugin.values(); - } /** diff --git a/sonar-core/src/main/java/org/sonar/core/util/FileUtils.java b/sonar-core/src/main/java/org/sonar/core/util/FileUtils.java index 079fa552539..5b91a8055f3 100644 --- a/sonar-core/src/main/java/org/sonar/core/util/FileUtils.java +++ b/sonar-core/src/main/java/org/sonar/core/util/FileUtils.java @@ -114,7 +114,7 @@ public final class FileUtils { * <li>No exceptions are thrown when a file or directory cannot be deleted.</li> * </ul> * - * @param path file or directory to delete, can be {@code null} + * @param file file or directory to delete, can be {@code null} * @return {@code true} if the file or directory was deleted, otherwise {@code false} */ public static boolean deleteQuietly(@Nullable Path path) { 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 index 329a5431d7a..9d825b758af 100644 --- a/sonar-core/src/test/java/org/sonar/core/platform/PluginClassloaderFactoryTest.java +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginClassloaderFactoryTest.java @@ -23,8 +23,10 @@ import com.sonarsource.plugins.license.api.FooBar; 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; @@ -36,7 +38,10 @@ public class PluginClassloaderFactoryTest { static final String BASE_PLUGIN_KEY = "base"; static final String DEPENDENT_PLUGIN_KEY = "dependent"; - PluginClassloaderFactory factory = new PluginClassloaderFactory(); + @Rule + public JUnitTempFolder temp = new JUnitTempFolder(); + + PluginClassloaderFactory factory = new PluginClassloaderFactory(temp); @Test public void create_isolated_classloader() { @@ -57,6 +62,20 @@ public class PluginClassloaderFactoryTest { } @Test + public void create_classloader_compatible_with_with_old_api_dependencies() { + 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() { PluginClassLoaderDef baseDef = basePluginDef(); PluginClassLoaderDef dependentDef = dependentPluginDef(); 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 2a2ae2c3575..76a1268f933 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 @@ -21,15 +21,13 @@ package org.sonar.core.platform; import com.google.common.collect.ImmutableMap; import java.io.File; +import java.io.IOException; import java.util.Collection; import java.util.Collections; -import java.util.List; import org.assertj.core.data.MapEntry; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.sonar.api.utils.log.LogTester; -import org.sonar.api.utils.log.LoggerLevel; import org.sonar.updatecenter.common.Version; import static org.assertj.core.api.Assertions.assertThat; @@ -41,11 +39,8 @@ public class PluginLoaderTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); - @Rule - public LogTester logTester = new LogTester(); - private PluginClassloaderFactory classloaderFactory = mock(PluginClassloaderFactory.class); - private PluginLoader underTest = new PluginLoader(new FakePluginExploder(), classloaderFactory); + private PluginLoader loader = new PluginLoader(new FakePluginExploder(), classloaderFactory); @Test public void define_classloader() throws Exception { @@ -55,7 +50,7 @@ public class PluginLoaderTest { .setMainClass("org.foo.FooPlugin") .setMinimalSqVersion(Version.create("5.2")); - Collection<PluginClassLoaderDef> defs = underTest.defineClassloaders(ImmutableMap.of("foo", info)); + Collection<PluginClassLoaderDef> defs = loader.defineClassloaders(ImmutableMap.of("foo", info)); assertThat(defs).hasSize(1); PluginClassLoaderDef def = defs.iterator().next(); @@ -64,6 +59,21 @@ public class PluginLoaderTest { 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<PluginClassLoaderDef> defs = loader.defineClassloaders(ImmutableMap.of("foo", info)); + assertThat(defs.iterator().next().isCompatibilityMode()).isTrue(); } /** @@ -94,7 +104,7 @@ public class PluginLoaderTest { .setBasePlugin("foo") .setUseChildFirstClassLoader(true); - Collection<PluginClassLoaderDef> defs = underTest.defineClassloaders(ImmutableMap.of( + Collection<PluginClassLoaderDef> defs = loader.defineClassloaders(ImmutableMap.of( base.getKey(), base, extension1.getKey(), extension1, extension2.getKey(), extension2)); assertThat(defs).hasSize(1); @@ -110,18 +120,22 @@ public class PluginLoaderTest { } @Test - public void log_warning_if_plugin_is_built_with_api_5_2_or_lower() throws Exception { - File jarFile = temp.newFile(); - PluginInfo info = new PluginInfo("foo") - .setJarFile(jarFile) - .setMainClass("org.foo.FooPlugin") - .setMinimalSqVersion(Version.create("4.5.2")); + public void plugin_is_not_recognised_as_system_extension_if_key_is_governance_and_extends_another_plugin() throws IOException { + PluginInfo foo = createPluginInfo("foo"); + PluginInfo governance = createPluginInfo("governance") + .setBasePlugin("foo"); + + Collection<PluginClassLoaderDef> defs = loader.defineClassloaders(ImmutableMap.of("foo", foo, "governance", governance)); - Collection<PluginClassLoaderDef> defs = underTest.defineClassloaders(ImmutableMap.of("foo", info)); - assertThat(defs).extracting(PluginClassLoaderDef::getBasePluginKey).containsExactly("foo"); + assertThat(defs).extracting("compatibilityMode").containsOnly(false, false); + } - List<String> warnings = logTester.logs(LoggerLevel.WARN); - assertThat(warnings).contains("API compatibility mode is no longer supported. In case of error, plugin foo [foo] should package its dependencies."); + private PluginInfo createPluginInfo(String pluginKey) throws IOException { + File jarFile = temp.newFile(); + return new PluginInfo(pluginKey) + .setJarFile(jarFile) + .setMainClass("org.foo." + pluginKey + "Plugin") + .setMinimalSqVersion(Version.create("6.6")); } /** |