Статьи

Еще один плагин фильтрации ресурсов для Sonar

Создать собственный плагин для платформы Sonar очень просто. Если вас не устраивают несколько встроенных плагинов или вам нужно что-то особенное, вы можете легко создать и использовать свой собственный.

В моем текущем проекте мы используем платформу качества кода Sonar уже два года. Это здорово, когда вы хотите обеспечить качество кода вашего проекта. Платформа может визуализировать:

  • проблемы, обнаруженные статическими анализаторами кода,
  • качества кода,
  • покрытие кода и другие умные и полезные вещи.

Если вы не знакомы с ним, продолжайте и попробуйте. Вы можете найти Платформу по адресу:  http://www.sonarsource.org/

Как вы, наверное, знаете, платформа Sonar имеет множество встроенных плагинов, а также имеет четко определенный API, если вы хотите написать свой собственный. API хорошо документирован, его можно найти на этом сайте:  http://www.sonarsource.org/docs/ . Вы можете получить краткое, но очень полезное описание на этом вики-сайте:  http://docs.codehaus.org/display/SONAR/Coding+a+plugin  о том, как разработать плагин.

Мой текущий проект очень сильно использует объекты-значения, сгенерированные JAXB, и мы также создали много пользовательских типов исключений . Я не хочу, чтобы эти сгенерированные компоненты и типы исключений влияли, например, на покрытие кода проекта (наша концепция заключалась в том, что мы не пишем модульные тесты для сгенерированных кодов.). Поэтому мне нужен способ исключить эти ресурсы из процедуры анализа.

Sonar поставляется с встроенным в плагин с именем ExcludedResourceFilter ,   который связан с ресурсами и ресурсами фильтрации на уровне проекта. Я думал, что мог бы использовать это, чтобы исключить упомянутые выше классы, но, к сожалению, у меня есть небольшая проблема с настройкой.

Проблема конфигурации в этом состоит в том, что я не могу установить глобальные — не уровень проекта — шаблоны исключения. Поэтому, если у меня есть два или более проектов Sonar, которые используют один и тот же шаблон исключения, я должен установить эти шаблоны в каждом проекте по одному.

Вдохновленный этим недостатком — и потому что мне лень заполнять эту информацию в каждом проекте 🙂 — я решил создать свой первый плагин Sonar, который фильтрует ресурсы в соответствии с шаблонами, определенными на глобальном уровне и / или уровне проекта. Плагин очень прост, потому что у него есть только одно расширение, которое действительно выполняет фильтрацию ресурсов.

 

Давайте посмотрим, как это реализовать.

 

Согласно руководству разработчика плагинов Sonar, первым шагом является создание класса, который реализует интерфейс плагина . В этом интерфейсе  метод getExtensions является наиболее важным, поскольку он определяет точки расширения.

Мой класс ResourceFilteringPlugin выглядит следующим образом:

1:  package example.sonar.resourcefitering.plugin;  
2:    
3:  import java.util.Arrays;  
4:  import java.util.List;  
5:    
6:  import org.sonar.api.Plugin;  
7:  import org.sonar.api.Properties;  
8:  import org.sonar.api.Property;  
9:    
10:  import example.sonar.resourcefitering.filter.CustomResourceFilter;  
11:    
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:       }  
26:    
27:       /**  
28:        * @deprecated this is not used anymore  
29:        */  
30:       @Deprecated  
31:       public String getName()  
32:       {  
33:            return "Resource Filtering";  
34:       }  
35:    
36:       /**  
37:        * @deprecated this is not used anymore  
38:        */  
39:       @Deprecated  
40:       public String getDescription()  
41:       {  
42:            return "Filters resources based on pattenrs.";  
43:       }  
44:    
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:  }  

