]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7459 new interface org.sonar.api.Plugin
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Wed, 16 Mar 2016 20:52:47 +0000 (21:52 +0100)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Fri, 18 Mar 2016 09:13:42 +0000 (10:13 +0100)
It allows to check version of SonarQube to filter extensions

25 files changed:
it/it-plugins/batch-plugin/src/main/java/com/sonarsource/BatchPlugin.java
server/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java
server/sonar-server/src/main/java/org/sonar/server/notification/NotificationDispatcherMetadata.java
server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java
server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java
sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java
sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java
sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java
sonar-plugin-api/src/main/java/org/sonar/api/Plugin.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/SonarPlugin.java
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java
sonar-plugin-api/src/test/java/org/sonar/api/PluginTest.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java
sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java
sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java
sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java
sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java
sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java
sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java
sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java
sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java
sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java

index 2be444b22a349268c8b22053a2bceb7da974be7e..5bc5ee622af1589d80d43e76a45b748a090aaaf6 100644 (file)
@@ -23,14 +23,15 @@ import com.sonarsource.decimal_scale_of_measures.DecimalScaleMeasureComputer;
 import com.sonarsource.decimal_scale_of_measures.DecimalScaleMetric;
 import com.sonarsource.decimal_scale_of_measures.DecimalScaleProperty;
 import com.sonarsource.decimal_scale_of_measures.DecimalScaleSensor;
-import java.util.Arrays;
-import java.util.List;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.Plugin;
 
