001/*
002 * SonarQube
003 * Copyright (C) 2009-2016 SonarSource SA
004 * mailto:contact AT sonarsource DOT com
005 *
006 * This program is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019 */
020package org.sonar.api;
021
022import com.google.common.annotations.Beta;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.List;
026import org.sonar.api.utils.Version;
027
028import static java.util.Arrays.asList;
029import static java.util.Objects.requireNonNull;
030
031/**
032 * Entry-point for plugins to inject extensions into SonarQube.
033 * <p>The JAR manifest must declare the name of the implementation class in the property <code>Plugin-Class</code>.
034 * This property is automatically set by sonar-packaging-maven-plugin when building plugin.
035 * <p>Example of implementation
036 * <pre>
037 * package com.mycompany.sonarqube;
038 * public class MyPlugin implements Plugin {
039 *  {@literal @}Override
040 *   public void define(Context context) {
041 *     context.addExtensions(MySensor.class, MyRules.class);
042 *     if (context.getSonarQubeVersion().isGreaterThanOrEqual(SonarQubeVersion.V5_6)) {
043 *       // Extension which supports only versions 5.6 and greater
044 *       // See org.sonar.api.SonarQubeVersion for more details.
045 *       context.addExtension(MyNewExtension.class);
046 *     }
047 *   }
048 * }
049 * </pre>
050 *
051 * <p>Example of pom.xml
052 * <pre>
053 * &lt;project&gt;
054 *   ...
055 *   &lt;packaging&gt;sonar-plugin&lt;/packaging&gt;
056 *
057 *   &lt;build&gt;
058 *     &lt;plugins&gt;
059 *       &lt;plugin&gt;
060 *         &lt;groupId&gt;org.sonarsource.sonar-packaging-maven-plugin&lt;/groupId&gt;
061 *         &lt;artifactId&gt;sonar-packaging-maven-plugin&lt;/artifactId&gt;
062 *         &lt;extensions&gt;true&lt;/extensions&gt;
063 *         &lt;configuration&gt;
064 *           &lt;pluginClass&gt;com.mycompany.sonarqube.MyPlugin&lt;/pluginClass&gt;
065 *         &lt;/configuration&gt;
066 *       &lt;/plugin&gt;
067 *     &lt;/plugins&gt;
068 *   &lt;/build&gt;
069 * &lt;/project&gt;
070 * </pre>
071 *
072 * <p>Example of test
073 * <pre>
074 * MyPlugin underTest = new MyPlugin();
075 *
076 *{@literal @}Test
077 * public void test_plugin_extensions_compatible_with_5_5() {
078 *   Plugin.Context context = new Plugin.Context(SonarQubeVersion.V5_5);
079 *   underTest.define(context);
080 *   assertThat(context.getExtensions()).hasSize(4);
081 * }
082 * </pre>
083 *
084 * @since 5.5
085 */
086@Beta
087public interface Plugin {
088
089  class Context {
090    private final Version version;
091    private final List extensions = new ArrayList();
092
093    public Context(Version version) {
094      this.version = version;
095    }
096
097    /**
098     * Runtime version of SonarQube
099     */
100    public Version getSonarQubeVersion() {
101      return version;
102    }
103
104    /**
105     * Add an extension as :
106     * <ul>
107     *   <li>a Class that is annotated with {@link org.sonar.api.batch.BatchSide}, {@link org.sonar.api.server.ServerSide}
108     *   or {@link org.sonar.api.ce.ComputeEngineSide}. The extension will be instantiated once. Its dependencies are
109     *   injected through constructor parameters.</li>
110     *   <li>an instance that is annotated with {@link org.sonar.api.batch.BatchSide}, {@link org.sonar.api.server.ServerSide}
111     *   or {@link org.sonar.api.ce.ComputeEngineSide}.</li>
112     * </ul>
113     * Only a single component can be registered for a class. It's not allowed for example to register:
114     * <ul>
115     *   <li>two MyExtension.class</li>
116     *   <li>MyExtension.class and new MyExtension()</li>
117     * </ul>
118     */
119    public Context addExtension(Object extension) {
120      requireNonNull(extension);
121      this.extensions.add(extension);
122      return this;
123    }
124
125    /**
126     * @see #addExtension(Object)
127     */
128    public Context addExtensions(Collection extensions) {
129      this.extensions.addAll(extensions);
130      return this;
131    }
132
133    /**
134     * @see #addExtension(Object)
135     */
136    public Context addExtensions(Object first, Object second, Object... others) {
137      addExtension(first);
138      addExtension(second);
139      addExtensions(asList(others));
140      return this;
141    }
142
143    public List getExtensions() {
144      return extensions;
145    }
146  }
147
148  /**
149   * This method is executed at runtime when:
150   * <ul>
151   *   <li>Web Server starts</li>
152   *   <li>Compute Engine starts</li>
153   *   <li>Scanner starts</li>
154   * </ul>
155   */
156  void define(Context context);
157}