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);
+ *     }
+ *   }
+ * }
+ * 
+ *

+ *

Example of pom.xml:

+ *
+ * <project>
+ *   ...
+ *   <packaging>sonar-plugin</packaging>
+ *
+ *   <build>
+ *     <plugins>
+ *       <plugin>
+ *         <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>
+ *         <artifactId>sonar-packaging-maven-plugin</artifactId>
+ *         <extensions>true</extensions>
+ *         <configuration>
+ *           <pluginClass>com.mycompany.sonarqube.MyPlugin</pluginClass>
+ *         </configuration>
+ *       </plugin>
+ *     </plugins>
+ *   </build>
+ * </project>
+ * 
+ * + * @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 : + * + * Only a single component can be registered for a class. It's not allowed for example to register: + * + */ + 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: + * + */ + 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 all(AnalysisMode analysisMode) {
     List components = Lists.newArrayList(
-      new SonarQubeVersionProvider(),
       DefaultResourceTypes.get(),
 
       // Tasks
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java
index 1c1ee8e1fc4..b1dd6aa5843 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java
@@ -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 installLocals() {
+  public Map installLocals() {
     return Collections.emptyMap();
   }
 
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java
index 4d0d0c46c2d..6a111cf23c0 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java
@@ -19,15 +19,14 @@
  */
 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 pluginInstancesByKeys;
+  private Map pluginInstancesByKeys;
   private Map 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 entry : installer.installLocals().entrySet()) {
+    for (Map.Entry 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;
   }
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java
index ff610139032..dfab90ea7e8 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java
@@ -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);
       }
     }
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java
index 8ac69fd6258..ea6e967da42 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java
@@ -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);
     }
   }
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java
index 64f923bd9a5..4525027dbaa 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java
@@ -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 installLocals();
+  Map installLocals();
 }
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java
index 71205c7b08d..561d7271768 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java
@@ -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 infosByKeys = new HashMap<>();
-  private final Map instancesByKeys = new HashMap<>();
+  private final Map 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 installLocals() {
+  public Map installLocals() {
     return instancesByKeys;
   }
 }
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java
index 0d0fc068dfe..58233e9b2be 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java
@@ -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 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
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java
index c368fb146b6..6b09a5d24f7 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java
@@ -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());
 
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java
index 619977ce156..f405eea8785 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/BatchMediumTester.java
@@ -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;
     }
-- 
2.39.5