Over a million developers have joined DZone.

Yet another Resource filtering plugin for Sonar

DZone's Guide to

Yet another Resource filtering plugin for Sonar

· Java Zone ·
Free Resource

Java-based (JDBC) data connectivity to SaaS, NoSQL, and Big Data. Download Now.

Creating a custom plugin for the Sonar platform is very easy. If you are not satisfied with the several built-in plugins or you need something special you can easily create and use your own.

In my current project we are using the Sonar code quality platform for two years now. It is a great thing when you want to ensure the quality of your project's code. The platform can visualize:

  • the problems found by static code analyzers,
  • code qualities,
  • code coverage and other smart and useful things.

If you are not familiar with it go on and give it a try. You can find the Platform at: http://www.sonarsource.org/

As you may know the Sonar platform has plenty of built-in plugins and it has also a well defined API if you want to write your custom one. The API is well documented which can be found at this site: http://www.sonarsource.org/docs/. You can get a short but very useful description at this wiki site: http://docs.codehaus.org/display/SONAR/Coding+a+plugin about how to develop a plugin.

My current project uses Value Objects generated by JAXB very heavily and we have created many custom Exception types also. I don't want these generated beans and exception types to affect the project's code coverage for example (Our concept was that we don't write unit tests for codes that were generated.). Therefore I need a way to exclude these resources from the analyzing procedure.

The Sonar comes with a built-in plugin named ExcludedResourceFilter  which is related to resources and resource filtering on project level. I thought that I could use this to exclude the classes mentioned above, but unfortunately I have a little configuration problem with it.

The configuration problem with this is that I can't set global - not project level - exclusion patterns. So if I have two or more Sonar projects which use the same exclusion pattern, then I must set these patterns in every project one by one.

Inspired by this shortcoming - and because I'm to lazy to fill these information in every project :) - I have decided to create my first Sonar plugin which filters resources according to patterns defined by global and / or project level. The plugin is very simple because it has just one extension which does the resource filtering indeed.


Let's look how to implement it.


According to the Sonar's plugin developer guide the first step is to create a class which implements the Plugin interface. In this interface the getExtensions method is the most important because this defines the extensions points.

My ResourceFilteringPlugin class looks like the following:

1:  package example.sonar.resourcefitering.plugin;  
3:  import java.util.Arrays;  
4:  import java.util.List;  
6:  import org.sonar.api.Plugin;  
7:  import org.sonar.api.Properties;  
8:  import org.sonar.api.Property;  
10:  import example.sonar.resourcefitering.filter.CustomResourceFilter;  
12:  @Properties({  
13:            @Property(key = CustomResourceFilter.PROPERTY_DEFAULT_PATTERNS, 
                                   name = "Default Patterns", 
                                   description = "These are the default exclusion patterns.", 
                                   defaultValue = "**/bean/*.java,**/*Exception.java", module = false, project = false, global = true),  
14:            @Property(key = CustomResourceFilter.PROPERTY_REPLACE_PROJECT_PATTERNS_WITH_DEFAULT_PATTERNS,
                                   name = "Replace project's exclusion patterns with default patterns.",
                                   description = "When this is enabled the default patterns are used and the project's patterns are ignored.",
                                   defaultValue = "false", module = false, project = false, global = true),  
15:            @Property(key = CustomResourceFilter.PROPERTY_EXTEND_PROJECT_PATTERNS_WITH_DEFAULT_PATTERNS, 
                                   name = "Extend project's exclusion patterns with default patterns.", 
                                   description = "If this property is set to true the project's exclusion patterns are extended with the default patterns.", 
                                   defaultValue = "true", module = false, project = false, global = true) })  
16:  public class ResourceFilteringPlugin implements Plugin  
17:  {  
18:       /**  
19:        * @deprecated this is not used anymore  
20:        */  
21:       @Deprecated  
22:       public String getKey()  
23:       {  
24:            return "resourcefiltering";  
25:       }  
27:       /**  
28:        * @deprecated this is not used anymore  
29:        */  
30:       @Deprecated  
31:       public String getName()  
32:       {  
33:            return "Resource Filtering";  
34:       }  
36:       /**  
37:        * @deprecated this is not used anymore  
38:        */  
39:       @Deprecated  
40:       public String getDescription()  
41:       {  
42:            return "Filters resources based on pattenrs.";  
43:       }  
45:       // This is where you're going to declare all your Sonar extensions  
46:       @SuppressWarnings({ "unchecked", "rawtypes" })  
47:       public List getExtensions()  
48:       {  
49:            return Arrays.asList(CustomResourceFilter.class);  
50:       }  
51:  }  

As you can see the class implements the Plugin interface with all required methods. The getExtensions method returns only one extension point, my CustomResourceFilter class which does the resource filtering. Another interesting thing is the Properties annotation on the top of the class. With this annotation you can define configuration settings for your plugin. The following list shortly describes the used properties:

  • sonar.resourcefiltering.default.patterns: You can define default exclusion patterns (separated by coma) in the plugin's global configuration panel.
  • sonar.resourcefiltering.replace.project.patterns.with.default.patterns: With this property you can instruct the plugin to ignore the project's exclusion patterns and use only the default ones (This is disabled by default.),
  • sonar.resourcefiltering.extend.project.patterns.with.default.patterns: At least, this configuration setting instructs the plugin to use both exclusion patterns, the patterns defined in the global default property and on the project's configuration panel. (This is enabled by default.)


In the next step we must implement the extension class itself which looks like the following in my case:

1:  package example.sonar.resourcefitering.filter;  
3:  import java.util.ArrayList;  
4:  import java.util.Arrays;  
5:  import java.util.List;  
7:  import org.apache.commons.configuration.Configuration;  
8:  import org.sonar.api.batch.ResourceFilter;  
9:  import org.sonar.api.resources.Project;  
10:  import org.sonar.api.resources.Resource;  
11:  import org.sonar.api.resources.ResourceUtils;  
13:  public class CustomResourceFilter implements ResourceFilter  
14:  {  
15:       public static final String PROPERTY_DEFAULT_PATTERNS = "sonar.resourcefiltering.default.patterns";  
17:       public static final String PROPERTY_REPLACE_PROJECT_PATTERNS_WITH_DEFAULT_PATTERNS = "sonar.resourcefiltering.replace.project.patterns.with.default.patterns";  
19:       public static final String PROPERTY_EXTEND_PROJECT_PATTERNS_WITH_DEFAULT_PATTERNS = "sonar.resourcefiltering.extend.project.patterns.with.default.patterns";  
21:       protected String[] exclusionPatterns;  
23:       public CustomResourceFilter(final Project project, final Configuration configuration)  
24:       {  
25:            final List<String> exclusionPatterns = new ArrayList<String>();  
27:            if (configuration.getBoolean(PROPERTY_REPLACE_PROJECT_PATTERNS_WITH_DEFAULT_PATTERNS, false))  
28:            {  
29:                 exclusionPatterns.addAll(Arrays.asList(configuration.getStringArray(PROPERTY_DEFAULT_PATTERNS)));  
30:            }  
31:            else  
32:            {  
33:                 exclusionPatterns.addAll(Arrays.asList(project.getExclusionPatterns()));  
35:                 if (configuration.getBoolean(PROPERTY_EXTEND_PROJECT_PATTERNS_WITH_DEFAULT_PATTERNS, true))  
36:                 {  
37:                      exclusionPatterns.addAll(Arrays.asList(configuration.getStringArray(PROPERTY_DEFAULT_PATTERNS)));  
38:                 }  
39:            }  
41:            this.exclusionPatterns = exclusionPatterns.toArray(new String[exclusionPatterns.size()]);  
42:       }  
44:       @SuppressWarnings("rawtypes")  
45:       public boolean isIgnored(final Resource resource)  
46:       {  
47:            if (ResourceUtils.isUnitTestClass(resource))  
48:            {  
49:                 return false;  
50:            }  
52:            for (final String pattern : this.exclusionPatterns)  
53:            {  
54:                 if (resource.matchFilePattern(pattern))  
55:                 {  
56:                      return true;  
57:                 }  
58:            }  
59:            return false;  
60:       }  
61:  }  

The class implements the ResourceFilter interface which defines only one method called isIgnored where the resource filtering is performed. The logic is very simple, if the given resource is not a unit test and it doesn’t match any filtering pattern then it is not filtered which means it will be included in the project’s code coverage for example. I think the constructor of this class is more interesting. As you can see this is not the class default constructor because it has two parameters. The first parameter is an entity which represents the current Sonar Project. With the help of this object you can reach the configuration settings of the given project. The second one is the global configuration entity that can be used to query the plugin’s global configuration settings. These elements are injected automatically by the Sonar framework. You don’t need to annotate or do anything they are just injected automatically. The constructor’s body creates the used exclusion patterns based on the given objects and configuration settings.

So we are done with the plugin and its extension. The last step is to create a jar which contains our plugin and deploy it into the Sonar server. To create a proper jar file we can use the Sonar‘s maven plugin as I did. The following POM snippet shows you how to use and configure the maven plugin:

1:   <build>  
2:    <plugins>  
3:     <plugin>  
4:      <groupId>org.codehaus.sonar</groupId>  
5:      <artifactId>sonar-packaging-maven-plugin</artifactId>  
6:      <version>1.0</version>  
7:      <extensions>true</extensions>  
8:      <configuration>  
9:       <pluginClass>example.sonar.resourcefitering.plugin.ResourceFilteringPlugin</pluginClass>  
10:       <pluginKey>resourcefiltering</pluginKey>  
11:       <pluginName>Resource Filtering</pluginName>  
12:       <pluginDescription>Filters resources based on patterns.</pluginDescription>  
13:      </configuration>  
14:     </plugin>  
15:     <plugin>  
16:      <groupId>org.apache.maven.plugins</groupId>  
17:      <artifactId>maven-compiler-plugin</artifactId>  
18:      <version>2.0.2</version>  
19:      <configuration>  
20:       <source>1.5</source>  
21:       <target>1.5</target>  
22:       <encoding>UTF-8</encoding>  
23:      </configuration>  
24:     </plugin>  
25:    </plugins>  
26:   </build>  

If you execute the mvn package command you will get a file named resourcefilter-1.0.0.jar which can be deployed into your Sonar server.


I think writing a Sonar plugin is not so hard as it seems for the first look. It can be very interesting and useful. I have attached the whole project to this post so you can download it and try it if you want. I hope you enjoyed my post and thank you for reading this short description.

Connect any Java based application to your SaaS data.  Over 100+ Java-based data source connectors.


Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}