From b5f917337a9373d801f63910214d8ce4be53afdd Mon Sep 17 00:00:00 2001
From: Simon Brandhof
Date: Wed, 16 Mar 2016 21:52:47 +0100
Subject: [PATCH] SONAR-7459 new interface org.sonar.api.Plugin
It allows to check version of SonarQube to filter extensions
---
.../java/com/sonarsource/BatchPlugin.java | 17 ++-
.../debt/DebtModelPluginRepository.java | 3 +-
.../NotificationDispatcherMetadata.java | 2 +-
.../server/platform/RailsAppsDeployer.java | 3 +-
.../plugins/ServerExtensionInstaller.java | 12 +-
.../plugins/ServerPluginRepository.java | 10 +-
.../java/org/sonar/server/ui/JRubyFacade.java | 3 +-
.../org/sonar/core/i18n/I18nClassloader.java | 3 +-
.../org/sonar/core/platform/PluginLoader.java | 18 +--
.../sonar/core/platform/PluginRepository.java | 6 +-
.../sonar/core/platform/PluginLoaderTest.java | 11 +-
.../src/main/java/org/sonar/api/Plugin.java | 139 ++++++++++++++++++
.../main/java/org/sonar/api/SonarPlugin.java | 6 +-
.../org/sonar/api/server/ws/WebService.java | 2 +-
.../test/java/org/sonar/api/PluginTest.java | 46 ++++++
.../batch/bootstrap/BatchComponents.java | 2 -
.../batch/bootstrap/BatchPluginInstaller.java | 4 +-
.../bootstrap/BatchPluginRepository.java | 15 +-
.../batch/bootstrap/ExtensionInstaller.java | 12 +-
.../batch/bootstrap/GlobalContainer.java | 6 +-
.../batch/bootstrap/PluginInstaller.java | 4 +-
.../batch/mediumtest/FakePluginInstaller.java | 8 +-
.../bootstrap/BatchPluginRepositoryTest.java | 6 +-
.../bootstrap/ExtensionInstallerTest.java | 7 +-
.../batch/mediumtest/BatchMediumTester.java | 26 +++-
25 files changed, 297 insertions(+), 74 deletions(-)
create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/Plugin.java
create mode 100644 sonar-plugin-api/src/test/java/org/sonar/api/PluginTest.java
diff --git a/it/it-plugins/batch-plugin/src/main/java/com/sonarsource/BatchPlugin.java b/it/it-plugins/batch-plugin/src/main/java/com/sonarsource/BatchPlugin.java
index 2be444b22a3..5bc5ee622af 100644
--- a/it/it-plugins/batch-plugin/src/main/java/com/sonarsource/BatchPlugin.java
+++ b/it/it-plugins/batch-plugin/src/main/java/com/sonarsource/BatchPlugin.java
@@ -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
+ ));
}
-
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java b/server/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java
index 1ce79e55b0f..fdaffcfed8d 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java
@@ -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);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationDispatcherMetadata.java b/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationDispatcherMetadata.java
index 0e77c0f49ca..9538f2cf600 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationDispatcherMetadata.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationDispatcherMetadata.java
@@ -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.
*
- * 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 {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java b/server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java
index ee56ab6226d..2a5d0a3e7ad 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java
@@ -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) {
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 eb36db275d1..53fe385e2e3 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
@@ -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 {
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 40bcc6b20dc..66729394485 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
@@ -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 pluginInfosByKeys = new HashMap<>();
- private final Map pluginInstancesByKeys = new HashMap<>();
+ private final Map 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));
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
index 8f4e7a0b87c..b1a05e4d43b 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
@@ -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);
diff --git a/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java b/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java
index 56ed5525948..d5da2bb12a8 100644
--- a/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java
+++ b/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java
@@ -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 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());
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 44b90ae7d55..d88d037fa7e 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
@@ -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.
*
- * 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 load(Map infoByKeys) {
+ public Map load(Map infoByKeys) {
Collection defs = defineClassloaders(infoByKeys);
Map 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 instantiatePluginClasses(Map classloaders) {
+ Map instantiatePluginClasses(Map classloaders) {
// instantiate plugins
- Map instancesByPluginKey = new HashMap<>();
+ Map instancesByPluginKey = new HashMap<>();
for (Map.Entry 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 plugins) {
- for (SonarPlugin plugin : plugins) {
+ public void unload(Collection plugins) {
+ for (Plugin plugin : plugins) {
ClassLoader classLoader = plugin.getClass().getClassLoader();
if (classLoader instanceof Closeable && classLoader != classloaderFactory.baseClassLoader()) {
try {
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java
index e71e5e58bf1..9f302ac81f7 100644
--- a/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java
+++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java
@@ -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);
}
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 e2912f3ff20..0bdef80d769 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
@@ -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 instances = loader.instantiatePluginClasses(ImmutableMap.of(def, getClass().getClassLoader()));
+ Map 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 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
index 00000000000..ac67fe1c2bf
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/Plugin.java
@@ -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.
+ *
The JAR manifest must declare the name of the implementation class in the property Plugin-Class.
+ * This property is automatically set by sonar-packaging-maven-plugin when building plugin.
+ *
Example of implementation:
+ *
+ * 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);
+ * }
+ * }
+ * }
+ *
+ *
+ * @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 :
+ *
+ *
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.
+ *
an instance that is annotated with {@link org.sonar.api.batch.BatchSide} or {@link org.sonar.api.server.ServerSide}
+ *
+ * Only a single component can be registered for a class. It's not allowed for example to register:
+ *
+ */
+ void define(Context context);
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/SonarPlugin.java b/sonar-plugin-api/src/main/java/org/sonar/api/SonarPlugin.java
index 823f1517b60..d9d45225532 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/SonarPlugin.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/SonarPlugin.java
@@ -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());
+ }
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java
index d1544367704..260df6f9722 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java
@@ -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.
*
*
- * 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}.
*
*
How to use
*
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
index 00000000000..204a430dde3
--- /dev/null
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/PluginTest.java
@@ -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");
+ }
+}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java
index 0a2ec661d73..aa107887ea4 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java
@@ -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