Coding Custom SonarQube Plugin

- Gaurav
Don't be shellfish...Google+FacebookTwitterLinkedInRedditDiggtumblrPinterest

sonarqubeThis will be a continuation of the sonar series in code quality section. This article is for users who have been using sonar and wish to extend and explore the possibilities of customization.

Go through the previous posts if you already haven’t:

  1. Setting up sonar
  2. Sonar analysis using eclipse plugin

If you already use sonar, you are already aware of the plugins and web service sonar offers. There are more than 30 useful plugins out there you can leverage from. But, mostly it boils down to customization. Those who use sonar will know that after setting up sonar comes a stage where the sonar is customized and personalized for code analysis. So, today I dealt with higher levels of customization of sonar. And yes it is damn easy, just like installing and using sonar.

Lets Create A Plugin, A Custom SonarQube Plugin

Step 1:

Create yourself a simple maven project. Get the pom going with the following dependency -

<dependency>
    <groupId>org.codehaus.sonar</groupId>
    <artifactId>sonar-plugin-api</artifactId>
    <version>3.7.2</version>
</dependency>

Step 2:

The packaging of the pom must be

<packaging>sonar-plugin</packaging>

Step 3:

Add the following plugins in the build tag.

<plugins>
    <plugin>
        <groupId>org.codehaus.sonar</groupId>
        <artifactId>sonar-packaging-maven-plugin</artifactId>
        <version>1.7</version>
        <extensions>true</extensions>
        <configuration>
            <pluginClass>com.kodelog.pluginclass.PluginClass</pluginClass>
        </configuration>
    </plugin>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
            <source>1.5</source>
            <target>1.5</target>
            <encoding>UTF-8</encoding>
        </configuration>
    </plugin>
</plugins>

And yes, use the maven compiler plugin to generate code for 1.5 as sonar is built in 1.5.

 

Step 4:

It is important to understand that a plugin is nothing but a set of extensions. So, each plugin has to have extension points. Extension points are basically classes which extend Sensor and Decorator classes of sonar plugin api. Sensors and Decorators will be discussed in later steps. An extension to the SonarPlugin must be is the entry point of your plugin for sonar. In the getExtensions method, one should include all the decorators and sensors the plugin wished to use. Here is an example of a plugin class.

public final class PluginClass extends SonarPlugin {
    public List getExtensions() {
        return ImmutableList.of(Sensor1.class,
                Decorator1.class);
    }
}

 

Step 5:

Before moving on to coding a plugin for ourselves, lets get accustomed to the terms ‘Metrics’, ‘Measures’ and ‘Resources’.

A) Resources are generally anything that can be analysed. The resources are categorized under – project, directory, file, program and block.
B) Metrics are the rules against which the code is accessed. e.g. Considering to use a logger instead of using System.out. Each violation of the rule results in an issue.
C) Measures are literally filtered statistics which might be used in decision making. e.g. selecting all the projects where cyclomatic complexity is greater than 12.

Next step would be to write your sensors and decorators.
D) Sensors are executed before the analysis is performed, this is where one can add metrics to the analysis or suppress them.
E) Decorators on the other hand come into picture once the analysis is over. Decorators are more suitable for generating and consolidating higher level measures. The decorators are run for each resource bottom up (from blocks, files … project). This is where one can access results of the analysis and also access the database.

A sample sensor looks like -

public class ExampleSensor implements Sensor {
    private static final Logger LOG = LoggerFactory.getLogger(ExampleSensor.class);
    private Settings settings;

    /**
     * Use of IoC to get Settings
     */
    public ExampleSensor(Settings settings) {
        this.settings = settings;
    }

    public boolean shouldExecuteOnProject(Project project) {
        // This sensor is executed on any type of projects
        return true;
    }

    public void analyse(Project project, SensorContext sensorContext) {
        String value = settings.getString(ExamplePlugin.MY_PROPERTY);
        LOG.info(ExamplePlugin.MY_PROPERTY + "=" + value);
        Measure measure = new Measure(ExampleMetrics.MESSAGE, value);
        sensorContext.saveMeasure(measure);
    }

    @Override
    public String toString() {
        return getClass().getSimpleName();
    }
}

A sample decorator looks like -

public class RandomDecorator implements Decorator {
    public boolean shouldExecuteOnProject(Project project) {
        // Execute only on Java projects
        return StringUtils.equals(project.getLanguageKey(), Java.KEY);
    }

    public void decorate(Resource resource, DecoratorContext context) {
        // This method is executed on the whole tree of resources.
        // Bottom-up navigation : Java methods -> Java classes -> files -> packages -> modules -> project
        if (Scopes.isBlockUnit(resource)) {
            // Sonar API includes many libraries like commons-lang and google-collections
            double value = RandomUtils.nextDouble();
            // Add a measure to the current Java method
            context.saveMeasure(ExampleMetrics.RANDOM, value);
        } else {
            // we sum random values on resources different than method
            context.saveMeasure(ExampleMetrics.RANDOM, MeasureUtils.sum(true, context.getChildrenMeasures(ExampleMetrics.RANDOM)));
        }
    }

    @Override
    public String toString() {
        return getClass().getSimpleName();
    }
}

An important thing to note here would be that all the resources you require can be easily injected using a public constructor in your sensors and decorators, just as you see in the examples.
e.g.
i) DatabaseSession which provides you with the entityManager and you are free to query your database in any manner you want.
ii) Settings which provides you with the global server side settings.
iii) EmailSettings for reusing the email settings.

Caution: You’ll find all such resources and service classes implementing BatchComponent or ServerComponent. The classes implementing server components are not yet available for injections. Only classes implementing BatchComponent can be used for injections. Also, not all classes extending BatchComponent are injectible, pay attention to the java docs to save you some trouble before injecting a resource.

Refer to the sample sonar plugin visit – Sample.

Step 6:

Once you’ve coded your plugin, just compile it. You’ll end up with a jar in your target folder. Copy this jar to the SONARQUBE_HOME/extensions/plugins folder ans restart sonar server. Observe the logs for failures.

Voila!! You’ve created yourself your first sonarqube plugin.

Don't be shellfish...Google+FacebookTwitterLinkedInRedditDiggtumblrPinterest