]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10544 Process external rules
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Tue, 17 Apr 2018 09:53:16 +0000 (11:53 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 26 Apr 2018 18:20:51 +0000 (20:20 +0200)
20 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleForIndexingDto.java
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/AddRuleExternalTest.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewExternalRule.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/Rule.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/RuleImpl.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/TrackerRawInputFactory.java
server/sonar-server/src/main/java/org/sonar/server/rule/ExternalRuleCreator.java
server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleDoc.java
server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleQueryFactory.java
server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/batch/BatchReportReaderRule.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/NewExternalRuleTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/TrackerRawInputFactoryTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistExternalRulesStepTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/ExternalRuleCreatorTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleQueryFactoryTest.java
sonar-plugin-api/src/main/java/org/sonar/api/rules/RuleType.java

index 61b10d7fd4af1348447ba8f451714d6d9ca67a47..2ed5d39306126e0d4f26254e73afe4a4ac6be318 100644 (file)
@@ -62,7 +62,7 @@ public class IssueDao implements Dao {
   }
 
   public void scrollNonClosedByComponentUuidExcludingExternals(DbSession dbSession, String componentUuid, ResultHandler<IssueDto> handler) {
-    mapper(dbSession).scrollNonClosedByComponentUuid(componentUuid, handler);
+    mapper(dbSession).scrollNonClosedByComponentUuidExcludingExternals(componentUuid, handler);
   }
 
   public void scrollNonClosedByModuleOrProjectExcludingExternals(DbSession dbSession, ComponentDto module, ResultHandler<IssueDto> handler) {
index 9e6956976dd896de9d79b61f123191de7c4118cc..3c502d5a10b5b91c2fcd9e7b024afb3d91827006 100644 (file)
@@ -43,6 +43,8 @@ public interface IssueMapper {
   int updateIfBeforeSelectedDate(IssueDto issue);
 
   void scrollNonClosedByComponentUuid(@Param("componentUuid") String componentUuid, ResultHandler<IssueDto> handler);
+  
+  void scrollNonClosedByComponentUuidExcludingExternals(@Param("componentUuid") String componentUuid, ResultHandler<IssueDto> handler);
 
   void scrollNonClosedByModuleOrProject(
     @Param("projectUuid") String projectUuid,
index 0a42c35461367caca00f945e28d4e3c53b412b56..e76a891a819c6aeb668918e9a061cd9b04e607d5 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.db.rule;
 import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableSet;
 import java.util.Set;
+import javax.annotation.CheckForNull;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
 import org.sonar.api.rules.RuleType;
@@ -121,8 +122,9 @@ public class RuleForIndexingDto {
     return updatedAt;
   }
 
+  @CheckForNull
   public RuleType getTypeAsRuleType() {
-    return RuleType.valueOf(type);
+    return RuleType.valueOfNullable(type);
   }
 
   public String getSeverityAsString() {
index 1716b6af4acfa9ea702990a7cf3869495462e5e8..52f3d91d7a6f3f5f72414f828078245b410da7cd 100644 (file)
   </select>
 
   <select id="scrollNonClosedByComponentUuid" parameterType="String" resultType="Issue" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY">
+    select
+    <include refid="issueColumns"/>
+    from issues i
+    inner join rules r on r.id=i.rule_id
+    inner join projects p on p.uuid=i.component_uuid
+    inner join projects root on root.uuid=i.project_uuid
+    where
+    i.component_uuid = #{componentUuid,jdbcType=VARCHAR} and
+    i.status &lt;&gt; 'CLOSED'
+  </select>
+  
+  <select id="scrollNonClosedByComponentUuidExcludingExternals" parameterType="String" resultType="Issue" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY">
     select
     <include refid="issueColumns"/>
     from issues i
index cf44af23344ec733a5bf8e0b46205d8e5c40d4a1..12020f0d5478f563930543aca4e760edf0961804 100644 (file)
  */
 package org.sonar.server.platform.db.migration.version.v72;
 
+import java.sql.SQLException;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.sonar.db.CoreDbTester;
 
-import java.sql.SQLException;
-
 import static java.sql.Types.BOOLEAN;
-import static java.sql.Types.VARCHAR;
 import static org.junit.rules.ExpectedException.none;
 import static org.sonar.db.CoreDbTester.createForSchema;
 
index 90d00df8e14af07da08cd5febdd35155b906d071..2e4c067a20dc04f4c672781ebbd065c29cf158e2 100644 (file)
@@ -22,7 +22,6 @@ package org.sonar.server.computation.task.projectanalysis.issue;
 import java.util.Collections;
 import java.util.Set;
 import javax.annotation.concurrent.Immutable;
-import org.apache.commons.lang.StringUtils;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.RuleStatus;
 import org.sonar.api.rules.RuleType;
@@ -31,22 +30,15 @@ import org.sonar.api.server.debt.DebtRemediationFunction;
 @Immutable
 public class NewExternalRule implements Rule {
   private final RuleKey key;
-  private final String severity;
+  private final String name;
   private final RuleType type;
   private final String pluginKey;
 
   private NewExternalRule(Builder builder) {
     this.key = checkNotNull(builder.key, "key");
-    this.severity = checkNotEmpty(builder.severity, "severity");
     this.type = checkNotNull(builder.type, "type");
     this.pluginKey = builder.pluginKey;
-  }
-
-  private static String checkNotEmpty(String str, String name) {
-    if (StringUtils.isEmpty(str)) {
-      throw new IllegalStateException("'" + name + "' not expected to be empty for an external rule");
-    }
-    return str;
+    this.name = builder.name;
   }
 
   private static <T> T checkNotNull(T obj, String name) {
@@ -56,10 +48,6 @@ public class NewExternalRule implements Rule {
     return obj;
   }
 
-  public String getSeverity() {
-    return severity;
-  }
-
   @Override
   public int getId() {
     return 0;
@@ -72,7 +60,7 @@ public class NewExternalRule implements Rule {
 
   @Override
   public String getName() {
-    return key.toString();
+    return name;
   }
 
   @Override
@@ -110,14 +98,15 @@ public class NewExternalRule implements Rule {
     private String severity;
     private RuleType type;
     private String pluginKey;
+    private String name;
 
     public Builder setKey(RuleKey key) {
       this.key = key;
       return this;
     }
 
-    public Builder setSeverity(String severity) {
-      this.severity = StringUtils.trimToNull(severity);
+    public Builder setName(String name) {
+      this.name = name;
       return this;
     }
 
@@ -134,6 +123,10 @@ public class NewExternalRule implements Rule {
       return type;
     }
 
+    public String name() {
+      return name;
+    }
+
     public NewExternalRule build() {
       return new NewExternalRule(this);
     }
index a3a56a743c0aabaaa00076ac1be6bca1ce57cd68..a417dc95cdfceaa95558baf9f0fe37a311aa5e18 100644 (file)
@@ -36,8 +36,9 @@ public interface Rule {
 
   RuleStatus getStatus();
 
+  @CheckForNull
   RuleType getType();
-  
+
   boolean isExternal();
 
   /**
index 0119b4d640ceeb7b74fd36b5a0ed6f66103f7d22..13e42188b9b941181e39e1db03a3ec8b4a657b44 100644 (file)
@@ -53,7 +53,7 @@ public class RuleImpl implements Rule {
     this.status = dto.getStatus();
     this.tags = union(dto.getSystemTags(), dto.getTags());
     this.remediationFunction = effectiveRemediationFunction(dto);
-    this.type = RuleType.valueOf(dto.getType());
+    this.type = RuleType.valueOfNullable(dto.getType());
     this.pluginKey = dto.getPluginKey();
     this.external = dto.isExternal();
   }
index eb5aa50afcfc23c6ca046df8e1f65655f7d9f6e9..320a7d10708039693a221640d7b115818376c544 100644 (file)
@@ -49,7 +49,7 @@ import static com.google.common.collect.Lists.newArrayList;
 import static org.apache.commons.lang.StringUtils.isNotEmpty;
 
 public class TrackerRawInputFactory {
-
+  private static final long DEFAULT_EXTERNAL_ISSUE_EFFORT = 0l;
   private final TreeRootHolder treeRootHolder;
   private final BatchReportReader reportReader;
   private final SourceLinesRepository sourceLinesRepository;
@@ -167,6 +167,7 @@ public class TrackerRawInputFactory {
           dbLocationsBuilder.addFlow(dbFlowBuilder);
         }
       }
+      issue.setFromExternalRuleEngine(false);
       issue.setLocations(dbLocationsBuilder.build());
       return issue;
     }
@@ -189,9 +190,7 @@ public class TrackerRawInputFactory {
       if (reportIssue.getSeverity() != Severity.UNSET_SEVERITY) {
         issue.setSeverity(reportIssue.getSeverity().name());
       }
-      if (reportIssue.getEffort() != 0) {
-        issue.setEffort(Duration.create(reportIssue.getEffort()));
-      }
+      issue.setEffort(Duration.create(reportIssue.getEffort() != 0 ? reportIssue.getEffort() : DEFAULT_EXTERNAL_ISSUE_EFFORT));
       DbIssues.Locations.Builder dbLocationsBuilder = DbIssues.Locations.newBuilder();
       if (reportIssue.hasTextRange()) {
         dbLocationsBuilder.setTextRange(convertTextRange(reportIssue.getTextRange()));
@@ -205,6 +204,7 @@ public class TrackerRawInputFactory {
           dbLocationsBuilder.addFlow(dbFlowBuilder);
         }
       }
+      issue.setFromExternalRuleEngine(true);
       issue.setLocations(dbLocationsBuilder.build());
       issue.setType(toRuleType(reportIssue.getType()));
 
@@ -215,8 +215,8 @@ public class TrackerRawInputFactory {
     private NewExternalRule toExternalRule(ScannerReport.ExternalIssue reportIssue) {
       NewExternalRule.Builder builder = new NewExternalRule.Builder()
         .setType(toRuleType(reportIssue.getType()))
-        .setKey(RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + reportIssue.getRuleRepository(), reportIssue.getRuleKey()))
-        .setPluginKey(reportIssue.getRuleRepository());
+        .setName(RuleKey.of(reportIssue.getRuleRepository(), reportIssue.getRuleKey()).toString())
+        .setKey(RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + reportIssue.getRuleRepository(), reportIssue.getRuleKey()));
 
       if (reportIssue.getSeverity() != Severity.UNSET_SEVERITY) {
         builder.setSeverity(reportIssue.getSeverity().name());
index 4b8c27d026185bc58f8ff2c4849aaab64d8cbc1d..1f96b689c94d4f17e2f25c9c0d243606a41074f0 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.server.rule;
 
-import org.sonar.api.rule.RuleStatus;
 import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
@@ -56,10 +55,7 @@ public class ExternalRuleCreator {
       .setPluginKey(external.getPluginKey())
       .setIsExternal(external.isExternal())
       .setName(external.getName())
-      .setType(external.getType())
       .setScope(ALL)
-      .setStatus(RuleStatus.READY)
-      .setSeverity(external.getSeverity())
       .setStatus(READY)
       .setCreatedAt(system2.now())
       .setUpdatedAt(system2.now()));
index f413798dc893f8f083d93855700fc848c2a72b74..986ad4e5e211a888a5982a5c69c068535124bd6a 100644 (file)
@@ -190,12 +190,13 @@ public class RuleDoc extends BaseDoc {
     return this;
   }
 
+  @CheckForNull
   public RuleType type() {
-    return RuleType.valueOf(getField(RuleIndexDefinition.FIELD_RULE_TYPE));
+    return RuleType.valueOfNullable(getNullableField(RuleIndexDefinition.FIELD_RULE_TYPE));
   }
 
-  public RuleDoc setType(RuleType ruleType) {
-    setField(RuleIndexDefinition.FIELD_RULE_TYPE, ruleType.name());
+  public RuleDoc setType(@Nullable RuleType ruleType) {
+    setField(RuleIndexDefinition.FIELD_RULE_TYPE, ruleType == null ? null : ruleType.name());
     return this;
   }
 
index 8188955280c6b48a3640e1c7bcbd1698cfb41212..de3c14b118c513dbf57ccd73bbc8952d611ff164 100644 (file)
@@ -72,7 +72,7 @@ public class RuleQueryFactory {
    */
   public RuleQuery createRuleSearchQuery(DbSession dbSession, Request request) {
     RuleQuery query = createRuleQuery(dbSession, request);
-    query.setIsExternal(request.paramAsBoolean(PARAM_IS_EXTERNAL));
+    query.setIsExternal(request.mandatoryParamAsBoolean(PARAM_IS_EXTERNAL));
     return query;
   }
 
index 2c9270c2656b07ac9fc3d25f70255e4637af6cee..ba7039a586fe79a86d605f0dff291d2cd766cdff 100644 (file)
@@ -203,12 +203,16 @@ public class SearchAction implements RulesWsAction {
 
     // Rule-specific search parameters
     defineGenericRuleSearchParameters(action);
-    
+    defineIsExternalParam(action);
+  }
+
+  static void defineIsExternalParam(WebService.NewAction action) {
     action
-    .createParam(PARAM_IS_EXTERNAL)
-    .setDescription("Filter external engine rules")
-    .setBooleanPossibleValues()
-    .setSince("7.2");
+      .createParam(PARAM_IS_EXTERNAL)
+      .setDescription("Filter external engine rules")
+      .setDefaultValue(false)
+      .setBooleanPossibleValues()
+      .setSince("7.2");
   }
 
   public static void defineGenericRuleSearchParameters(WebService.NewAction action) {
index cfeaa8ab89ce023cc0c8d9fbfe892ab11ee59720..5fc58b012cb6a7e9e55505d254a88d74a157bf9f 100644 (file)
@@ -179,8 +179,13 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader {
     return closeableIterator(externalIssues.get(componentRef));
   }
 
-  public BatchReportReaderRule putIssues(int componentRef, List<ScannerReport.Issue> issue) {
-    this.issues.put(componentRef, issue);
+  public BatchReportReaderRule putIssues(int componentRef, List<ScannerReport.Issue> issues) {
+    this.issues.put(componentRef, issues);
+    return this;
+  }
+  
+  public BatchReportReaderRule putExternalIssues(int componentRef, List<ScannerReport.ExternalIssue> externalIssues) {
+    this.externalIssues.put(componentRef, externalIssues);
     return this;
   }
 
index 65b0bd315eccc143865212e06a0aa935ac659c5a..f1ade8f696cd159ae2a3de49c36e3e9fad58ea43 100644 (file)
@@ -35,15 +35,17 @@ public class NewExternalRuleTest {
     NewExternalRule.Builder builder = new NewExternalRule.Builder()
       .setKey(RuleKey.of("repo", "rule"))
       .setPluginKey("repo")
+      .setName("name")
       .setSeverity("MAJOR")
       .setType(RuleType.BUG);
 
     assertThat(builder.severity()).isEqualTo("MAJOR");
     assertThat(builder.type()).isEqualTo(RuleType.BUG);
+    assertThat(builder.name()).isEqualTo("name");
 
     NewExternalRule rule = builder.build();
 
-    assertThat(rule.getName()).isEqualTo("repo:rule");
+    assertThat(rule.getName()).isEqualTo("name");
     assertThat(rule.getPluginKey()).isEqualTo("repo");
     assertThat(rule.getSeverity()).isEqualTo("MAJOR");
     assertThat(rule.getType()).isEqualTo(RuleType.BUG);
index 4bfe7bfd937f21bb88cc411c7b420ea80c9abc56..8b3d48dfc5d3c83f570b90fb44e3251113f8fb36 100644 (file)
@@ -26,6 +26,7 @@ import org.junit.Test;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.Severity;
+import org.sonar.api.utils.Duration;
 import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.issue.tracking.Input;
 import org.sonar.scanner.protocol.Constants;
@@ -62,7 +63,7 @@ public class TrackerRawInputFactoryTest {
 
   @Rule
   public SourceLinesRepositoryRule fileSourceRepository = new SourceLinesRepositoryRule();
-  
+
   @Rule
   public RuleRepositoryRule ruleRepository = new RuleRepositoryRule();
 
@@ -118,6 +119,68 @@ public class TrackerRawInputFactoryTest {
     assertThat(issue.gap()).isEqualTo(3.14);
     assertThat(issue.message()).isEqualTo("the message");
 
+    // fields set by compute engine
+    assertThat(issue.checksum()).isEqualTo(input.getLineHashSequence().getHashForLine(2));
+    assertThat(issue.tags()).isEmpty();
+    assertInitializedIssue(issue);
+    assertThat(issue.debt()).isNull();
+  }
+
+  @Test
+  public void load_external_issues_from_report() {
+    fileSourceRepository.addLines(FILE_REF, "line 1;", "line 2;");
+    ScannerReport.ExternalIssue reportIssue = ScannerReport.ExternalIssue.newBuilder()
+      .setTextRange(TextRange.newBuilder().setStartLine(2).build())
+      .setMsg("the message")
+      .setRuleRepository("eslint")
+      .setRuleKey("S001")
+      .setSeverity(Constants.Severity.BLOCKER)
+      .setEffort(20l)
+      .build();
+    reportReader.putExternalIssues(FILE.getReportAttributes().getRef(), asList(reportIssue));
+    Input<DefaultIssue> input = underTest.create(FILE);
+
+    Collection<DefaultIssue> issues = input.getIssues();
+    assertThat(issues).hasSize(1);
+    DefaultIssue issue = Iterators.getOnlyElement(issues.iterator());
+
+    // fields set by analysis report
+    assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("external_eslint", "S001"));
+    assertThat(issue.severity()).isEqualTo(Severity.BLOCKER);
+    assertThat(issue.line()).isEqualTo(2);
+    assertThat(issue.effort()).isEqualTo(Duration.create(20l));
+    assertThat(issue.message()).isEqualTo("the message");
+
+    // fields set by compute engine
+    assertThat(issue.checksum()).isEqualTo(input.getLineHashSequence().getHashForLine(2));
+    assertThat(issue.tags()).isEmpty();
+    assertInitializedIssue(issue);
+  }
+
+  @Test
+  public void load_external_issues_from_report_with_default_effort() {
+    fileSourceRepository.addLines(FILE_REF, "line 1;", "line 2;");
+    ScannerReport.ExternalIssue reportIssue = ScannerReport.ExternalIssue.newBuilder()
+      .setTextRange(TextRange.newBuilder().setStartLine(2).build())
+      .setMsg("the message")
+      .setRuleRepository("eslint")
+      .setRuleKey("S001")
+      .setSeverity(Constants.Severity.BLOCKER)
+      .build();
+    reportReader.putExternalIssues(FILE.getReportAttributes().getRef(), asList(reportIssue));
+    Input<DefaultIssue> input = underTest.create(FILE);
+
+    Collection<DefaultIssue> issues = input.getIssues();
+    assertThat(issues).hasSize(1);
+    DefaultIssue issue = Iterators.getOnlyElement(issues.iterator());
+
+    // fields set by analysis report
+    assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("external_eslint", "S001"));
+    assertThat(issue.severity()).isEqualTo(Severity.BLOCKER);
+    assertThat(issue.line()).isEqualTo(2);
+    assertThat(issue.effort()).isEqualTo(Duration.create(0l));
+    assertThat(issue.message()).isEqualTo("the message");
+
     // fields set by compute engine
     assertThat(issue.checksum()).isEqualTo(input.getLineHashSequence().getHashForLine(2));
     assertThat(issue.tags()).isEmpty();
@@ -198,6 +261,5 @@ public class TrackerRawInputFactoryTest {
     assertThat(issue.status()).isEqualTo(Issue.STATUS_OPEN);
     assertThat(issue.key()).isNull();
     assertThat(issue.authorLogin()).isNull();
-    assertThat(issue.debt()).isNull();
   }
 }
index de7d3d8396dce758dd7a5558722f3972e26e45d4..f834e616ee307d9ab7f459ed336d3261eb7cb668 100644 (file)
@@ -81,6 +81,7 @@ public class PersistExternalRulesStepTest extends BaseStepTest {
       .setKey(ruleKey)
       .setPluginKey("eslint")
       .setSeverity(BLOCKER)
+      .setName("eslint:no-cond-assign")
       .setType(BUG)
       .build());
 
index cc2f8daca1c7345ab91dd766c12371c2cc1b7c9a..96d4bb8d6336a4f3e7785b36c7d1f559e1aef631 100644 (file)
@@ -30,7 +30,6 @@ import org.sonar.server.es.EsTester;
 import org.sonar.server.rule.index.RuleIndexer;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.api.rule.Severity.BLOCKER;
 import static org.sonar.api.rules.RuleType.BUG;
 
 public class ExternalRuleCreatorTest {
@@ -50,8 +49,8 @@ public class ExternalRuleCreatorTest {
     NewExternalRule externalRule = new NewExternalRule.Builder()
       .setKey(ruleKey)
       .setPluginKey("eslint")
-      .setSeverity(BLOCKER)
       .setType(BUG)
+      .setName("name")
       .build();
 
     Rule rule1 = underTest.persistAndIndex(dbSession, externalRule);
@@ -61,9 +60,8 @@ public class ExternalRuleCreatorTest {
     assertThat(rule1.getId()).isGreaterThan(0);
     assertThat(rule1.getKey()).isEqualTo(ruleKey);
     assertThat(rule1.getPluginKey()).isEqualTo("eslint");
-    assertThat(rule1.getName()).isEqualTo(ruleKey.toString());
-    assertThat(rule1.getType()).isEqualTo(BUG);
-
+    assertThat(rule1.getName()).isEqualTo("name");
+    assertThat(rule1.getType()).isNull();
   }
 
 }
index 62ba7c6f33a0228b6f556146bbcf6f8b05a5aa71..a07e81e4d42af279974ad8e51271f045fbc71fca 100644 (file)
@@ -26,6 +26,7 @@ import org.junit.rules.ExpectedException;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
+import org.sonar.api.server.ws.internal.SimpleGetRequest;
 import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbTester;
@@ -51,14 +52,13 @@ import static org.sonar.api.server.ws.WebService.Param.SORT;
 import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY;
 import static org.sonar.db.qualityprofile.ActiveRuleDto.INHERITED;
 import static org.sonar.db.qualityprofile.ActiveRuleDto.OVERRIDES;
-import static org.sonar.server.rule.ws.SearchAction.defineGenericRuleSearchParameters;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVATION;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_AVAILABLE_SINCE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INHERITANCE;
-import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_IS_TEMPLATE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_IS_EXTERNAL;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_IS_TEMPLATE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_LANGUAGES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ORGANIZATION;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_QPROFILE;
@@ -69,6 +69,7 @@ import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_STATUSES;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TAGS;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TEMPLATE_KEY;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_TYPES;
+import static org.sonar.server.rule.ws.SearchAction.defineGenericRuleSearchParameters;
 
 public class RuleQueryFactoryTest {
 
@@ -116,6 +117,50 @@ public class RuleQueryFactoryTest {
     assertThat(result.getCompareToQProfile()).isNull();
   }
 
+  @Test
+  public void create_rule_search_query() {
+    QProfileDto qualityProfile = dbTester.qualityProfiles().insert(organization);
+    QProfileDto compareToQualityProfile = dbTester.qualityProfiles().insert(organization);
+
+    RuleQuery result = executeRuleSearchQuery(
+      PARAM_RULE_KEY, "ruleKey",
+
+      PARAM_ACTIVATION, "true",
+      PARAM_ACTIVE_SEVERITIES, "MINOR,MAJOR",
+      PARAM_AVAILABLE_SINCE, "2016-01-01",
+      PARAM_INHERITANCE, "INHERITED,OVERRIDES",
+      PARAM_IS_TEMPLATE, "true",
+      PARAM_IS_EXTERNAL, "false",
+      PARAM_LANGUAGES, "java,js",
+      TEXT_QUERY, "S001",
+      PARAM_ORGANIZATION, organization.getKey(),
+      PARAM_QPROFILE, qualityProfile.getKee(),
+      PARAM_COMPARE_TO_PROFILE, compareToQualityProfile.getKee(),
+      PARAM_REPOSITORIES, "pmd,checkstyle",
+      PARAM_SEVERITIES, "MINOR,CRITICAL",
+      PARAM_STATUSES, "DEPRECATED,READY",
+      PARAM_TAGS, "tag1,tag2",
+      PARAM_TEMPLATE_KEY, "architectural",
+      PARAM_TYPES, "CODE_SMELL,BUG",
+
+      SORT, "updatedAt",
+      ASCENDING, "false");
+
+    assertResult(result, qualityProfile, compareToQualityProfile);
+    assertThat(result.isExternal()).isFalse();
+  }
+
+  @Test
+  public void is_external_is_mandatory_for_rule_search_query() {
+    dbTester.qualityProfiles().insert(organization);
+    dbTester.qualityProfiles().insert(organization);
+    Request request = new SimpleGetRequest();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("The 'is_external' parameter is missing");
+    underTest.createRuleSearchQuery(dbTester.getSession(), request);
+  }
+
   @Test
   public void create_query() {
     QProfileDto qualityProfile = dbTester.qualityProfiles().insert(organization);
@@ -128,7 +173,7 @@ public class RuleQueryFactoryTest {
       PARAM_ACTIVE_SEVERITIES, "MINOR,MAJOR",
       PARAM_AVAILABLE_SINCE, "2016-01-01",
       PARAM_INHERITANCE, "INHERITED,OVERRIDES",
-      PARAM_IS_TEMPLATE, "true",      
+      PARAM_IS_TEMPLATE, "true",
       PARAM_IS_EXTERNAL, "true",
       PARAM_LANGUAGES, "java,js",
       TEXT_QUERY, "S001",
@@ -145,6 +190,11 @@ public class RuleQueryFactoryTest {
       SORT, "updatedAt",
       ASCENDING, "false");
 
+    assertResult(result, qualityProfile, compareToQualityProfile);
+    assertThat(result.isExternal()).isNull();
+  }
+
+  private void assertResult(RuleQuery result, QProfileDto qualityProfile, QProfileDto compareToQualityProfile) {
     assertThat(result.getKey()).isEqualTo("ruleKey");
 
     assertThat(result.getActivation()).isTrue();
@@ -165,7 +215,6 @@ public class RuleQueryFactoryTest {
     assertThat(result.getTags()).containsOnly("tag1", "tag2");
     assertThat(result.templateKey()).isEqualTo("architectural");
     assertThat(result.getTypes()).containsOnly(BUG, CODE_SMELL);
-
     assertThat(result.getSortField()).isEqualTo("updatedAt");
   }
 
@@ -250,7 +299,8 @@ public class RuleQueryFactoryTest {
     QProfileDto compareToQualityProfile = dbTester.qualityProfiles().insert(otherOrganization);
 
     expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("The specified quality profile '" + compareToQualityProfile.getKee() + "' is not part of the specified organization '" + organization.getKey() + "'");
+    expectedException
+      .expectMessage("The specified quality profile '" + compareToQualityProfile.getKee() + "' is not part of the specified organization '" + organization.getKey() + "'");
 
     execute(PARAM_QPROFILE, qualityProfile.getKee(),
       PARAM_COMPARE_TO_PROFILE, compareToQualityProfile.getKee(),
@@ -298,6 +348,15 @@ public class RuleQueryFactoryTest {
     return fakeAction.getRuleQuery();
   }
 
+  private RuleQuery executeRuleSearchQuery(String... paramsKeyAndValue) {
+    SimpleGetRequest request = new SimpleGetRequest();
+    for (int i = 0; i < paramsKeyAndValue.length; i += 2) {
+      request.setParam(paramsKeyAndValue[i], paramsKeyAndValue[i + 1]);
+    }
+
+    return underTest.createRuleSearchQuery(dbTester.getSession(), request);
+  }
+
   private class FakeAction implements WsAction {
 
     private final RuleQueryFactory ruleQueryFactory;
index a7bf6eb3eb15ab49dc505be3c4ef32dcaab0b995..fc1bb923540e916e06a787058262b8c091e7aedf 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.api.rules;
 
 import java.util.LinkedHashSet;
 import java.util.Set;
+import javax.annotation.CheckForNull;
 
 import static java.lang.String.format;
 import static java.util.Arrays.stream;
@@ -60,5 +61,19 @@ public enum RuleType {
     }
     throw new IllegalArgumentException(format("Unsupported type value : %d", dbConstant));
   }
+  
+  @CheckForNull
+  public static RuleType valueOfNullable(int dbConstant) {
+    // iterating the array is fast-enough as size is small. No need for a map.
+    for (RuleType type : values()) {
+      if (type.getDbConstant() == dbConstant) {
+        return type;
+      }
+    }
+    if (dbConstant == 0) {
+      return null;
+    }
+    throw new IllegalArgumentException(format("Unsupported type value : %d", dbConstant));
+  }
 
 }