Skip to end of metadata
Go to start of metadata

Extension Points

SonarQube provides extension points for its three technical stacks:

  • Scanner, which runs the source code analysis
  • Compute Engine, which consolidates the output of scanners, for example by 
    • computing 2nd-level measures such as ratings
    • aggregating measures (for example number of lines of code of project = sum of lines of code of all files)
    • assigning new issues to developers
    • persisting everything in data stores
  • Web application

Extension points are not designed to add new features but to complete existing features. Technically they are contracts defined by a Java interface or an abstract class annotated with @ExtensionPoint. The exhaustive list of extension points is available in the javadoc.

The implementations of extension points (named "extensions") provided by a plugin must be declared in its entry point class, which implements org.sonar.api.Plugin and which is referenced in pom.xml :

ExamplePlugin.java
package org.sonarqube.plugins.example;
import org.sonar.api.Plugin;

public class ExamplePlugin implements Plugin {
  @Override
  public void define(Context context) {
    // implementations of extension points
    context.addExtensions(FooLanguage.class, ExampleProperties.class);
  }
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>
        <artifactId>sonar-packaging-maven-plugin</artifactId>
        <extensions>true</extensions>
        <configuration>
          <pluginClass>org.sonarqube.plugins.example.ExamplePlugin</pluginClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

The entry-point class org.sonar.api.SonarPlugin was deprecated in version 5.6 in favor of org.sonar.api.Plugin. SonarPlugin will be supported until SonarQube version 7.0.

Lifecycle

A plugin extension exists only in its associated technical stacks. A scanner sensor is for example instantiated and executed only in a scanner runtime, but not in the web server nor in Compute Engine. The stack is defined by the annotations @ScannerSide@ServerSide (for web server) and @ComputeEngineSide

An extension can call core components or another extension of the same stack. These dependencies are defined by constructor injection :

@ScannerSide
public class Foo {
  public void call() {}
}

// Sensor is a scanner extension point 
public class MySensor implements Sensor {
  private final Foo foo;
  private final Languages languages;
 
  // Languages is core component which lists all the supported programming languages.
  public MySensor(Foo foo, Languages languages) {    
    this.foo = foo;
    this.languages = languages;
  }
 
  @Override
  public void execute(SensorContext context) {
    System.out.println(this.languages.all());
    foo.call();
  }
}

 
public class ExamplePlugin implements Plugin {
  @Override
  public void define(Context context) {
    // Languages is a core component. It must not be declared by plugins.
    context.addExtensions(Foo.class, MySensor.class);
  }
}

It is recommended not to call other components in constructors. Indeed, they may not be initialized at that time. Constructors should only be used for dependency injection.

Compilation does not fail if incorrect dependencies are defined, such as a scanner extension trying to call a web server extension. Still it will fail at runtime when plugin is loaded.

Third-party Libraries

Plugins are executed in their own isolated classloaders. That allows the packaging and use of 3rd-party libraries without runtime conflicts with core internal libraries or other plugins. Note that since version 5.2, the SonarQube API does not bring transitive dependencies, except SLF4J. The libraries just have to be declared in the pom.xml with default scope "compile":

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project>
  ...
  <dependencies>
    ...
    <dependency>
      <groupId>commons-codec</groupId>
      <artifactId>commons-codec</artifactId>
      <version>1.10</version>
    </dependency>
 </dependencies>
</project>

Technically the libraries are packaged in the directory META-INF/lib of the generated JAR file. An alternative is to shade libraries, for example with maven-shade-plugin. That minimizes the size of the plugin .jar file by copying only the effective used classes.

Hint

The command mvn dependency:tree gives the list of all dependencies, including transitive ones.

Configuration

The core component org.sonar.api.config.Configuration provides access to configuration. It deals with default values and decryption of values. It is available in all stacks (scanner, web server, Compute Engine). As recommended earlier, it must not be called from constructors.

MyExtension.java
public class MyRules implements RulesDefinition {
  private final Configuration config;
 
  public MyRules(Configuration config) {    
    this.config = config;  
  }
 
  @Override
  public void define(Context context) {
    int value = config.getInt("sonar.property").orElse(0);
  }
}

Scanner sensors can get config directly from SensorContext, without using constructor injection :

MySensor.java
public class MySensor extends Sensor {
  @Override
  public void execute(SensorContext context) {
    int value = context.config().getInt("sonar.property").orElse(0);
  }
}

In the scanner stack, properties are checked in the following order, and the first non-blank value is the one that is used:

  1. System property
  2. Scanner command-line (-Dsonar.property=foo for instance)
  3. Scanner tool (<properties> of scanner for Maven for instance) 
  4. Project configuration defined in the web UI 
  5. Global configuration defined in the web UI 
  6. Default value

Plugins can define their own properties so that they can be configured from web administration console. The extension point org.sonar.api.config.PropertyDefinition must be used :

public class ExamplePlugin implements Plugin {
  @Override
  public void define(Context context) {
    context.addExtension(
      PropertyDefinition.builder("sonar.my.property")
       .name("My Property")
       .description("This is the description displayed in web admin console")
	   .defaultValue("42")
       .build()
    );
  }
}

Security

Values of the properties suffixed with ".secured" are not available to non-authorized users (anonymous and users without project or global administration rights). ".secured" is needed for passwords, for instance.

The annotation @org.sonar.api.Property can also be used on an extension to declare a property, but org.sonar.api.config.PropertyDefinition is preferred.

@Properties(
    @Property(key="sonar.my.property", name="My Property", defaultValue="42")
)
public class MySensor implements Sensor {
  // ...
}
 
public class ExamplePlugin implements Plugin {
  @Override
  public void define(Context context) {
    context.addExtension(MySensor.class);
  }
}

Logging

The class org.sonar.api.utils.log.Logger is used to log messages to scanner output, web server logs/sonar.log, or Compute Engine logs (available from administration web console). It's convenient for unit testing (see class LogTester).

import org.sonar.api.utils.log.*;
public class MyClass {
  private static final Logger LOGGER = Loggers.get(MyClass.class);

  public void doSomething() {
    LOGGER.info("foo");
  }
}

Internally SLF4J is used as a facade of various logging frameworks (log4j, commons-log, logback, java.util.logging). That allows all these frameworks to work at runtime, such as when they are required for a 3rd party library. SLF4J loggers can also be used instead of org.sonar.api.utils.log.Logger. Read the SLF4J manual for more details.

As an exception, plugins must not package logging libraries. Dependencies like SLF4J or log4j must be declared with scope "provided".

Exposing APIs to Other Plugins

The common use case is to write a language plugin that will allow some other plugins to contribute additional rules (see for example how it is done in the Java plugin). The main plugin will expose some APIs that will be implemented/used by the "rule" plugins.

Plugins are loaded in isolated classloaders. It means a plugin can't access another plugin's classes. There is an exception for package names following pattern org.sonar.plugins.<pluginKey>.api. For example all classes in a plugin with the key myplugin that are located in org.sonar.plugins.myplugin.api are visible to other plugins.

  • No labels