]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6749 add serverExtension support to Plugin class loaders 464/head
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 31 Jul 2015 15:09:13 +0000 (17:09 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 6 Aug 2015 12:20:48 +0000 (14:20 +0200)
sonar-core/src/main/java/org/sonar/core/platform/PluginClassloaderDef.java
sonar-core/src/main/java/org/sonar/core/platform/PluginClassloaderFactory.java
sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java
sonar-core/src/test/java/org/sonar/classloader/MaskReader.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java

index 9939c1a1c4232ecef571ac5b84197ec4c6a3d999..a78403fa49eb0a02fede0db7325cb245e5ecd2f4 100644 (file)
@@ -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) {
index f321e2bab72c4564b08a1a723cc41ccda3d26e44..3750e9e925a326dcaf4da4cf06373c627020e520 100644 (file)
@@ -65,9 +65,9 @@ public class PluginClassloaderFactory {
   public Map<PluginClassloaderDef, ClassLoader> create(Collection<PluginClassloaderDef> 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/");
index c3a284281414fb07b9fc3b35240df103c9b8587c..3428358e6fdb304b608b70f4492d1798af0414ad 100644 (file)
@@ -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.
  * <p/>
- * 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<String> 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 (file)
index 0000000..2df4ca9
--- /dev/null
@@ -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<String> getInclusions() {
+    return mask.getInclusions();
+  }
+
+  public List<String> getExclusions() {
+    return mask.getExclusions();
+  }
+}
index fbeae3a2483eaea3f6b7e785c25bf5b7e709fbe5..3e4a2634740f9b7d233da8cca36b6b0b009363d1 100644 (file)
@@ -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<PluginClassloaderDef> 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<PluginClassloaderDef> 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.
    */