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 java.util.ArrayList;
023import java.util.Collection;
024import java.util.List;
025import org.sonar.api.utils.Version;
026
027import static java.util.Arrays.asList;
028import static java.util.Objects.requireNonNull;
029
030/**
031 * Entry-point for plugins to inject extensions into SonarQube.
032 * <p>The JAR manifest must declare the name of the implementation class in the property <code>Plugin-Class</code>.
033 * This property is automatically set by sonar-packaging-maven-plugin when building plugin.
034 * <p>Example of implementation
035 * <pre>
036  * public class MyPlugin implements Plugin {
037 *  {@literal @}Override
038 *   public void define(Context context) {
039 *     context.addExtensions(MySensor.class, MyRules.class);
040 *     if (context.getSonarQubeVersion().isGreaterThanOrEqual(Version.create(6, 0))) {
041 *       // Extension which supports only versions 6.0 and greater
042 *       // See org.sonar.api.SonarRuntime for more details.
043 *       context.addExtension(MyNewExtension.class);
044 *     }
045 *   }
046 * }
047 * </pre>
048 *
049 * <p>Example of pom.xml
050 * <pre>
051 * &lt;project&gt;
052 *   ...
053 *   &lt;packaging&gt;sonar-plugin&lt;/packaging&gt;
054 *
055 *   &lt;build&gt;
056 *     &lt;plugins&gt;
057 *       &lt;plugin&gt;
058 *         &lt;groupId&gt;org.sonarsource.sonar-packaging-maven-plugin&lt;/groupId&gt;
059 *         &lt;artifactId&gt;sonar-packaging-maven-plugin&lt;/artifactId&gt;
060 *         &lt;extensions&gt;true&lt;/extensions&gt;
061 *         &lt;configuration&gt;
062 *           &lt;pluginClass&gt;com.mycompany.sonarqube.MyPlugin&lt;/pluginClass&gt;
063 *         &lt;/configuration&gt;
064 *       &lt;/plugin&gt;
065 *     &lt;/plugins&gt;
066 *   &lt;/build&gt;
067 * &lt;/project&gt;
068 * </pre>
069 *
070 * <p>Example of test
071 * <pre>
072 * MyPlugin underTest = new MyPlugin();
073 *
074 *{@literal @}Test
075 * public void test_plugin_extensions_compatible_with_5_6() {
076 *   SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(5, 6));
077 *   Plugin.Context context = new Plugin.Context(runtime);
078 *   underTest.define(context);
079 *   assertThat(context.getExtensions()).hasSize(4);
080 * }
081 * </pre>
082 *
083 * @since 5.5
084 */
085public interface Plugin {
086
087  class Context {
088    private final SonarRuntime sonarRuntime;
089    private final List extensions = new ArrayList();
090
091    public Context(SonarRuntime sonarRuntime) {
092      this.sonarRuntime = sonarRuntime;
093    }
094
095    /**
096     * Shortcut on {@code getRuntime().getApiVersion()} since version 6.0.
097     *
098     * @see #getRuntime()
099     * @since 5.5
100     * @return the version of SonarQube API at runtime, not at compilation time
101     */
102    public Version getSonarQubeVersion() {
103      return sonarRuntime.getApiVersion();
104    }
105
106    /**
107     * Runtime environment. Can be use to add some extensions only on some conditions.
108     * @since 6.0
109     */
110    public SonarRuntime getRuntime() {
111      return sonarRuntime;
112    }
113
114    /**
115     * Add an extension as :
116     * <ul>
117     *   <li>a Class that is annotated with {@link org.sonar.api.batch.ScannerSide}, {@link org.sonar.api.server.ServerSide}
118     *   or {@link org.sonar.api.ce.ComputeEngineSide}. The extension will be instantiated once. Its dependencies are
119     *   injected through constructor parameters.</li>
120     *   <li>an instance that is annotated with {@link org.sonar.api.batch.ScannerSide}, {@link org.sonar.api.server.ServerSide}
121     *   or {@link org.sonar.api.ce.ComputeEngineSide}.</li>
122     * </ul>
123     * Only a single component can be registered for a class. It's not allowed for example to register:
124     * <ul>
125     *   <li>two MyExtension.class</li>
126     *   <li>MyExtension.class and new MyExtension()</li>
127     * </ul>
128     */
129    public Context addExtension(Object extension) {
130      requireNonNull(extension);
131      this.extensions.add(extension);
132      return this;
133    }
134
135    /**
136     * @see #addExtension(Object)
137     */
138    public Context addExtensions(Collection extensions) {
139      this.extensions.addAll(extensions);
140      return this;
141    }
142
143    /**
144     * @see #addExtension(Object)
145     */
146    public Context addExtensions(Object first, Object second, Object... others) {
147      addExtension(first);
148      addExtension(second);
149      addExtensions(asList(others));
150      return this;
151    }
152
153    public List getExtensions() {
154      return extensions;
155    }
156
157  }
158
159  /**
160   * This method is executed at runtime when:
161   * <ul>
162   *   <li>Web Server starts</li>
163   *   <li>Compute Engine starts</li>
164   *   <li>Scanner starts</li>
165   * </ul>
166   */
167  void define(Context context);
168}