Как видите, класс реализует интерфейс Plugin со всеми необходимыми методами. Метод getExtensions возвращает только одну точку расширения, мой класс CustomResourceFilter, который выполняет фильтрацию ресурсов. Еще одна интересная вещь — аннотация Properties в верхней части класса. С помощью этой аннотации вы можете определить параметры конфигурации для вашего плагина. В следующем списке кратко описаны используемые свойства:

  • sonar.resourcefiltering.default.patterns:  Вы можете определить шаблоны исключений по умолчанию (разделенные комой) на панели глобальной конфигурации плагина.
  • sonar.resourcefiltering.replace.project.patterns.with.default.patterns: С помощью этого свойства вы можете указать  плагину игнорировать шаблоны исключения проекта и использовать только стандартные (это отключено по умолчанию.),
  • sonar.resourcefiltering.extend.project.patterns.with.default.patterns: по  крайней мере, этот параметр конфигурации указывает плагину использовать оба шаблона исключения, шаблоны, определенные в глобальном свойстве по умолчанию и на панели конфигурации проекта. (Это включено по умолчанию.)

 

На следующем шаге мы должны реализовать сам класс расширения, который в моем случае выглядит следующим образом:

1:  package example.sonar.resourcefitering.filter;  
2:    
3:  import java.util.ArrayList;  
4:  import java.util.Arrays;  
5:  import java.util.List;  
6:    
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;  
12:    
13:  public class CustomResourceFilter implements ResourceFilter  
14:  {  
15:       public static final String PROPERTY_DEFAULT_PATTERNS = "sonar.resourcefiltering.default.patterns";  
16:    
17:       public static final String PROPERTY_REPLACE_PROJECT_PATTERNS_WITH_DEFAULT_PATTERNS = "sonar.resourcefiltering.replace.project.patterns.with.default.patterns";  
18:    
19:       public static final String PROPERTY_EXTEND_PROJECT_PATTERNS_WITH_DEFAULT_PATTERNS = "sonar.resourcefiltering.extend.project.patterns.with.default.patterns";  
20:    
21:       protected String[] exclusionPatterns;  
22:    
23:       public CustomResourceFilter(final Project project, final Configuration configuration)  
24:       {  
25:            final List<String> exclusionPatterns = new ArrayList<String>();  
26:    
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()));  
34:    
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:            }  
40:    
41:            this.exclusionPatterns = exclusionPatterns.toArray(new String[exclusionPatterns.size()]);  
42:       }  
43:    
44:       @SuppressWarnings("rawtypes")  
45:       public boolean isIgnored(final Resource resource)  
46:       {  
47:            if (ResourceUtils.isUnitTestClass(resource))  
48:            {  
49:                 return false;  
50:            }  
51:    
52:            for (final String pattern : this.exclusionPatterns)  
53:            {  
54:                 if (resource.matchFilePattern(pattern))  
55:                 {  
56:                      return true;  
57:                 }  
58:            }  
59:            return false;  
60:       }  
61:  }  

Класс реализует интерфейс ResourceFilter, который определяет только один метод isIgnored, в котором выполняется фильтрация ресурсов. Логика очень проста: если данный ресурс не является модульным тестом и не соответствует ни одному шаблону фильтрации, он не фильтруется, что означает, что он будет включен, например, в покрытие кода проекта. Я думаю, что конструктор этого класса более интересен. Как видите, это не конструктор класса по умолчанию, потому что он имеет два параметра. Первый параметр — это объект, представляющий текущий проект сонара., С помощью этого объекта вы можете получить доступ к настройкам конфигурации данного проекта. Второй — объект глобальной конфигурации, который можно использовать для запроса настроек глобальной конфигурации плагина. Эти элементы вводятся автоматически с помощью каркаса сонара . Вам не нужно комментировать или делать что-либо, они просто вводятся автоматически. Тело конструктора создает используемые шаблоны исключений на основе заданных объектов и параметров конфигурации.

Итак, мы закончили с плагином и его расширением. Последний шаг — создать jar-файл, содержащий наш плагин, и развернуть его на сервере Sonar . Чтобы создать правильный файл jar, мы можем использовать плагин Sonar maven, как я это сделал. В следующем фрагменте POM показано, как использовать и настраивать плагин maven:

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>  

Если вы выполните команду mvn package, вы получите файл с именем resourcefilter-1.0.0.jar, который можно развернуть на вашем сервере Sonar .

Резюме

Я думаю, что написание плагина Sonar не так сложно, как кажется на первый взгляд. Это может быть очень интересно и полезно. К этому сообщению я приложил весь проект, чтобы вы могли скачать его и попробовать, если хотите. Надеюсь, вам понравился мой пост, и спасибо, что прочитали это краткое описание.