-public class BatchPlugin extends SonarPlugin {
+import static java.util.Arrays.asList;
 
-  public List getExtensions() {
-    return Arrays.asList(
+public class BatchPlugin implements Plugin {
+
+  @Override
+  public void define(Context context) {
+    context.addExtensions(asList(
       // SONAR-6939 decimal_scale_of_measures
       DecimalScaleMeasureComputer.class,
       DecimalScaleMetric.class,
@@ -40,7 +41,7 @@ public class BatchPlugin extends SonarPlugin {
       DumpSettingsInitializer.class,
       RaiseMessageException.class,
       TempFolderExtension.class,
-      WaitingSensor.class);
+      WaitingSensor.class
+      ));
   }
-
 }
index 1ce79e55b0f0b19e6779505ea1cd141de88ded08..fdaffcfed8d35613ee9efce2af5f9e595229e726 100644 (file)
@@ -28,6 +28,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
 import org.picocontainer.Startable;
+import org.sonar.api.Plugin;
 import org.sonar.api.SonarPlugin;
 import org.sonar.api.server.ServerSide;
 import org.sonar.core.platform.PluginInfo;
@@ -88,7 +89,7 @@ public class DebtModelPluginRepository implements Startable {
       contributingPluginKeyToClassLoader.put(DEFAULT_MODEL, getClass().getClassLoader());
       for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) {
         String pluginKey = pluginInfo.getKey();
-        SonarPlugin plugin = pluginRepository.getPluginInstance(pluginKey);
+        Plugin plugin = pluginRepository.getPluginInstance(pluginKey);
         ClassLoader classLoader = plugin.getClass().getClassLoader();
         if (classLoader.getResource(getXMLFilePath(pluginKey)) != null) {
           contributingPluginKeyToClassLoader.put(pluginKey, classLoader);
index 0e77c0f49ca6a33838fc229b39530cdb12894919..9538f2cf60080d0283e6c06ff70abfe747cdbb52 100644 (file)
@@ -28,7 +28,7 @@ import org.sonar.api.server.ServerSide;
  * Notification dispatchers (see {@link NotificationDispatcher}) can define their own metadata class in order
  * to tell more about them.
  * <p/>
- * Instances of these classes must be declared in {@link org.sonar.api.SonarPlugin#getExtensions()}.
+ * Instances of these classes must be declared by {@link org.sonar.api.Plugin}.
  */
 @ServerSide
 public final class NotificationDispatcherMetadata {
index ee56ab6226d4ec3277924a9cfd2c7c19066d854e..2a5d0a3e7ad5b83edea81a8fd4510328e13204fc 100644 (file)
@@ -27,6 +27,7 @@ import javax.annotation.Nullable;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang.StringUtils;
 import org.picocontainer.Startable;
+import org.sonar.api.Plugin;
 import org.sonar.api.SonarPlugin;
 import org.sonar.api.platform.ServerFileSystem;
 import org.sonar.api.utils.log.Logger;
@@ -60,7 +61,7 @@ public class RailsAppsDeployer implements Startable {
 
     for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) {
       String pluginKey = pluginInfo.getKey();
-      SonarPlugin plugin = pluginRepository.getPluginInstance(pluginKey);
+      Plugin plugin = pluginRepository.getPluginInstance(pluginKey);
       try {
         deployRailsApp(appsDir, pluginKey, plugin.getClass().getClassLoader());
       } catch (Exception e) {
index eb36db275d10f59ec0a30fa8ba9c74fc15439b35..53fe385e2e3636de1c2ba10fb52e168915a4640a 100644 (file)
@@ -24,7 +24,9 @@ import com.google.common.collect.ListMultimap;
 import java.util.Map;
 import org.sonar.api.Extension;
 import org.sonar.api.ExtensionProvider;
+import org.sonar.api.Plugin;
 import org.sonar.api.SonarPlugin;
+import org.sonar.api.SonarQubeVersion;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.AnnotationUtils;
 import org.sonar.core.platform.ComponentContainer;
@@ -37,9 +39,11 @@ import org.sonar.core.platform.PluginRepository;
 @ServerSide
 public class ServerExtensionInstaller {
 
+  private final SonarQubeVersion sonarQubeVersion;
   private final PluginRepository pluginRepository;
 
-  public ServerExtensionInstaller(PluginRepository pluginRepository) {
+  public ServerExtensionInstaller(SonarQubeVersion sonarQubeVersion, PluginRepository pluginRepository) {
+    this.sonarQubeVersion = sonarQubeVersion;
     this.pluginRepository = pluginRepository;
   }
 
@@ -49,10 +53,12 @@ public class ServerExtensionInstaller {
     for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) {
       try {
         String pluginKey = pluginInfo.getKey();
-        SonarPlugin plugin = pluginRepository.getPluginInstance(pluginKey);
+        Plugin plugin = pluginRepository.getPluginInstance(pluginKey);
         container.addExtension(pluginInfo, plugin);
 
-        for (Object extension : plugin.getExtensions()) {
+        Plugin.Context context = new Plugin.Context(sonarQubeVersion);
+        plugin.define(context);
+        for (Object extension : context.getExtensions()) {
           if (installExtension(container, pluginInfo, extension, true) != null) {
             installedExtensionsByPlugin.put(pluginInfo, extension);
           } else {
index 40bcc6b20dc1e76f36ed3ac79fce63918ef00386..66729394485a4b87f04eccc1ae339c629f4ddcb3 100644 (file)
@@ -37,7 +37,7 @@ import java.util.Set;
 import javax.annotation.Nonnull;
 import org.apache.commons.io.FileUtils;
 import org.picocontainer.Startable;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.Plugin;
 import org.sonar.api.platform.Server;
 import org.sonar.api.platform.ServerUpgradeStatus;
 import org.sonar.api.utils.MessageException;
@@ -53,10 +53,10 @@ import static com.google.common.collect.Iterables.transform;
 import static com.google.common.collect.Lists.newArrayList;
 import static java.lang.String.format;
 import static org.apache.commons.io.FileUtils.copyFile;
-import static org.sonar.core.util.FileUtils.deleteQuietly;
 import static org.apache.commons.io.FileUtils.moveFile;
 import static org.apache.commons.io.FileUtils.moveFileToDirectory;
 import static org.sonar.core.platform.PluginInfo.jarToPluginInfo;
+import static org.sonar.core.util.FileUtils.deleteQuietly;
 
 /**
  * Entry point to install and load plugins on server startup. It manages
@@ -83,7 +83,7 @@ public class ServerPluginRepository implements PluginRepository, Startable {
 
   // following fields are available after startup
   private final Map<String, PluginInfo> pluginInfosByKeys = new HashMap<>();
-  private final Map<String, SonarPlugin> pluginInstancesByKeys = new HashMap<>();
+  private final Map<String, Plugin> pluginInstancesByKeys = new HashMap<>();
 
   public ServerPluginRepository(Server server, ServerUpgradeStatus upgradeStatus,
     DefaultServerFileSystem fs, PluginLoader loader) {
@@ -348,8 +348,8 @@ public class ServerPluginRepository implements PluginRepository, Startable {
   }
 
   @Override
-  public SonarPlugin getPluginInstance(String key) {
-    SonarPlugin plugin = pluginInstancesByKeys.get(key);
+  public Plugin getPluginInstance(String key) {
+    Plugin plugin = pluginInstancesByKeys.get(key);
     if (plugin == null) {
       throw new IllegalArgumentException(format("Plugin [%s] does not exist", key));
     }
index 8f4e7a0b87c0cf48c0aba68550ca75c51f416a4e..b1a05e4d43ba668f327027901c33b96cf81f918f 100644 (file)
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.Map;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+import org.sonar.api.Plugin;
 import org.sonar.api.SonarPlugin;
 import org.sonar.api.config.License;
 import org.sonar.api.config.PropertyDefinitions;
@@ -287,7 +288,7 @@ public final class JRubyFacade {
   }
 
   public Object getComponentByClassname(String pluginKey, String className) {
-    SonarPlugin plugin = get(PluginRepository.class).getPluginInstance(pluginKey);
+    Plugin plugin = get(PluginRepository.class).getPluginInstance(pluginKey);
     try {
       Class componentClass = plugin.getClass().getClassLoader().loadClass(className);
       return get(componentClass);
index 56ed55259482b2be83286d97be6f38e9bb811c79..d5da2bb12a81e6aed5b1941a9db3b4cdeac48b86 100644 (file)
@@ -24,6 +24,7 @@ import com.google.common.collect.Lists;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.List;
+import org.sonar.api.Plugin;
 import org.sonar.api.SonarPlugin;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.core.platform.PluginRepository;
@@ -71,7 +72,7 @@ class I18nClassloader extends URLClassLoader {
     // there may be duplicated classloaders in the list.
     List<ClassLoader> list = Lists.newArrayList();
     for (PluginInfo info : pluginRepository.getPluginInfos()) {
-      SonarPlugin plugin = pluginRepository.getPluginInstance(info.getKey());
+      Plugin plugin = pluginRepository.getPluginInstance(info.getKey());
       list.add(plugin.getClass().getClassLoader());
     }
     list.add(I18nClassloader.class.getClassLoader());
index 44b90ae7d553d0cc2c55d4b1e6b89273e4f0ffda..d88d037fa7e7825eebdf0053caa26a84f07a9edf 100644 (file)
@@ -28,7 +28,7 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 import org.apache.commons.lang.SystemUtils;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.Plugin;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.updatecenter.common.Version;
 
@@ -46,7 +46,7 @@ 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 org.sonar.api.SonarPlugin}.
+ * This class is stateless. It does not keep pointers to classloaders and {@link org.sonar.api.Plugin}.
  */
 public class PluginLoader {
 
@@ -67,7 +67,7 @@ public class PluginLoader {
     this.classloaderFactory = classloaderFactory;
   }
 
-  public Map<String, SonarPlugin> load(Map<String, PluginInfo> infoByKeys) {
+  public Map<String, Plugin> load(Map<String, PluginInfo> infoByKeys) {
     Collection<PluginClassLoaderDef> defs = defineClassloaders(infoByKeys);
     Map<PluginClassLoaderDef, ClassLoader> classloaders = classloaderFactory.create(defs);
     return instantiatePluginClasses(classloaders);
@@ -120,15 +120,15 @@ public class PluginLoader {
   }
 
   /**
-   * Instantiates collection of {@link org.sonar.api.SonarPlugin} according to given metadata and classloaders
+   * Instantiates collection of {@link org.sonar.api.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
    */
   @VisibleForTesting
-  Map<String, SonarPlugin> instantiatePluginClasses(Map<PluginClassLoaderDef, ClassLoader> classloaders) {
+  Map<String, Plugin> instantiatePluginClasses(Map<PluginClassLoaderDef, ClassLoader> classloaders) {
     // instantiate plugins
-    Map<String, SonarPlugin> instancesByPluginKey = new HashMap<>();
+    Map<String, Plugin> instancesByPluginKey = new HashMap<>();
     for (Map.Entry<PluginClassLoaderDef, ClassLoader> entry : classloaders.entrySet()) {
       PluginClassLoaderDef def = entry.getKey();
       ClassLoader classLoader = entry.getValue();
@@ -138,7 +138,7 @@ public class PluginLoader {
         String pluginKey = mainClassEntry.getKey();
         String mainClass = mainClassEntry.getValue();
         try {
-          instancesByPluginKey.put(pluginKey, (SonarPlugin) classLoader.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);
@@ -151,8 +151,8 @@ public class PluginLoader {
     return instancesByPluginKey;
   }
 
-  public void unload(Collection<SonarPlugin> plugins) {
-    for (SonarPlugin plugin : plugins) {
+  public void unload(Collection<Plugin> plugins) {
+    for (Plugin plugin : plugins) {
       ClassLoader classLoader = plugin.getClass().getClassLoader();
       if (classLoader instanceof Closeable && classLoader != classloaderFactory.baseClassLoader()) {
         try {
index e71e5e58bf19f962efa4e66cf7a3a74d5db86f4d..9f302ac81f78d19e2020fc667decb2a40a4f228b 100644 (file)
@@ -20,7 +20,7 @@
 package org.sonar.core.platform;
 
 import java.util.Collection;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.Plugin;
 import org.sonar.api.batch.BatchSide;
 import org.sonar.api.server.ServerSide;
 
@@ -36,9 +36,9 @@ public interface PluginRepository {
   PluginInfo getPluginInfo(String key);
 
   /**
-   * @return the instance of {@link SonarPlugin} for the given plugin key. Never return null.
+   * @return the instance of {@link Plugin} for the given plugin key. Never return null.
    */
-  SonarPlugin getPluginInstance(String key);
+  Plugin getPluginInstance(String key);
 
   boolean hasPlugin(String key);
 }
index e2912f3ff2079e4618772e85bff96d31459b8739..0bdef80d769d27f4c2a0966d04ff7accf444123d 100644 (file)
@@ -30,6 +30,7 @@ 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.updatecenter.common.Version;
 
@@ -51,7 +52,7 @@ public class PluginLoaderTest {
     PluginClassLoaderDef def = new PluginClassLoaderDef("fake");
     def.addMainClass("fake", FakePlugin.class.getName());
 
-    Map<String, SonarPlugin> instances = loader.instantiatePluginClasses(ImmutableMap.of(def, getClass().getClassLoader()));
+    Map<String, Plugin> instances = loader.instantiatePluginClasses(ImmutableMap.of(def, getClass().getClassLoader()));
     assertThat(instances).containsOnlyKeys("fake");
     assertThat(instances.get("fake")).isInstanceOf(FakePlugin.class);
   }
@@ -168,7 +169,7 @@ public class PluginLoaderTest {
   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");
+      .setBasePlugin("foo");
 
     Collection<PluginClassLoaderDef> defs = loader.defineClassloaders(ImmutableMap.of("foo", foo, "views", views));
 
@@ -178,9 +179,9 @@ public class PluginLoaderTest {
   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"));
+      .setJarFile(jarFile)
+      .setMainClass("org.foo." + pluginKey + "Plugin")
+      .setMinimalSqVersion(Version.create("5.2"));
   }
 
   /**
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/Plugin.java b/sonar-plugin-api/src/main/java/org/sonar/api/Plugin.java
new file mode 100644 (file)
index 0000000..ac67fe1
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api;
+
+import com.google.common.annotations.Beta;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Entry-point for plugins to inject extensions into SonarQube.
+ * <p>The JAR manifest must declare the name of the implementation class in the property <code>Plugin-Class</code>.
+ * This property is automatically set by sonar-packaging-maven-plugin when building plugin.</p>
+ * <p>Example of implementation:
+ * <pre>
+ * package com.mycompany.sonarqube;
+ * public class MyPlugin implements Plugin {
+ *   {@literal @}Override
+ *   public void define(Context context) {
+ *     context.addExtensions(MySensor.class, MyRules.class);
+ *     if (context.getSonarQubeVersion().isGreaterThanOrEqual(SonarQubeVersion.V5_6)) {
+ *       // Extension which supports only versions 5.6 and greater
+ *       // See org.sonar.api.SonarQubeVersion for more details.
+ *       context.addExtension(MyNewExtension.class);
+ *     }
+ *   }
+ * }
+ * </pre>
+ * </p>
+ * <p>Example of pom.xml:</p>
+ * <pre>
+ * &lt;project&gt;
+ *   ...
+ *   &lt;packaging&gt;sonar-plugin&lt;/packaging&gt;
+ *
+ *   &lt;build&gt;
+ *     &lt;plugins&gt;
+ *       &lt;plugin&gt;
+ *         &lt;groupId&gt;org.sonarsource.sonar-packaging-maven-plugin&lt;/groupId&gt;
+ *         &lt;artifactId&gt;sonar-packaging-maven-plugin&lt;/artifactId&gt;
+ *         &lt;extensions&gt;true&lt;/extensions&gt;
+ *         &lt;configuration&gt;
+ *           &lt;pluginClass&gt;com.mycompany.sonarqube.MyPlugin&lt;/pluginClass&gt;
+ *         &lt;/configuration&gt;
+ *       &lt;/plugin&gt;
+ *     &lt;/plugins&gt;
+ *   &lt;/build&gt;
+ * &lt;/project&gt;
+ * </pre>
+ *
+ * @since 5.5
+ */
+@Beta
+public interface Plugin {
+
+  class Context {
+    private final SonarQubeVersion version;
+    private final List extensions = new ArrayList();
+
+    public Context(SonarQubeVersion version) {
+      this.version = version;
+    }
+
+    public SonarQubeVersion getSonarQubeVersion() {
+      return version;
+    }
+
+    /**
+     * Add an extension as :
+     * <ul>
+     *   <li>a Class that is annotated with {@link org.sonar.api.batch.BatchSide} or {@link org.sonar.api.server.ServerSide}.
+     *   The extension will be instantiated once. Its dependencies are injected through constructor parameters.</li>
+     *   <li>an instance that is annotated with {@link org.sonar.api.batch.BatchSide} or {@link org.sonar.api.server.ServerSide}</li>
+     * </ul>
+     * Only a single component can be registered for a class. It's not allowed for example to register:
+     * <ul>
+     *   <li>two MyExtension.class</li>
+     *   <li>MyExtension.class and new MyExtension()</li>
+     * </ul>
+     */
+    public Context addExtension(Object extension) {
+      requireNonNull(extension);
+      this.extensions.add(extension);
+      return this;
+    }
+
+    /**
+     * @see #addExtension(Object)
+     */
+    public Context addExtensions(Collection extensions) {
+      this.extensions.addAll(extensions);
+      return this;
+    }
+
+    /**
+     * @see #addExtension(Object)
+     */
+    public Context addExtensions(Object first, Object second, Object... others) {
+      addExtension(first);
+      addExtension(second);
+      addExtensions(asList(others));
+      return this;
+    }
+
+    public List getExtensions() {
+      return extensions;
+    }
+  }
+
+  /**
+   * This method is executed at runtime when:
+   * <ul>
+   *   <li>Web Server starts</li>
+   *   <li>Compute Engine starts</li>
+   *   <li>Scanner starts</li>
+   * </ul>
+   */
+  void define(Context context);
+}
index 823f1517b60460df7c5248af27dd99731ee85b73..d9d45225532078edb969e649c2ff3cc213d276fb 100644 (file)
@@ -29,7 +29,7 @@ import java.util.List;
  * 
  * @since 2.8
  */
-public abstract class SonarPlugin {
+public abstract class SonarPlugin implements Plugin {
 
   /**
    * Classes of the implemented extensions.
@@ -44,4 +44,8 @@ public abstract class SonarPlugin {
     return getClass().getSimpleName();
   }
 
+  @Override
+  public void define(Context context) {
+    context.addExtensions(getExtensions());
+  }
 }
index d1544367704e3db53f4ee750d9f26ff301d2dcb2..260df6f97220e463819d3a3862f07615bae6d9a8 100644 (file)
@@ -56,7 +56,7 @@ import static java.lang.String.format;
  * the ws is fully implemented in Java and does not require any Ruby on Rails code.
  * <p/>
  * <p/>
- * The classes implementing this extension point must be declared in {@link org.sonar.api.SonarPlugin#getExtensions()}.
+ * The classes implementing this extension point must be declared by {@link org.sonar.api.Plugin}.
  * <p/>
  * <h3>How to use</h3>
  * <pre>
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/PluginTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/PluginTest.java
new file mode 100644 (file)
index 0000000..204a430
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api;
+
+import java.util.Arrays;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.SonarQubeVersion.V5_5;
+
+public class PluginTest {
+
+  @Test
+  public void test_context() {
+    Plugin.Context context = new Plugin.Context(new SonarQubeVersion(V5_5));
+
+    assertThat(context.getSonarQubeVersion().get()).isEqualTo(V5_5);
+    assertThat(context.getExtensions()).isEmpty();
+
+    context.addExtension("foo");
+    assertThat(context.getExtensions()).containsOnly("foo");
+
+    context.addExtensions(Arrays.asList("bar", "baz"));
+    assertThat(context.getExtensions()).containsOnly("foo", "bar", "baz");
+
+    context.addExtensions("one", "two", "three", "four");
+    assertThat(context.getExtensions()).containsOnly("foo", "bar", "baz", "one", "two", "three", "four");
+  }
+}
index 0a2ec661d7395db5851e01e21aed4ff13dea3970..aa107887ea44aff67c55f79e92b105b12b23a22d 100644 (file)
@@ -43,7 +43,6 @@ import org.sonar.batch.task.Tasks;
 import org.sonar.core.component.DefaultResourceTypes;
 import org.sonar.core.config.CorePropertyDefinitions;
 import org.sonar.core.issue.tracking.Tracker;
-import org.sonar.core.platform.SonarQubeVersionProvider;
 
 public class BatchComponents {
   private BatchComponents() {
@@ -52,7 +51,6 @@ public class BatchComponents {
 
   public static Collection<Object> all(AnalysisMode analysisMode) {
     List<Object> components = Lists.newArrayList(
-      new SonarQubeVersionProvider(),
       DefaultResourceTypes.get(),
 
       // Tasks
index 1c1ee8e1fc49bb8b02eaa185788bde3b6619a15e..b1dd6aa584324ea5ce0a7fd6db9f55745f4b3b9c 100644 (file)
@@ -31,7 +31,7 @@ import java.util.Map;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang.CharUtils;
 import org.apache.commons.lang.StringUtils;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.Plugin;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.api.utils.log.Profiler;
@@ -94,7 +94,7 @@ public class BatchPluginInstaller implements PluginInstaller {
    * @see org.sonar.batch.mediumtest.BatchMediumTester
    */
   @Override
-  public Map<String, SonarPlugin> installLocals() {
+  public Map<String, Plugin> installLocals() {
     return Collections.emptyMap();
   }
 
index 4d0d0c46c2dd7c471269bf9612cc31252a5a6032..6a111cf23c0b24897924732a651ca25d9a82117b 100644 (file)
  */
 package org.sonar.batch.bootstrap;
 
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Maps;
 import java.util.Collection;
 import java.util.Map;
 import org.picocontainer.Startable;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.Plugin;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.core.platform.PluginLoader;
 import org.sonar.core.platform.PluginRepository;
@@ -41,7 +40,7 @@ public class BatchPluginRepository implements PluginRepository, Startable {
   private final PluginInstaller installer;
   private final PluginLoader loader;
 
-  private Map<String, SonarPlugin> pluginInstancesByKeys;
+  private Map<String, Plugin> pluginInstancesByKeys;
   private Map<String, PluginInfo> infosByKeys;
 
   public BatchPluginRepository(PluginInstaller installer, PluginLoader loader) {
@@ -55,7 +54,7 @@ public class BatchPluginRepository implements PluginRepository, Startable {
     pluginInstancesByKeys = Maps.newHashMap(loader.load(infosByKeys));
 
     // this part is only used by tests
-    for (Map.Entry<String, SonarPlugin> entry : installer.installLocals().entrySet()) {
+    for (Map.Entry<String, Plugin> entry : installer.installLocals().entrySet()) {
       String pluginKey = entry.getKey();
       infosByKeys.put(pluginKey, new PluginInfo(pluginKey));
       pluginInstancesByKeys.put(pluginKey, entry.getValue());
@@ -97,8 +96,8 @@ public class BatchPluginRepository implements PluginRepository, Startable {
   }
 
   @Override
-  public SonarPlugin getPluginInstance(String key) {
-    SonarPlugin instance = pluginInstancesByKeys.get(key);
+  public Plugin getPluginInstance(String key) {
+    Plugin instance = pluginInstancesByKeys.get(key);
     Preconditions.checkState(instance != null, String.format("Plugin [%s] does not exist", key));
     return instance;
   }
index ff610139032b3e027b94b0c2fb561a1005cfd0b1..dfab90ea7e8e5dbbbcb9bbbac74b5774d555dbfa 100644 (file)
@@ -22,7 +22,9 @@ package org.sonar.batch.bootstrap;
 import java.util.List;
 import javax.annotation.Nullable;
 import org.sonar.api.ExtensionProvider;
+import org.sonar.api.Plugin;
 import org.sonar.api.SonarPlugin;
+import org.sonar.api.SonarQubeVersion;
 import org.sonar.api.batch.AnalysisMode;
 import org.sonar.core.platform.ComponentContainer;
 import org.sonar.core.platform.PluginInfo;
@@ -30,10 +32,12 @@ import org.sonar.core.platform.PluginRepository;
 
 public class ExtensionInstaller {
 
+  private final SonarQubeVersion sonarQubeVersion;
   private final PluginRepository pluginRepository;
   private final AnalysisMode analysisMode;
 
-  public ExtensionInstaller(PluginRepository pluginRepository, AnalysisMode analysisMode) {
+  public ExtensionInstaller(SonarQubeVersion sonarQubeVersion, PluginRepository pluginRepository, AnalysisMode analysisMode) {
+    this.sonarQubeVersion = sonarQubeVersion;
     this.pluginRepository = pluginRepository;
     this.analysisMode = analysisMode;
   }
@@ -47,8 +51,10 @@ public class ExtensionInstaller {
 
     // plugin extensions
     for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) {
-      SonarPlugin plugin = pluginRepository.getPluginInstance(pluginInfo.getKey());
-      for (Object extension : plugin.getExtensions()) {
+      Plugin plugin = pluginRepository.getPluginInstance(pluginInfo.getKey());
+      Plugin.Context context = new Plugin.Context(sonarQubeVersion);
+      plugin.define(context);
+      for (Object extension : context.getExtensions()) {
         doInstall(container, matcher, pluginInfo, extension);
       }
     }
index 8ac69fd625840b1facee6834e10937f2ee801ed2..ea6e967da42a9d0dd106b3f7e73c82519edfbf1b 100644 (file)
@@ -21,7 +21,7 @@ package org.sonar.batch.bootstrap;
 
 import java.util.List;
 import java.util.Map;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.Plugin;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.UriReader;
 import org.sonar.batch.cache.GlobalPersistentCacheProvider;
@@ -39,6 +39,7 @@ 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.platform.SonarQubeVersionProvider;
 import org.sonar.core.util.DefaultHttpDownloader;
 import org.sonar.core.util.UuidFactoryImpl;
 
@@ -89,6 +90,7 @@ public class GlobalContainer extends ComponentContainer {
       BatchPluginPredicate.class,
       ExtensionInstaller.class,
 
+      new SonarQubeVersionProvider(),
       CachesManager.class,
       GlobalSettings.class,
       new BatchWsClientProvider(),
@@ -113,7 +115,7 @@ public class GlobalContainer extends ComponentContainer {
   private void installPlugins() {
     PluginRepository pluginRepository = getComponentByType(PluginRepository.class);
     for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) {
-      SonarPlugin instance = pluginRepository.getPluginInstance(pluginInfo.getKey());
+      Plugin instance = pluginRepository.getPluginInstance(pluginInfo.getKey());
       addExtension(pluginInfo, instance);
     }
   }
index 64f923bd9a5b7941babb18d15865045bc92ef38b..4525027dbaab037921ce4a65cf66487d55f64dfd 100644 (file)
@@ -20,7 +20,7 @@
 package org.sonar.batch.bootstrap;
 
 import java.util.Map;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.Plugin;
 import org.sonar.api.batch.BatchSide;
 import org.sonar.core.platform.PluginInfo;
 
@@ -38,5 +38,5 @@ public interface PluginInstaller {
    * Used only by tests.
    * @see org.sonar.batch.mediumtest.BatchMediumTester
    */
-  Map<String, SonarPlugin> installLocals();
+  Map<String, Plugin> installLocals();
 }
index 71205c7b08d012d69ef9d326455bb9039bf72bff..561d7271768f625f28195a2a4f83fd6971169c34 100644 (file)
@@ -22,7 +22,7 @@ package org.sonar.batch.mediumtest;
 import java.io.File;
 import java.util.HashMap;
 import java.util.Map;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.Plugin;
 import org.sonar.batch.bootstrap.PluginInstaller;
 import org.sonar.core.platform.PluginInfo;
 
@@ -30,14 +30,14 @@ public class FakePluginInstaller implements PluginInstaller {
   public static final String MEDIUM_TEST_ENABLED = "sonar.mediumTest.enabled";
 
   private final Map<String, PluginInfo> infosByKeys = new HashMap<>();
-  private final Map<String, SonarPlugin> instancesByKeys = new HashMap<>();
+  private final Map<String, Plugin> instancesByKeys = new HashMap<>();
 
   public FakePluginInstaller add(String pluginKey, File jarFile) {
     infosByKeys.put(pluginKey, PluginInfo.create(jarFile));
     return this;
   }
 
-  public FakePluginInstaller add(String pluginKey, SonarPlugin instance) {
+  public FakePluginInstaller add(String pluginKey, Plugin instance) {
     instancesByKeys.put(pluginKey, instance);
     return this;
   }
@@ -48,7 +48,7 @@ public class FakePluginInstaller implements PluginInstaller {
   }
 
   @Override
-  public Map<String, SonarPlugin> installLocals() {
+  public Map<String, Plugin> installLocals() {
     return instancesByKeys;
   }
 }
index 0d0fc068dfe5b3435d2d65755a9d84b69ced4f07..58233e9b2be2b02ec147c6787f77297db793722b 100644 (file)
@@ -21,7 +21,7 @@ package org.sonar.batch.bootstrap;
 
 import com.google.common.collect.ImmutableMap;
 import org.junit.Test;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.Plugin;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.core.platform.PluginLoader;
 
@@ -42,7 +42,7 @@ public class BatchPluginRepositoryTest {
   public void install_and_load_plugins() {
     PluginInfo info = new PluginInfo("squid");
     ImmutableMap<String, PluginInfo> infos = ImmutableMap.of("squid", info);
-    SonarPlugin instance = mock(SonarPlugin.class);
+    Plugin instance = mock(Plugin.class);
     when(loader.load(infos)).thenReturn(ImmutableMap.of("squid", instance));
     when(installer.installRemotes()).thenReturn(infos);
 
@@ -53,7 +53,7 @@ public class BatchPluginRepositoryTest {
     assertThat(underTest.getPluginInstance("squid")).isSameAs(instance);
 
     underTest.stop();
-    verify(loader).unload(anyCollectionOf(SonarPlugin.class));
+    verify(loader).unload(anyCollectionOf(Plugin.class));
   }
 
   @Test
index c368fb146b6e0c177103cb794140b9730c1a0f10..6b09a5d24f70f003b177de20e6b25dce801849a9 100644 (file)
@@ -27,6 +27,7 @@ import org.junit.Test;
 import org.sonar.api.BatchExtension;
 import org.sonar.api.ExtensionProvider;
 import org.sonar.api.SonarPlugin;
+import org.sonar.api.SonarQubeVersion;
 import org.sonar.api.batch.AnalysisMode;
 import org.sonar.core.platform.ComponentContainer;
 import org.sonar.core.platform.PluginInfo;
@@ -59,7 +60,7 @@ public class ExtensionInstallerTest {
     when(pluginRepository.getPluginInstance("foo")).thenReturn(newPluginInstance(Foo.class, Bar.class));
 
     ComponentContainer container = new ComponentContainer();
-    ExtensionInstaller installer = new ExtensionInstaller(pluginRepository, mock(AnalysisMode.class));
+    ExtensionInstaller installer = new ExtensionInstaller(mock(SonarQubeVersion.class), pluginRepository, mock(AnalysisMode.class));
     installer.install(container, new FooMatcher());
 
     assertThat(container.getComponentByType(Foo.class)).isNotNull();
@@ -71,7 +72,7 @@ public class ExtensionInstallerTest {
     when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(new PluginInfo("foo")));
     when(pluginRepository.getPluginInstance("foo")).thenReturn(newPluginInstance(new FooProvider(), new BarProvider()));
     ComponentContainer container = new ComponentContainer();
-    ExtensionInstaller installer = new ExtensionInstaller(pluginRepository, mock(AnalysisMode.class));
+    ExtensionInstaller installer = new ExtensionInstaller(mock(SonarQubeVersion.class), pluginRepository, mock(AnalysisMode.class));
 
     installer.install(container, new FooMatcher());
 
@@ -84,7 +85,7 @@ public class ExtensionInstallerTest {
     when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(new PluginInfo("foo")));
     when(pluginRepository.getPluginInstance("foo")).thenReturn(newPluginInstance(new FooBarProvider()));
     ComponentContainer container = new ComponentContainer();
-    ExtensionInstaller installer = new ExtensionInstaller(pluginRepository, mock(AnalysisMode.class));
+    ExtensionInstaller installer = new ExtensionInstaller(mock(SonarQubeVersion.class), pluginRepository, mock(AnalysisMode.class));
 
     installer.install(container, new TrueMatcher());
 
index 619977ce156a9fbc70b8acf7d2c637cc942a58e2..f405eea878547c35276296439a4ffd46421dbd4c 100644 (file)
@@ -43,7 +43,8 @@ import org.sonar.batch.rule.RulesLoader;
 import org.sonar.scanner.protocol.input.GlobalRepositories;
 import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue;
 import com.google.common.base.Function;
-
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -58,20 +59,35 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
-
+import javax.annotation.Nullable;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.mutable.MutableBoolean;
 import org.sonar.api.CoreProperties;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.Plugin;
 import org.sonar.api.batch.debt.internal.DefaultDebtModel;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.measures.Metric;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.api.server.rule.RulesDefinition.Repository;
+import org.sonar.api.utils.DateUtils;
 import org.sonar.batch.bootstrapper.Batch;
 import org.sonar.batch.bootstrapper.EnvironmentInformation;
+import org.sonar.batch.bootstrapper.IssueListener;
 import org.sonar.batch.bootstrapper.LogOutput;
 import org.sonar.batch.issue.tracking.ServerLineHashesLoader;
 import org.sonar.batch.report.ReportPublisher;
+import org.sonar.batch.repository.FileData;
 import org.sonar.batch.repository.GlobalRepositoriesLoader;
+import org.sonar.batch.repository.ProjectRepositories;
 import org.sonar.batch.repository.ProjectRepositoriesLoader;
+import org.sonar.batch.repository.QualityProfileLoader;
 import org.sonar.batch.repository.ServerIssuesLoader;
+import org.sonar.batch.rule.ActiveRulesLoader;
+import org.sonar.batch.rule.LoadedActiveRule;
+import org.sonar.batch.rule.RulesLoader;
+import org.sonarqube.ws.QualityProfiles.SearchWsResponse.QualityProfile;
+import org.sonarqube.ws.Rules.ListResponse.Rule;
 
 /**
  * Main utility class for writing batch medium tests.
@@ -151,7 +167,7 @@ public class BatchMediumTester {
       return this;
     }
 
-    public BatchMediumTesterBuilder registerPlugin(String pluginKey, SonarPlugin instance) {
+    public BatchMediumTesterBuilder registerPlugin(String pluginKey, Plugin instance) {
       pluginInstaller.add(pluginKey, instance);
       return this;
     }
@@ -234,7 +250,7 @@ public class BatchMediumTester {
       r.setTemplateRuleKey(templateRuleKey);
       r.setLanguage(languag);
       r.setSeverity(severity);
-      
+
       activeRules.addActiveRule(r);
       return this;
     }