From: Sébastien Lesaint Date: Fri, 31 Jul 2015 15:09:13 +0000 (+0200) Subject: SONAR-6749 add serverExtension support to Plugin class loaders X-Git-Tag: 5.2-RC1~819 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=d2599d66acb0df2b9a9be95dfbd09e396f54310b;p=sonarqube.git SONAR-6749 add serverExtension support to Plugin class loaders --- 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 9939c1a1c42..a78403fa49e 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 @@ -46,6 +46,8 @@ class PluginClassloaderDef { */ private boolean compatibilityMode = false; + private boolean serverExtension = false; + PluginClassloaderDef(String basePluginKey) { Preconditions.checkArgument(!Strings.isNullOrEmpty(basePluginKey)); this.basePluginKey = basePluginKey; @@ -93,6 +95,14 @@ class PluginClassloaderDef { this.compatibilityMode = b; } + boolean isServerExtension() { + return serverExtension; + } + + void setServerExtension(boolean serverExtension) { + this.serverExtension = serverExtension; + } + @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 f321e2bab72..3750e9e925a 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 @@ -65,9 +65,9 @@ public class PluginClassloaderFactory { 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.setMask(API_CLASSLOADER_KEY, def.isServerExtension() ? new Mask() : apiMask()); builder.newClassloader(def.getBasePluginKey()); builder.setParent(def.getBasePluginKey(), API_CLASSLOADER_KEY, new Mask()); builder.setLoadingOrder(def.getBasePluginKey(), def.isSelfFirstStrategy() ? SELF_FIRST : PARENT_FIRST); @@ -145,7 +145,7 @@ 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/") @@ -169,6 +169,7 @@ public class PluginClassloaderFactory { .addInclusion("org/sonar/server/platform/") .addInclusion("org/sonar/core/persistence/") .addInclusion("org/sonar/core/properties/") + .addInclusion("org/sonar/server/views/") // API exclusions .addExclusion("org/sonar/api/internal/"); 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 c3a28428141..3428358e6fd 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,10 +21,12 @@ package org.sonar.core.platform; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableSet; import java.io.Closeable; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Set; import org.apache.commons.lang.SystemUtils; import org.sonar.api.Plugin; import org.sonar.api.utils.log.Loggers; @@ -44,11 +46,17 @@ import static java.util.Arrays.asList; * 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 pointers to classloaders and {@link Plugin}. + * This class is stateless. It does not keep pointers to classloaders and {@link org.sonar.api.SonarPlugin}. */ public class PluginLoader { private static final String[] DEFAULT_SHARED_RESOURCES = {"org/sonar/plugins", "com/sonar/plugins", "com/sonarsource/plugins"}; + /** + * Defines the base keys (defined by {@link #basePluginKey(PluginInfo, Map)}) of the plugins which are allowed to + * run a full server extensions. + */ + private static final Set SYSTEM_EXTENSION_PLUGINS_BASE_KEYS = ImmutableSet.of("views"); + public static final Version COMPATIBILITY_MODE_MAX_VERSION = Version.create("5.2"); private final PluginJarExploder jarExploder; @@ -85,9 +93,9 @@ public class PluginLoader { def.addFiles(explodedPlugin.getLibs()); def.addMainClass(info.getKey(), info.getMainClass()); - for (String defaultSharedResource : DEFAULT_SHARED_RESOURCES) { - def.getExportMask().addInclusion(String.format("%s/%s/api/", defaultSharedResource, info.getKey())); - } + for (String defaultSharedResource : DEFAULT_SHARED_RESOURCES) { + 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. @@ -96,6 +104,7 @@ public class PluginLoader { Version minSqVersion = info.getMinimalSqVersion(); boolean compatibilityMode = minSqVersion != null && minSqVersion.compareToIgnoreQualifier(COMPATIBILITY_MODE_MAX_VERSION) < 0; def.setCompatibilityMode(compatibilityMode); + def.setServerExtension(isServerExtension(baseKey)); if (compatibilityMode) { Loggers.get(getClass()).debug("API compatibility mode is enabled on plugin {} [{}] " + "(built with API lower than {})", @@ -106,8 +115,12 @@ public class PluginLoader { return classloadersByBasePlugin.values(); } + private static boolean isServerExtension(String basePluginKey) { + return SYSTEM_EXTENSION_PLUGINS_BASE_KEYS.contains(basePluginKey); + } + /** - * Instantiates collection of ({@link Plugin} according to given metadata and classloaders + * Instantiates collection of {@link org.sonar.api.SonarPlugin} 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 diff --git a/sonar-core/src/test/java/org/sonar/classloader/MaskReader.java b/sonar-core/src/test/java/org/sonar/classloader/MaskReader.java new file mode 100644 index 00000000000..2df4ca9c57f --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/classloader/MaskReader.java @@ -0,0 +1,41 @@ +/* + * 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.classloader; + +import java.util.List; + +/** + * Gives access to protected read methods of {@link Mask}. + */ +public class MaskReader { + private final Mask mask; + + public MaskReader(Mask mask) { + this.mask = mask; + } + + public List getInclusions() { + return mask.getInclusions(); + } + + public List getExclusions() { + return mask.getExclusions(); + } +} 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 fbeae3a2483..3e4a2634740 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,6 +21,7 @@ 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; @@ -146,6 +147,34 @@ public class PluginLoaderTest { // TODO test mask - require change in sonar-classloader } + @Test + public void plugin_is_recognised_as_server_extension_if_key_is_views_and_extends_no_other_plugin_and_runs_in_compatibility_mode() throws IOException { + PluginInfo views = create52PluginInfo("views"); + + Collection defs = loader.defineClassloaders(ImmutableMap.of("views", views)); + + assertThat(defs.iterator().next().isServerExtension()).isTrue(); + } + + @Test + public void plugin_is_not_recognised_as_system_extension_if_key_is_views_and_extends_another_plugin() throws IOException { + PluginInfo foo = create52PluginInfo("foo"); + PluginInfo views = create52PluginInfo("views") + .setBasePlugin("foo"); + + Collection defs = loader.defineClassloaders(ImmutableMap.of("foo", foo, "views", views)); + + assertThat(defs).extracting("compatibilityMode").containsOnly(false, false); + } + + private PluginInfo create52PluginInfo(String pluginKey) throws IOException { + File jarFile = temp.newFile(); + return new PluginInfo(pluginKey) + .setJarFile(jarFile) + .setMainClass("org.foo." + pluginKey + "Plugin") + .setMinimalSqVersion(Version.create("5.2")); + } + /** * Does not unzip jar file. It directly returns the JAR file defined on PluginInfo. */