Browse Source

SONAR-7330 Add rule index definition

tags/5.5-M6
Julien Lancelot 8 years ago
parent
commit
d4087f1c01
38 changed files with 2405 additions and 1473 deletions
  1. 5
    0
      server/sonar-server/src/main/java/org/sonar/server/es/BaseIndex.java
  2. 38
    11
      server/sonar-server/src/main/java/org/sonar/server/es/EsUtils.java
  3. 13
    8
      server/sonar-server/src/main/java/org/sonar/server/es/NewIndex.java
  4. 66
    0
      server/sonar-server/src/main/java/org/sonar/server/es/SearchIdResult.java
  5. 3
    3
      server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
  6. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndexer.java
  7. 2
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
  8. 4
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
  9. 10
    11
      server/sonar-server/src/main/java/org/sonar/server/qualityprofile/index/ActiveRuleIndex.java
  10. 12
    14
      server/sonar-server/src/main/java/org/sonar/server/rule/RuleService.java
  11. 116
    17
      server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleDoc.java
  12. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java
  13. 493
    0
      server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex2.java
  14. 100
    0
      server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndexDefinition.java
  15. 101
    0
      server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndexer.java
  16. 23
    15
      server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleQuery.java
  17. 141
    0
      server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleResultSetIterator.java
  18. 32
    24
      server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java
  19. 3
    3
      server/sonar-server/src/main/java/org/sonar/server/search/IndexSynchronizer.java
  20. 5
    6
      server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java
  21. 24
    1
      server/sonar-server/src/test/java/org/sonar/server/es/NewIndexTest.java
  22. 0
    418
      server/sonar-server/src/test/java/org/sonar/server/rule/RuleBackendMediumTest.java
  23. 50
    0
      server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleDocTesting.java
  24. 732
    0
      server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndex2Test.java
  25. 48
    0
      server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexDefinitionTest.java
  26. 0
    935
      server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java
  27. 109
    0
      server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexerTest.java
  28. 169
    0
      server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleResultSetIteratorTest.java
  29. 1
    1
      server/sonar-server/src/test/resources/org/sonar/server/rule/db/RuleDaoTest/insert-result.xml
  30. 2
    2
      server/sonar-server/src/test/resources/org/sonar/server/rule/db/RuleDaoTest/insert_all-result.xml
  31. 1
    1
      server/sonar-server/src/test/resources/org/sonar/server/rule/db/RuleDaoTest/update-result.xml
  32. 13
    0
      server/sonar-server/src/test/resources/org/sonar/server/rule/index/RuleIndexerTest/index.xml
  33. 13
    0
      server/sonar-server/src/test/resources/org/sonar/server/rule/index/RuleIndexerTest/removed_rule.xml
  34. 25
    0
      server/sonar-server/src/test/resources/org/sonar/server/rule/index/RuleResultSetIteratorTest/custom_rule.xml
  35. 13
    0
      server/sonar-server/src/test/resources/org/sonar/server/rule/index/RuleResultSetIteratorTest/one_rule.xml
  36. 13
    0
      server/sonar-server/src/test/resources/org/sonar/server/rule/index/RuleResultSetIteratorTest/removed_rule.xml
  37. 22
    0
      server/sonar-server/src/test/resources/org/sonar/server/rule/index/RuleResultSetIteratorTest/shared.xml
  38. 1
    1
      sonar-db/src/test/resources/org/sonar/db/version/v55/FeedRulesLongDateColumnsTest/execute.xml

+ 5
- 0
server/sonar-server/src/main/java/org/sonar/server/es/BaseIndex.java View File

@@ -23,6 +23,11 @@ import org.sonar.api.server.ServerSide;

@ServerSide
public abstract class BaseIndex {

public static final String SORT_SUFFIX = "sort";
public static final String SEARCH_WORDS_SUFFIX = "words";
public static final String SEARCH_PARTIAL_SUFFIX = "grams";

private final EsClient client;

public BaseIndex(EsClient client) {

+ 38
- 11
server/sonar-server/src/main/java/org/sonar/server/es/EsUtils.java View File

@@ -21,17 +21,6 @@ package org.sonar.server.es;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import org.elasticsearch.action.search.SearchScrollRequestBuilder;
import org.elasticsearch.common.joda.time.format.ISODateTimeFormat;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.sonar.server.search.BaseDoc;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
@@ -42,6 +31,15 @@ import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Queue;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.elasticsearch.action.search.SearchScrollRequestBuilder;
import org.elasticsearch.common.joda.time.format.ISODateTimeFormat;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.sonar.server.search.BaseDoc;

public class EsUtils {

@@ -121,4 +119,33 @@ public class EsUtils {
}
};
}

public static <ID> Iterator<ID> scrollIds(final EsClient esClient, final String scrollId, final Function<String, ID> idConverter) {
return new Iterator<ID>() {
private final Queue<SearchHit> hits = new ArrayDeque<>();

@Override
public boolean hasNext() {
if (hits.isEmpty()) {
SearchScrollRequestBuilder esRequest = esClient.prepareSearchScroll(scrollId)
.setScroll(TimeValue.timeValueMinutes(SCROLL_TIME_IN_MINUTES));
Collections.addAll(hits, esRequest.get().getHits().getHits());
}
return !hits.isEmpty();
}

@Override
public ID next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return idConverter.apply(hits.poll().getId());
}

@Override
public void remove() {
throw new UnsupportedOperationException("Cannot remove item when scrolling");
}
};
}
}

+ 13
- 8
server/sonar-server/src/main/java/org/sonar/server/es/NewIndex.java View File

@@ -54,7 +54,7 @@ public class NewIndex {
// defaults
attributes.put("dynamic", false);
attributes.put("_all", ImmutableSortedMap.of("enabled", false));
attributes.put("_source", ImmutableSortedMap.of("enabled", true));
attributes.put("properties", properties);
}

@@ -78,6 +78,11 @@ public class NewIndex {
return this;
}

public NewIndexType setEnableSource(boolean enableSource) {
attributes.put("_source", ImmutableSortedMap.of("enabled", enableSource));
return this;
}

public StringFieldBuilder stringFieldBuilder(String fieldName) {
return new StringFieldBuilder(this, fieldName);
}
@@ -250,7 +255,13 @@ public class NewIndex {
public void build() {
validate();
Map<String, Object> hash = new TreeMap<>();
if (!subFields.isEmpty()) {
if (subFields.isEmpty()) {
hash.putAll(ImmutableMap.of(
"type", "string",
"index", disableSearch ? "no" : "not_analyzed",
"omit_norms", "true",
"doc_values", docValues));
} else {
hash.put("type", "multi_field");
Map<String, Object> multiFields = new TreeMap<>(subFields);
multiFields.put(fieldName, ImmutableMap.of(
@@ -259,12 +270,6 @@ public class NewIndex {
"omit_norms", "true",
"doc_values", docValues));
hash.put("fields", multiFields);
} else {
hash.putAll(ImmutableMap.of(
"type", "string",
"index", disableSearch ? "no" : "not_analyzed",
"omit_norms", "true",
"doc_values", docValues));
}

indexType.setProperty(fieldName, hash);

+ 66
- 0
server/sonar-server/src/main/java/org/sonar/server/es/SearchIdResult.java View File

@@ -0,0 +1,66 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.es;

import com.google.common.base.Function;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;

public class SearchIdResult<ID> {

private final List<ID> ids;
private final Facets facets;
private final long total;

public SearchIdResult(SearchResponse response, Function<String, ID> converter) {
this.facets = new Facets(response);
this.total = response.getHits().totalHits();
this.ids = convertToIds(response.getHits(), converter);
}

public List<ID> getIds() {
return ids;
}

public long getTotal() {
return total;
}

public Facets getFacets() {
return this.facets;
}

@Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}

public static <ID> List<ID> convertToIds(SearchHits hits, Function<String, ID> converter) {
List<ID> docs = new ArrayList<>();
for (SearchHit hit : hits.getHits()) {
docs.add(converter.apply(hit.getId()));
}
return docs;
}
}

+ 3
- 3
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java View File

@@ -76,12 +76,12 @@ import org.sonar.server.es.SearchResult;
import org.sonar.server.es.Sorting;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.issue.IssueQuery;
import org.sonarqube.ws.client.issue.IssueFilterParameters;
import org.sonar.server.rule.index.RuleNormalizer;
import org.sonar.server.rule.index.RuleIndexDefinition;
import org.sonar.server.search.IndexDefinition;
import org.sonar.server.search.StickyFacetBuilder;
import org.sonar.server.user.UserSession;
import org.sonar.server.view.index.ViewIndexDefinition;
import org.sonarqube.ws.client.issue.IssueFilterParameters;

import static com.google.common.collect.Lists.newArrayList;

@@ -652,7 +652,7 @@ public class IssueIndex extends BaseIndex {
issueTags.include(String.format(SUBSTRING_MATCH_REGEXP, textQuery));
}
TermsBuilder ruleTags = AggregationBuilders.terms(tagsOnRulesSubAggregation)
.field(RuleNormalizer.RuleField.ALL_TAGS.field())
.field(RuleIndexDefinition.FIELD_RULE_ALL_TAGS)
.size(maxNumberOfTags)
.order(Terms.Order.term(true))
.minDocCount(1L);

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndexer.java View File

@@ -106,7 +106,7 @@ public class IssueIndexer extends BaseIndexer {
bulk.stop();
}

BulkIndexer createBulkIndexer(boolean large) {
private BulkIndexer createBulkIndexer(boolean large) {
BulkIndexer bulk = new BulkIndexer(esClient, IssueIndexDefinition.INDEX);
bulk.setLarge(large);
return bulk;

+ 2
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java View File

@@ -48,6 +48,7 @@ import org.sonar.server.qualityprofile.index.ActiveRuleNormalizer;
import org.sonar.server.ruby.PlatformRackBridge;
import org.sonar.server.rule.db.RuleDao;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleIndex2;
import org.sonar.server.rule.index.RuleNormalizer;
import org.sonar.server.search.EsSearchModule;
import org.sonar.server.search.IndexQueue;
@@ -108,6 +109,7 @@ public class PlatformLevel1 extends PlatformLevel {
ActiveRuleDao.class,

// rules/qprofiles
RuleIndex2.class,
RuleNormalizer.class,
ActiveRuleNormalizer.class,
RuleIndex.class,

+ 4
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java View File

@@ -256,6 +256,8 @@ import org.sonar.server.rule.RuleOperations;
import org.sonar.server.rule.RuleRepositories;
import org.sonar.server.rule.RuleService;
import org.sonar.server.rule.RuleUpdater;
import org.sonar.server.rule.index.RuleIndexDefinition;
import org.sonar.server.rule.index.RuleIndexer;
import org.sonar.server.rule.ws.ActiveRuleCompleter;
import org.sonar.server.rule.ws.RepositoriesAction;
import org.sonar.server.rule.ws.RuleMapper;
@@ -430,6 +432,8 @@ public class PlatformLevel4 extends PlatformLevel {
RubyQProfileActivityService.class,

// rule
RuleIndexDefinition.class,
RuleIndexer.class,
AnnotationRuleParser.class,
XMLRuleParser.class,
DefaultRuleFinder.class,

+ 10
- 11
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/index/ActiveRuleIndex.java View File

@@ -21,6 +21,11 @@ package org.sonar.server.qualityprofile.index;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
@@ -36,19 +41,13 @@ import org.sonar.api.rule.RuleStatus;
import org.sonar.db.qualityprofile.ActiveRuleDto;
import org.sonar.db.qualityprofile.ActiveRuleKey;
import org.sonar.server.qualityprofile.ActiveRule;
import org.sonar.server.rule.index.RuleNormalizer;
import org.sonar.server.rule.index.RuleIndexDefinition;
import org.sonar.server.search.BaseIndex;
import org.sonar.server.search.FacetValue;
import org.sonar.server.search.IndexDefinition;
import org.sonar.server.search.IndexField;
import org.sonar.server.search.SearchClient;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class ActiveRuleIndex extends BaseIndex<ActiveRule, ActiveRuleDto, ActiveRuleKey> {

public static final String COUNT_ACTIVE_RULES = "countActiveRules";
@@ -139,7 +138,7 @@ public class ActiveRuleIndex extends BaseIndex<ActiveRule, ActiveRuleDto, Active
.setQuery(QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(), FilterBuilders.boolFilter()
.must(FilterBuilders.termFilter(ActiveRuleNormalizer.ActiveRuleField.PROFILE_KEY.field(), key))
.mustNot(FilterBuilders.hasParentFilter(this.getParentType(),
FilterBuilders.termFilter(RuleNormalizer.RuleField.STATUS.field(), RuleStatus.REMOVED.name())))))
FilterBuilders.termFilter(RuleIndexDefinition.FIELD_STATUS, RuleStatus.REMOVED.name())))))
.setRouting(key);

SearchResponse response = request.get();
@@ -154,14 +153,14 @@ public class ActiveRuleIndex extends BaseIndex<ActiveRule, ActiveRuleDto, Active
return countByField(ActiveRuleNormalizer.ActiveRuleField.PROFILE_KEY,
FilterBuilders.hasParentFilter(IndexDefinition.RULE.getIndexType(),
FilterBuilders.notFilter(
FilterBuilders.termFilter(RuleNormalizer.RuleField.STATUS.field(), "REMOVED")))).get(key);
FilterBuilders.termFilter(RuleIndexDefinition.FIELD_STATUS, "REMOVED")))).get(key);
}

public Map<String, Long> countAllByQualityProfileKey() {
return countByField(ActiveRuleNormalizer.ActiveRuleField.PROFILE_KEY,
FilterBuilders.hasParentFilter(IndexDefinition.RULE.getIndexType(),
FilterBuilders.notFilter(
FilterBuilders.termFilter(RuleNormalizer.RuleField.STATUS.field(), "REMOVED"))));
FilterBuilders.termFilter(RuleIndexDefinition.FIELD_STATUS, "REMOVED"))));
}

public Multimap<String, FacetValue> getStatsByProfileKey(String key) {
@@ -174,7 +173,7 @@ public class ActiveRuleIndex extends BaseIndex<ActiveRule, ActiveRuleDto, Active
QueryBuilders.termsQuery(ActiveRuleNormalizer.ActiveRuleField.PROFILE_KEY.field(), keys),
FilterBuilders.boolFilter()
.mustNot(FilterBuilders.hasParentFilter(this.getParentType(),
FilterBuilders.termFilter(RuleNormalizer.RuleField.STATUS.field(), RuleStatus.REMOVED.name())))))
FilterBuilders.termFilter(RuleIndexDefinition.FIELD_STATUS, RuleStatus.REMOVED.name())))))
.addAggregation(AggregationBuilders.terms(ActiveRuleNormalizer.ActiveRuleField.PROFILE_KEY.field())
.field(ActiveRuleNormalizer.ActiveRuleField.PROFILE_KEY.field()).size(0)
.subAggregation(AggregationBuilders.terms(ActiveRuleNormalizer.ActiveRuleField.INHERITANCE.field())

+ 12
- 14
server/sonar-server/src/main/java/org/sonar/server/rule/RuleService.java View File

@@ -27,9 +27,8 @@ import javax.annotation.Nullable;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.server.ServerSide;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleNormalizer;
import org.sonar.server.rule.index.RuleIndex2;
import org.sonar.server.rule.index.RuleIndexDefinition;
import org.sonar.server.rule.index.RuleQuery;
import org.sonar.server.search.QueryContext;
import org.sonar.server.search.Result;
@@ -41,13 +40,13 @@ import org.sonar.server.user.UserSession;
@ServerSide
public class RuleService {

private final RuleIndex index;
private final RuleIndex2 index;
private final RuleUpdater ruleUpdater;
private final RuleCreator ruleCreator;
private final RuleDeleter ruleDeleter;
private final UserSession userSession;

public RuleService(RuleIndex index, RuleUpdater ruleUpdater, RuleCreator ruleCreator, RuleDeleter ruleDeleter, UserSession userSession) {
public RuleService(RuleIndex2 index, RuleUpdater ruleUpdater, RuleCreator ruleCreator, RuleDeleter ruleDeleter, UserSession userSession) {
this.index = index;
this.ruleUpdater = ruleUpdater;
this.ruleCreator = ruleCreator;
@@ -56,20 +55,19 @@ public class RuleService {
}

@CheckForNull
@Deprecated
public Rule getByKey(RuleKey key) {
return index.getNullableByKey(key);
throw new UnsupportedOperationException("Please use RuleDao");
}

@Deprecated
public List<Rule> getByKeys(Collection<RuleKey> keys) {
return index.getByKeys(keys);
throw new UnsupportedOperationException("Please use RuleDao");
}

@Deprecated
public Rule getNonNullByKey(RuleKey key) {
Rule rule = index.getNullableByKey(key);
if (rule == null) {
throw new NotFoundException("Rule not found: " + key);
}
return rule;
throw new UnsupportedOperationException("Please use RuleDao");
}

public RuleQuery newRuleQuery() {
@@ -85,7 +83,7 @@ public class RuleService {
*/
public Set<String> listTags() {
/** using combined ALL_TAGS field of ES until ES update that has multiTerms aggregation */
return index.terms(RuleNormalizer.RuleField.ALL_TAGS.field());
return index.terms(RuleIndexDefinition.FIELD_RULE_ALL_TAGS);
}

/**
@@ -93,7 +91,7 @@ public class RuleService {
*/
public Set<String> listTags(@Nullable String query, int size) {
/** using combined ALL_TAGS field of ES until ES update that has multiTerms aggregation */
return index.terms(RuleNormalizer.RuleField.ALL_TAGS.field(), query, size);
return index.terms(RuleIndexDefinition.FIELD_RULE_ALL_TAGS, query, size);
}

public RuleKey create(NewRule newRule) {

+ 116
- 17
server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleDoc.java View File

@@ -19,9 +19,11 @@
*/
package org.sonar.server.rule.index;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -49,11 +51,6 @@ public class RuleDoc extends BaseDoc implements Rule {
super(fields);
}

@Override
public RuleKey key() {
return RuleKey.parse(this.<String>getField(RuleNormalizer.RuleField.KEY.field()));
}

/**
* @deprecated Only use for sqale backward compat. Use key() instead.
*/
@@ -62,34 +59,87 @@ public class RuleDoc extends BaseDoc implements Rule {
return getField(RuleNormalizer.RuleField.ID.field());
}

/**
* Alias for backward-compatibility with SQALE
*/
public RuleKey ruleKey() {
return key();
@Override
public RuleKey key() {
return RuleKey.parse(this.<String>getField(RuleIndexDefinition.FIELD_RULE_KEY));
}

public RuleDoc setKey(@Nullable String s) {
setField(RuleIndexDefinition.FIELD_RULE_KEY, s);
return this;
}

@VisibleForTesting
List<String> keyAsList() {
return (List<String>) getField(RuleIndexDefinition.FIELD_RULE_KEY_AS_LIST);
}

public RuleDoc setKeyAsList(@Nullable List<String> s) {
setField(RuleIndexDefinition.FIELD_RULE_KEY_AS_LIST, s);
return this;
}

@CheckForNull
public String ruleKey() {
return getNullableField(RuleIndexDefinition.FIELD_RULE_RULE_KEY);
}

public RuleDoc setRuleKey(@Nullable String s) {
setField(RuleIndexDefinition.FIELD_RULE_RULE_KEY, s);
return this;
}

@CheckForNull
public String repository() {
return getNullableField(RuleIndexDefinition.FIELD_RULE_REPOSITORY);
}

public RuleDoc setRepository(@Nullable String s) {
setField(RuleIndexDefinition.FIELD_RULE_REPOSITORY, s);
return this;
}

@Override
@CheckForNull
public String internalKey() {
return getNullableField(RuleNormalizer.RuleField.INTERNAL_KEY.field());
return getNullableField(RuleIndexDefinition.FIELD_RULE_INTERNAL_KEY);
}

public RuleDoc setInternalKey(@Nullable String s) {
setField(RuleIndexDefinition.FIELD_RULE_INTERNAL_KEY, s);
return this;
}

@Override
@CheckForNull
public String language() {
return getNullableField(RuleNormalizer.RuleField.LANGUAGE.field());
return getNullableField(RuleIndexDefinition.FIELD_RULE_LANGUAGE);
}

public RuleDoc setLanguage(@Nullable String s) {
setField(RuleIndexDefinition.FIELD_RULE_LANGUAGE, s);
return this;
}

@Override
public String name() {
return getField(RuleNormalizer.RuleField.NAME.field());
return getField(RuleIndexDefinition.FIELD_RULE_NAME);
}

public RuleDoc setName(@Nullable String s) {
setField(RuleIndexDefinition.FIELD_RULE_NAME, s);
return this;
}

@Override
@CheckForNull
public String htmlDescription() {
return getNullableField(RuleNormalizer.RuleField.HTML_DESCRIPTION.field());
return getNullableField(RuleIndexDefinition.FIELD_RULE_HTML_DESCRIPTION);
}

public RuleDoc setHtmlDescription(@Nullable String s) {
setField(RuleIndexDefinition.FIELD_RULE_HTML_DESCRIPTION, s);
return this;
}

@Override
@@ -107,18 +157,33 @@ public class RuleDoc extends BaseDoc implements Rule {
@Override
@CheckForNull
public String severity() {
return (String) getNullableField(RuleNormalizer.RuleField.SEVERITY.field());
return (String) getNullableField(RuleIndexDefinition.FIELD_RULE_SEVERITY);
}

public RuleDoc setSeverity(@Nullable String s) {
setField(RuleIndexDefinition.FIELD_RULE_SEVERITY, s);
return this;
}

@Override
@CheckForNull
public RuleStatus status() {
return RuleStatus.valueOf((String) getField(RuleNormalizer.RuleField.STATUS.field()));
return RuleStatus.valueOf((String) getField(RuleIndexDefinition.FIELD_RULE_STATUS));
}

public RuleDoc setStatus(@Nullable String s) {
setField(RuleIndexDefinition.FIELD_RULE_STATUS, s);
return this;
}

@Override
public boolean isTemplate() {
return (Boolean) getField(RuleNormalizer.RuleField.IS_TEMPLATE.field());
return (Boolean) getField(RuleIndexDefinition.FIELD_RULE_IS_TEMPLATE);
}

public RuleDoc setIsTemplate(@Nullable Boolean b) {
setField(RuleIndexDefinition.FIELD_RULE_IS_TEMPLATE, b);
return this;
}

@Override
@@ -128,6 +193,11 @@ public class RuleDoc extends BaseDoc implements Rule {
return templateKey != null ? RuleKey.parse(templateKey) : null;
}

public RuleDoc setTemplateKey(@Nullable String s) {
setField(RuleIndexDefinition.FIELD_RULE_TEMPLATE_KEY, s);
return this;
}

@Override
public List<String> tags() {
return (List<String>) getField(RuleNormalizer.RuleField.TAGS.field());
@@ -138,6 +208,15 @@ public class RuleDoc extends BaseDoc implements Rule {
return (List<String>) getField(RuleNormalizer.RuleField.SYSTEM_TAGS.field());
}

public Collection<String> allTags() {
return (Collection<String>) getField(RuleIndexDefinition.FIELD_RULE_ALL_TAGS);
}

public RuleDoc setAllTags(@Nullable Collection<String> l) {
setField(RuleIndexDefinition.FIELD_RULE_ALL_TAGS, l);
return this;
}

@Override
public List<RuleParam> params() {
List<RuleParam> params = new ArrayList<>();
@@ -267,11 +346,31 @@ public class RuleDoc extends BaseDoc implements Rule {
return IndexUtils.parseDateTime((String) getNullableField(RuleNormalizer.RuleField.CREATED_AT.field()));
}

@CheckForNull
public Long createdAtAsLong() {
return (Long) getField(RuleIndexDefinition.FIELD_RULE_CREATED_AT);
}

public RuleDoc setCreatedAt(@Nullable Long l) {
setField(RuleIndexDefinition.FIELD_RULE_CREATED_AT, l);
return this;
}

@Override
public Date updatedAt() {
return IndexUtils.parseDateTime((String) getNullableField(RuleNormalizer.RuleField.UPDATED_AT.field()));
}

@CheckForNull
public Long updatedAtAtAsLong() {
return (Long) getField(RuleIndexDefinition.FIELD_RULE_UPDATED_AT);
}

public RuleDoc setUpdatedAt(@Nullable Long l) {
setField(RuleIndexDefinition.FIELD_RULE_UPDATED_AT, l);
return this;
}

@Override
public boolean isManual() {
return getField(RuleNormalizer.RuleField.REPOSITORY.field()).equals(MANUAL_REPOSITORY);

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java View File

@@ -151,7 +151,7 @@ public class RuleIndex extends BaseIndex<Rule, RuleDto, RuleKey> {
/* integrate Query Sort */
String queryText = query.getQueryText();
if (query.getSortField() != null) {
FieldSortBuilder sort = SortBuilders.fieldSort(query.getSortField().sortField());
FieldSortBuilder sort = SortBuilders.fieldSort(query.getSortField());
if (query.isAscendingSort()) {
sort.order(SortOrder.ASC);
} else {

+ 493
- 0
server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex2.java View File

@@ -0,0 +1,493 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.rule.index;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.BoolFilterBuilder;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.FilterBuilder;
import org.elasticsearch.index.query.FilterBuilders;
import org.elasticsearch.index.query.HasParentFilterBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.SimpleQueryStringBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.server.es.BaseIndex;
import org.sonar.server.es.EsClient;
import org.sonar.server.es.SearchIdResult;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.qualityprofile.index.ActiveRuleNormalizer;
import org.sonar.server.search.IndexDefinition;
import org.sonar.server.search.IndexField;
import org.sonar.server.search.StickyFacetBuilder;

import static org.sonar.server.es.EsUtils.SCROLL_TIME_IN_MINUTES;
import static org.sonar.server.es.EsUtils.scrollIds;

/**
* The unique entry-point to interact with Elasticsearch index "rules".
* All the requests are listed here.
*/
public class RuleIndex2 extends BaseIndex {

public static final String FACET_LANGUAGES = "languages";
public static final String FACET_TAGS = "tags";
public static final String FACET_REPOSITORIES = "repositories";
public static final String FACET_SEVERITIES = "severities";
public static final String FACET_ACTIVE_SEVERITIES = "active_severities";
public static final String FACET_STATUSES = "statuses";
public static final String FACET_OLD_DEFAULT = "true";

public static final List<String> ALL_STATUSES_EXCEPT_REMOVED = ImmutableList.copyOf(
Collections2.filter(
Collections2.transform(
Arrays.asList(RuleStatus.values()),
new Function<RuleStatus, String>() {
@Override
public String apply(@Nonnull RuleStatus input) {
return input.toString();
}
}),
new Predicate<String>() {
@Override
public boolean apply(@Nonnull String input) {
return !RuleStatus.REMOVED.toString().equals(input);
}
}));

public RuleIndex2(EsClient client) {
super(client);
}

public SearchIdResult<RuleKey> search(RuleQuery query, SearchOptions options) {
SearchRequestBuilder esSearch = getClient()
.prepareSearch(RuleIndexDefinition.INDEX)
.setTypes(RuleIndexDefinition.TYPE_RULE);

QueryBuilder qb = buildQuery(query);
Map<String, FilterBuilder> filters = buildFilters(query);

if (!options.getFacets().isEmpty()) {
for (AggregationBuilder aggregation : getFacets(query, options, qb, filters).values()) {
esSearch.addAggregation(aggregation);
}
}

setSorting(query, esSearch);
setPagination(options, esSearch);

BoolFilterBuilder fb = FilterBuilders.boolFilter();
for (FilterBuilder filterBuilder : filters.values()) {
fb.must(filterBuilder);
}

esSearch.setQuery(QueryBuilders.filteredQuery(qb, fb));
return new SearchIdResult<>(esSearch.get(), ToRuleKey.INSTANCE);
}

/**
* Return all keys matching the search query, without pagination nor facets
*/
public Iterator<RuleKey> searchAll(RuleQuery query) {
SearchRequestBuilder esSearch = getClient()
.prepareSearch(RuleIndexDefinition.INDEX)
.setTypes(RuleIndexDefinition.TYPE_RULE)
.setSearchType(SearchType.SCAN)
.setScroll(TimeValue.timeValueMinutes(SCROLL_TIME_IN_MINUTES));

QueryBuilder qb = buildQuery(query);
Map<String, FilterBuilder> filters = buildFilters(query);
setSorting(query, esSearch);

BoolFilterBuilder fb = FilterBuilders.boolFilter();
for (FilterBuilder filterBuilder : filters.values()) {
fb.must(filterBuilder);
}

esSearch.setQuery(QueryBuilders.filteredQuery(qb, fb));
SearchResponse response = esSearch.get();
return scrollIds(getClient(), response.getScrollId(), ToRuleKey.INSTANCE);
}

/* Build main query (search based) */
private QueryBuilder buildQuery(RuleQuery query) {

// No contextual query case
String queryText = query.getQueryText();
if (queryText == null || queryText.isEmpty()) {
return QueryBuilders.matchAllQuery();
}

// Build RuleBased contextual query
BoolQueryBuilder qb = QueryBuilders.boolQuery();
String queryString = query.getQueryText();

// Human readable type of querying
qb.should(QueryBuilders.simpleQueryStringQuery(query.getQueryText())
.field(RuleIndexDefinition.FIELD_RULE_NAME + "." + BaseIndex.SEARCH_WORDS_SUFFIX, 20f)
.field(RuleIndexDefinition.FIELD_RULE_HTML_DESCRIPTION + "." + BaseIndex.SEARCH_WORDS_SUFFIX, 3f)
.defaultOperator(SimpleQueryStringBuilder.Operator.AND)
).boost(20f);

// Match and partial Match queries
qb.should(this.termQuery(RuleIndexDefinition.FIELD_RULE_KEY, queryString, 15f));
qb.should(this.termQuery(RuleIndexDefinition.FIELD_RULE_KEY_AS_LIST, queryString, 35f));
qb.should(this.termQuery(RuleIndexDefinition.FIELD_RULE_LANGUAGE, queryString, 3f));
qb.should(this.termQuery(RuleIndexDefinition.FIELD_RULE_ALL_TAGS, queryString, 10f));
qb.should(this.termAnyQuery(RuleIndexDefinition.FIELD_RULE_ALL_TAGS, queryString, 1f));

return qb;
}

private QueryBuilder termQuery(String field, String query, float boost) {
return QueryBuilders.multiMatchQuery(query,
field, field + "." + IndexField.SEARCH_PARTIAL_SUFFIX)
.operator(MatchQueryBuilder.Operator.AND)
.boost(boost);
}

private QueryBuilder termAnyQuery(String field, String query, float boost) {
return QueryBuilders.multiMatchQuery(query,
field, field + "." + IndexField.SEARCH_PARTIAL_SUFFIX)
.operator(MatchQueryBuilder.Operator.OR)
.boost(boost);
}

/* Build main filter (match based) */
private Map<String, FilterBuilder> buildFilters(RuleQuery query) {

Map<String, FilterBuilder> filters = new HashMap<>();

/* Add enforced filter on rules that are REMOVED */
filters.put(RuleIndexDefinition.FIELD_RULE_STATUS,
FilterBuilders.boolFilter().mustNot(
FilterBuilders.termFilter(RuleIndexDefinition.FIELD_RULE_STATUS,
RuleStatus.REMOVED.toString())));

if (!StringUtils.isEmpty(query.getInternalKey())) {
filters.put(RuleIndexDefinition.FIELD_RULE_INTERNAL_KEY,
FilterBuilders.termFilter(RuleIndexDefinition.FIELD_RULE_INTERNAL_KEY, query.getInternalKey()));
}

if (!StringUtils.isEmpty(query.getRuleKey())) {
filters.put(RuleIndexDefinition.FIELD_RULE_RULE_KEY,
FilterBuilders.termFilter(RuleIndexDefinition.FIELD_RULE_RULE_KEY, query.getRuleKey()));
}

if (!CollectionUtils.isEmpty(query.getLanguages())) {
filters.put(RuleIndexDefinition.FIELD_RULE_LANGUAGE,
FilterBuilders.termsFilter(RuleIndexDefinition.FIELD_RULE_LANGUAGE, query.getLanguages()));
}

if (!CollectionUtils.isEmpty(query.getRepositories())) {
filters.put(RuleIndexDefinition.FIELD_RULE_REPOSITORY,
FilterBuilders.termsFilter(RuleIndexDefinition.FIELD_RULE_REPOSITORY, query.getRepositories()));
}

if (!CollectionUtils.isEmpty(query.getSeverities())) {
filters.put(RuleIndexDefinition.FIELD_RULE_SEVERITY,
FilterBuilders.termsFilter(RuleIndexDefinition.FIELD_RULE_SEVERITY, query.getSeverities()));
}

if (!StringUtils.isEmpty(query.getKey())) {
filters.put(RuleIndexDefinition.FIELD_RULE_KEY,
FilterBuilders.termFilter(RuleIndexDefinition.FIELD_RULE_KEY, query.getKey()));
}

if (!CollectionUtils.isEmpty(query.getTags())) {
filters.put(RuleIndexDefinition.FIELD_RULE_ALL_TAGS,
FilterBuilders.termsFilter(RuleIndexDefinition.FIELD_RULE_ALL_TAGS, query.getTags()));
}

if (query.getAvailableSinceLong() != null) {
filters.put("availableSince", FilterBuilders.rangeFilter(RuleIndexDefinition.FIELD_RULE_CREATED_AT)
.gte(query.getAvailableSinceLong()));
}

Collection<RuleStatus> statusValues = query.getStatuses();
if (statusValues != null && !statusValues.isEmpty()) {
Collection<String> stringStatus = new ArrayList<>();
for (RuleStatus status : statusValues) {
stringStatus.add(status.name());
}
filters.put(RuleIndexDefinition.FIELD_RULE_STATUS,
FilterBuilders.termsFilter(RuleIndexDefinition.FIELD_RULE_STATUS, stringStatus));
}

Boolean isTemplate = query.isTemplate();
if (isTemplate != null) {
filters.put(RuleIndexDefinition.FIELD_RULE_IS_TEMPLATE,
FilterBuilders.termFilter(RuleIndexDefinition.FIELD_RULE_IS_TEMPLATE, Boolean.toString(isTemplate)));
}

String template = query.templateKey();
if (template != null) {
filters.put(RuleIndexDefinition.FIELD_RULE_TEMPLATE_KEY,
FilterBuilders.termFilter(RuleIndexDefinition.FIELD_RULE_TEMPLATE_KEY, template));
}

// ActiveRule Filter (profile and inheritance)
BoolFilterBuilder childrenFilter = FilterBuilders.boolFilter();
this.addTermFilter(childrenFilter, ActiveRuleNormalizer.ActiveRuleField.PROFILE_KEY.field(), query.getQProfileKey());
this.addTermFilter(childrenFilter, ActiveRuleNormalizer.ActiveRuleField.INHERITANCE.field(), query.getInheritance());
this.addTermFilter(childrenFilter, ActiveRuleNormalizer.ActiveRuleField.SEVERITY.field(), query.getActiveSeverities());

// ChildQuery
FilterBuilder childQuery;
if (childrenFilter.hasClauses()) {
childQuery = childrenFilter;
} else {
childQuery = FilterBuilders.matchAllFilter();
}

/** Implementation of activation query */
if (Boolean.TRUE.equals(query.getActivation())) {
filters.put("activation",
FilterBuilders.hasChildFilter(IndexDefinition.ACTIVE_RULE.getIndexType(),
childQuery));
} else if (Boolean.FALSE.equals(query.getActivation())) {
filters.put("activation",
FilterBuilders.boolFilter().mustNot(
FilterBuilders.hasChildFilter(IndexDefinition.ACTIVE_RULE.getIndexType(),
childQuery)));
}

return filters;
}

private BoolFilterBuilder addTermFilter(BoolFilterBuilder filter, String field, @Nullable Collection<String> values) {
if (values != null && !values.isEmpty()) {
BoolFilterBuilder valuesFilter = FilterBuilders.boolFilter();
for (String value : values) {
FilterBuilder valueFilter = FilterBuilders.termFilter(field, value);
valuesFilter.should(valueFilter);
}
filter.must(valuesFilter);
}
return filter;
}

private BoolFilterBuilder addTermFilter(BoolFilterBuilder filter, String field, @Nullable String value) {
if (value != null && !value.isEmpty()) {
filter.must(FilterBuilders.termFilter(field, value));
}
return filter;
}

private Map<String, AggregationBuilder> getFacets(RuleQuery query, SearchOptions options, QueryBuilder queryBuilder, Map<String, FilterBuilder> filters) {
Map<String, AggregationBuilder> aggregations = new HashMap<>();
StickyFacetBuilder stickyFacetBuilder = stickyFacetBuilder(queryBuilder, filters);

addDefaultFacets(query, options, aggregations, stickyFacetBuilder);

addStatusFacetIfNeeded(options, aggregations, stickyFacetBuilder);

if (options.getFacets().contains(FACET_SEVERITIES)) {
aggregations.put(FACET_SEVERITIES,
stickyFacetBuilder.buildStickyFacet(RuleIndexDefinition.FIELD_RULE_SEVERITY, FACET_SEVERITIES, Severity.ALL.toArray()));
}

addActiveSeverityFacetIfNeeded(query, options, aggregations, stickyFacetBuilder);
return aggregations;
}

private void addDefaultFacets(RuleQuery query, SearchOptions options, Map<String, AggregationBuilder> aggregations, StickyFacetBuilder stickyFacetBuilder) {
if (options.getFacets().contains(FACET_LANGUAGES) || options.getFacets().contains(FACET_OLD_DEFAULT)) {
Collection<String> languages = query.getLanguages();
aggregations.put(FACET_LANGUAGES,
stickyFacetBuilder.buildStickyFacet(RuleIndexDefinition.FIELD_RULE_LANGUAGE, FACET_LANGUAGES,
languages == null ? new String[0] : languages.toArray()));
}
if (options.getFacets().contains(FACET_TAGS) || options.getFacets().contains(FACET_OLD_DEFAULT)) {
Collection<String> tags = query.getTags();
aggregations.put(FACET_TAGS,
stickyFacetBuilder.buildStickyFacet(RuleIndexDefinition.FIELD_RULE_ALL_TAGS, FACET_TAGS,
tags == null ? new String[0] : tags.toArray()));
}
if (options.getFacets().contains("repositories") || options.getFacets().contains(FACET_OLD_DEFAULT)) {
Collection<String> repositories = query.getRepositories();
aggregations.put(FACET_REPOSITORIES,
stickyFacetBuilder.buildStickyFacet(RuleIndexDefinition.FIELD_RULE_REPOSITORY, FACET_REPOSITORIES,
repositories == null ? new String[0] : repositories.toArray()));
}
}

private void addStatusFacetIfNeeded(SearchOptions options, Map<String, AggregationBuilder> aggregations, StickyFacetBuilder stickyFacetBuilder) {
if (options.getFacets().contains(FACET_STATUSES)) {
BoolFilterBuilder facetFilter = stickyFacetBuilder.getStickyFacetFilter(RuleIndexDefinition.FIELD_RULE_STATUS);
AggregationBuilder statuses = AggregationBuilders.filter(FACET_STATUSES + "_filter")
.filter(facetFilter)
.subAggregation(
AggregationBuilders
.terms(FACET_STATUSES)
.field(RuleIndexDefinition.FIELD_RULE_STATUS)
.include(Joiner.on('|').join(ALL_STATUSES_EXCEPT_REMOVED))
.exclude(RuleStatus.REMOVED.toString())
.size(ALL_STATUSES_EXCEPT_REMOVED.size()));

aggregations.put(FACET_STATUSES, AggregationBuilders.global(FACET_STATUSES).subAggregation(statuses));
}
}

private void addActiveSeverityFacetIfNeeded(RuleQuery query, SearchOptions options, Map<String, AggregationBuilder> aggregations, StickyFacetBuilder stickyFacetBuilder) {
if (options.getFacets().contains(FACET_ACTIVE_SEVERITIES)) {
// We are building a children aggregation on active rules
// so the rule filter has to be used as parent filter for active rules
// from which we remove filters that concern active rules ("activation")
HasParentFilterBuilder ruleFilter = FilterBuilders.hasParentFilter(
IndexDefinition.RULE.getIndexType(),
stickyFacetBuilder.getStickyFacetFilter("activation"));

// Rebuilding the active rule filter without severities
BoolFilterBuilder childrenFilter = FilterBuilders.boolFilter();
this.addTermFilter(childrenFilter, ActiveRuleNormalizer.ActiveRuleField.PROFILE_KEY.field(), query.getQProfileKey());
this.addTermFilter(childrenFilter, ActiveRuleNormalizer.ActiveRuleField.INHERITANCE.field(), query.getInheritance());
FilterBuilder activeRuleFilter;
if (childrenFilter.hasClauses()) {
activeRuleFilter = childrenFilter.must(ruleFilter);
} else {
activeRuleFilter = ruleFilter;
}

AggregationBuilder activeSeverities = AggregationBuilders.children(FACET_ACTIVE_SEVERITIES + "_children")
.childType(IndexDefinition.ACTIVE_RULE.getIndexType())
.subAggregation(AggregationBuilders.filter(FACET_ACTIVE_SEVERITIES + "_filter")
.filter(activeRuleFilter)
.subAggregation(
AggregationBuilders
.terms(FACET_ACTIVE_SEVERITIES)
.field(ActiveRuleNormalizer.ActiveRuleField.SEVERITY.field())
.include(Joiner.on('|').join(Severity.ALL))
.size(Severity.ALL.size())));

aggregations.put(FACET_ACTIVE_SEVERITIES, AggregationBuilders.global(FACET_ACTIVE_SEVERITIES).subAggregation(activeSeverities));
}
}

private StickyFacetBuilder stickyFacetBuilder(QueryBuilder query, Map<String, FilterBuilder> filters) {
return new StickyFacetBuilder(query, filters);
}

private void setSorting(RuleQuery query, SearchRequestBuilder esSearch) {
/* integrate Query Sort */
String queryText = query.getQueryText();
if (query.getSortField() != null) {
FieldSortBuilder sort = SortBuilders.fieldSort(appendSortSuffixIfNeeded(query.getSortField()));
if (query.isAscendingSort()) {
sort.order(SortOrder.ASC);
} else {
sort.order(SortOrder.DESC);
}
esSearch.addSort(sort);
} else if (queryText != null && !queryText.isEmpty()) {
esSearch.addSort(SortBuilders.scoreSort());
} else {
esSearch.addSort(appendSortSuffixIfNeeded(RuleIndexDefinition.FIELD_RULE_UPDATED_AT), SortOrder.DESC);
// deterministic sort when exactly the same updated_at (same millisecond)
esSearch.addSort(appendSortSuffixIfNeeded(RuleIndexDefinition.FIELD_RULE_KEY), SortOrder.ASC);
}
}

public static String appendSortSuffixIfNeeded(String field) {
return field +
((field.equals(RuleIndexDefinition.FIELD_RULE_NAME)
|| field.equals(RuleIndexDefinition.FIELD_RULE_KEY))
? "." + BaseIndex.SORT_SUFFIX
: "");
}

private void setPagination(SearchOptions options, SearchRequestBuilder esSearch) {
esSearch.setFrom(options.getOffset());
esSearch.setSize(options.getLimit());
}

public Set<String> terms(String fields) {
return terms(fields, null, Integer.MAX_VALUE);
}

public Set<String> terms(String fields, @Nullable String query, int size) {
Set<String> tags = new HashSet<>();
String key = "_ref";

TermsBuilder terms = AggregationBuilders.terms(key)
.field(fields)
.size(size)
.minDocCount(1);
if (query != null) {
terms.include(".*" + query + ".*");
}
SearchRequestBuilder request = this.getClient()
.prepareSearch(RuleIndexDefinition.INDEX)
.setQuery(QueryBuilders.matchAllQuery())
.addAggregation(terms);

SearchResponse esResponse = request.get();

Terms aggregation = esResponse.getAggregations().get(key);

if (aggregation != null) {
for (Terms.Bucket value : aggregation.getBuckets()) {
tags.add(value.getKey());
}
}
return tags;
}

private enum ToRuleKey implements Function<String, RuleKey> {
INSTANCE;

@Override
public RuleKey apply(@Nonnull String input) {
return RuleKey.parse(input);
}
}

}

+ 100
- 0
server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndexDefinition.java View File

@@ -0,0 +1,100 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.rule.index;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
import org.sonar.api.config.Settings;
import org.sonar.server.es.IndexDefinition;
import org.sonar.server.es.NewIndex;

/**
* Definition of ES index "rules", including settings and fields.
*/
public class RuleIndexDefinition implements IndexDefinition {

public static final String INDEX = "rules";
public static final String TYPE_RULE = "rule";

public static final String FIELD_RULE_KEY = "key";
// TODO find at what this field is useful ?
public static final String FIELD_RULE_KEY_AS_LIST = "_key";
public static final String FIELD_RULE_REPOSITORY = "repo";
public static final String FIELD_RULE_RULE_KEY = "ruleKey";
public static final String FIELD_RULE_INTERNAL_KEY = "internalKey";
public static final String FIELD_RULE_NAME = "name";
public static final String FIELD_RULE_HTML_DESCRIPTION = "htmlDesc";
public static final String FIELD_RULE_SEVERITY = "severity";
public static final String FIELD_RULE_STATUS = "status";
public static final String FIELD_RULE_LANGUAGE = "lang";
public static final String FIELD_RULE_IS_TEMPLATE = "isTemplate";
public static final String FIELD_RULE_TEMPLATE_KEY = "templateKey";
public static final String FIELD_RULE_ALL_TAGS = "allTags";
public static final String FIELD_RULE_CREATED_AT = "createdAt";
public static final String FIELD_RULE_UPDATED_AT = "updatedAt";

public static final Set<String> SORT_FIELDS = ImmutableSet.of(
RuleIndexDefinition.FIELD_RULE_NAME,
RuleIndexDefinition.FIELD_RULE_UPDATED_AT,
RuleIndexDefinition.FIELD_RULE_CREATED_AT,
RuleIndexDefinition.FIELD_RULE_KEY
);

private final Settings settings;

public RuleIndexDefinition(Settings settings) {
this.settings = settings;
}

@Override
public void define(IndexDefinitionContext context) {
NewIndex index = context.create(INDEX);

index.refreshHandledByIndexer();
index.setShards(settings);

// Rule type
NewIndex.NewIndexType ruleMapping = index.createType(TYPE_RULE);
ruleMapping.setAttribute("_id", ImmutableMap.of("path", FIELD_RULE_KEY));
ruleMapping.setAttribute("_routing", ImmutableMap.of("required", true, "path", RuleIndexDefinition.FIELD_RULE_REPOSITORY));
ruleMapping.setEnableSource(false);

ruleMapping.stringFieldBuilder(FIELD_RULE_KEY).enableSorting().enableGramSearch().build();
ruleMapping.stringFieldBuilder(FIELD_RULE_KEY_AS_LIST).enableGramSearch().build();
ruleMapping.stringFieldBuilder(FIELD_RULE_RULE_KEY).disableSearch().docValues().build();
ruleMapping.stringFieldBuilder(FIELD_RULE_REPOSITORY).docValues().build();
ruleMapping.stringFieldBuilder(FIELD_RULE_INTERNAL_KEY).disableSearch().docValues().build();

ruleMapping.stringFieldBuilder(FIELD_RULE_NAME).enableSorting().enableWordSearch().build();
ruleMapping.stringFieldBuilder(FIELD_RULE_HTML_DESCRIPTION).enableWordSearch().build();
ruleMapping.stringFieldBuilder(FIELD_RULE_SEVERITY).docValues().build();
ruleMapping.stringFieldBuilder(FIELD_RULE_STATUS).docValues().build();
ruleMapping.stringFieldBuilder(FIELD_RULE_LANGUAGE).enableGramSearch().build();

ruleMapping.createBooleanField(FIELD_RULE_IS_TEMPLATE);
ruleMapping.stringFieldBuilder(FIELD_RULE_TEMPLATE_KEY).docValues().build();

ruleMapping.stringFieldBuilder(FIELD_RULE_ALL_TAGS).enableGramSearch().build();

ruleMapping.createLongField(FIELD_RULE_CREATED_AT);
ruleMapping.createLongField(FIELD_RULE_UPDATED_AT);
}
}

+ 101
- 0
server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndexer.java View File

@@ -0,0 +1,101 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.rule.index;

import java.util.Iterator;
import org.elasticsearch.action.index.IndexRequest;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.server.es.BaseIndexer;
import org.sonar.server.es.BulkIndexer;
import org.sonar.server.es.EsClient;

import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_UPDATED_AT;
import static org.sonar.server.rule.index.RuleIndexDefinition.INDEX;
import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;

public class RuleIndexer extends BaseIndexer {

private final DbClient dbClient;

public RuleIndexer(DbClient dbClient, EsClient esClient) {
super(esClient, 300, INDEX, TYPE_RULE, FIELD_RULE_UPDATED_AT);
this.dbClient = dbClient;
}

@Override
protected long doIndex(long lastUpdatedAt) {
return doIndex(createBulkIndexer(false), lastUpdatedAt);
}

public void index(Iterator<RuleDoc> rules) {
doIndex(createBulkIndexer(false), rules);
}

private long doIndex(BulkIndexer bulk, long lastUpdatedAt) {
DbSession dbSession = dbClient.openSession(false);
long maxDate;
try {
RuleResultSetIterator rowIt = RuleResultSetIterator.create(dbClient, dbSession, lastUpdatedAt);
maxDate = doIndex(bulk, rowIt);
rowIt.close();
return maxDate;
} finally {
dbSession.close();
}
}

private long doIndex(BulkIndexer bulk, Iterator<RuleDoc> rules) {
bulk.start();
long maxDate = 0L;
while (rules.hasNext()) {
RuleDoc rule = rules.next();
// TODO when active rule is not more DAO v2, restore deleting of REMOVED rules and also remove active rules linked to this rule
// if (rule.status() == RuleStatus.REMOVED) {
// bulk.add(newDeleteRequest(rule));
// } else {
// }
bulk.add(newUpsertRequest(rule));

// it's more efficient to sort programmatically than in SQL on some databases (MySQL for instance)
maxDate = Math.max(maxDate, rule.updatedAtAtAsLong());
}
bulk.stop();
return maxDate;
}

private BulkIndexer createBulkIndexer(boolean large) {
BulkIndexer bulk = new BulkIndexer(esClient, INDEX);
bulk.setLarge(large);
return bulk;
}

private IndexRequest newUpsertRequest(RuleDoc rule) {
return new IndexRequest(INDEX, TYPE_RULE, rule.key().toString())
.routing(rule.repository())
.source(rule.getFields());
}

// private DeleteRequest newDeleteRequest(RuleDoc rule) {
// return new DeleteRequest(INDEX, TYPE_RULE, rule.key().toString())
// .routing(rule.repository());
// }

}

+ 23
- 15
server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleQuery.java View File

@@ -20,15 +20,12 @@
package org.sonar.server.rule.index;

import com.google.common.base.Preconditions;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.server.search.IndexField;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

import java.util.Collection;
import java.util.Date;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;

public class RuleQuery {

@@ -45,8 +42,8 @@ public class RuleQuery {
private Collection<String> activeSeverities;
private String templateKey;
private Boolean isTemplate;
private Date availableSince;
private IndexField sortField;
private Long availableSince;
private String sortField;
private boolean ascendingSort = true;
private String internalKey;
private String ruleKey;
@@ -203,15 +200,15 @@ public class RuleQuery {
return this;
}

public IndexField getSortField() {
public String getSortField() {
return this.sortField;
}

public RuleQuery setSortField(@Nullable IndexField sf) {
if (sf != null && !sf.isSortable()) {
throw new IllegalStateException(String.format("Field '%s' is not sortable", sf.field()));
public RuleQuery setSortField(@Nullable String field) {
if (field != null && !RuleIndexDefinition.SORT_FIELDS.contains(field)) {
throw new IllegalStateException(String.format("Field '%s' is not sortable", field));
}
this.sortField = sf;
this.sortField = field;
return this;
}

@@ -224,12 +221,23 @@ public class RuleQuery {
return this;
}

@Deprecated
public RuleQuery setAvailableSince(@Nullable Date d) {
this.availableSince = d;
this.availableSince = d.getTime();
return this;
}

@Deprecated
public Date getAvailableSince() {
return new Date(this.availableSince);
}

public RuleQuery setAvailableSince(@Nullable Long l) {
this.availableSince = l;
return this;
}

public Long getAvailableSinceLong() {
return this.availableSince;
}


+ 141
- 0
server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleResultSetIterator.java View File

@@ -0,0 +1,141 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.rule.index;

import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Set;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.rule.RuleKey;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.ResultSetIterator;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.SeverityUtil;
import org.sonar.markdown.Markdown;

/**
* Scrolls over table RULES and reads documents to populate the rules index
*/
public class RuleResultSetIterator extends ResultSetIterator<RuleDoc> {

private static final String[] FIELDS = {
// column 1
"r.plugin_rule_key",
"r.plugin_name",
"r.name",
"r.description",
"r.description_format",
"r.priority",
"r.status",
"r.is_template",
"r.tags",
"r.system_tags",

// column 11
"t.plugin_rule_key",
"t.plugin_name",
"r.plugin_config_key",
"r.language",
"r.created_at_ms",
"r.updated_at_ms",
};

private static final String SQL_ALL = "SELECT " + StringUtils.join(FIELDS, ",") + " FROM rules r " +
"LEFT OUTER JOIN rules t ON t.id=r.template_id";

private static final String SQL_AFTER_DATE = SQL_ALL + " WHERE r.updated_at_ms>?";

private static final Splitter TAGS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();

private RuleResultSetIterator(PreparedStatement stmt) throws SQLException {
super(stmt);
}

static RuleResultSetIterator create(DbClient dbClient, DbSession session, long afterDate) {
try {
String sql = afterDate > 0L ? SQL_AFTER_DATE : SQL_ALL;
PreparedStatement stmt = dbClient.getMyBatis().newScrollingSelectStatement(session, sql);
if (afterDate > 0L) {
stmt.setLong(1, afterDate);
}
return new RuleResultSetIterator(stmt);
} catch (SQLException e) {
throw new IllegalStateException("Fail to prepare SQL request to select all rules", e);
}
}

@Override
protected RuleDoc read(ResultSet rs) throws SQLException {
RuleDoc doc = new RuleDoc(Maps.<String, Object>newHashMapWithExpectedSize(16));

String ruleKey = rs.getString(1);
String repositoryKey = rs.getString(2);
RuleKey key = RuleKey.of(repositoryKey, ruleKey);

// all the fields must be present, even if value is null
doc.setKey(key.toString());
doc.setKeyAsList(ImmutableList.of(repositoryKey, ruleKey));
doc.setRuleKey(ruleKey);
doc.setRepository(repositoryKey);
doc.setName(rs.getString(3));

String description = rs.getString(4);
String descriptionFormat = rs.getString(5);
if (descriptionFormat != null) {
if (RuleDto.Format.HTML.equals(RuleDto.Format.valueOf(descriptionFormat))) {
doc.setHtmlDescription(description);
} else {
doc.setHtmlDescription(description == null ? null : Markdown.convertToHtml(description));
}
}

doc.setSeverity(SeverityUtil.getSeverityFromOrdinal(rs.getInt(6)));
doc.setStatus(rs.getString(7));
doc.setIsTemplate(rs.getBoolean(8));
doc.setAllTags(Sets.union(stringTagsToSet(rs.getString(9)), stringTagsToSet(rs.getString(10))));

String templateRuleKey = rs.getString(11);
String templateRepoKey = rs.getString(12);
if (templateRepoKey != null && templateRuleKey != null) {
doc.setTemplateKey(RuleKey.of(templateRepoKey, templateRuleKey).toString());
} else {
doc.setTemplateKey(null);
}

doc.setInternalKey(rs.getString(13));
doc.setLanguage(rs.getString(14));
doc.setCreatedAt(rs.getLong(15));
doc.setUpdatedAt(rs.getLong(16));

return doc;
}

private static Set<String> stringTagsToSet(@Nullable String tags) {
return ImmutableSet.copyOf(TAGS_SPLITTER.split(tags == null ? "" : tags));
}
}

+ 32
- 24
server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java View File

@@ -55,8 +55,8 @@ import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.RuleParamDto;
import org.sonar.server.qualityprofile.ActiveRule;
import org.sonar.server.rule.Rule;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleNormalizer;
import org.sonar.server.rule.index.RuleIndex2;
import org.sonar.server.rule.index.RuleIndexDefinition;
import org.sonar.server.rule.index.RuleQuery;
import org.sonar.server.search.FacetValue;
import org.sonar.server.search.Facets;
@@ -71,6 +71,15 @@ import static com.google.common.collect.FluentIterable.from;
import static org.sonar.server.search.QueryContext.MAX_LIMIT;
import static org.sonar.server.ws.WsUtils.writeProtobuf;

import static org.sonar.server.rule.index.RuleIndex2.ALL_STATUSES_EXCEPT_REMOVED;
import static org.sonar.server.rule.index.RuleIndex2.FACET_ACTIVE_SEVERITIES;
import static org.sonar.server.rule.index.RuleIndex2.FACET_LANGUAGES;
import static org.sonar.server.rule.index.RuleIndex2.FACET_OLD_DEFAULT;
import static org.sonar.server.rule.index.RuleIndex2.FACET_REPOSITORIES;
import static org.sonar.server.rule.index.RuleIndex2.FACET_SEVERITIES;
import static org.sonar.server.rule.index.RuleIndex2.FACET_STATUSES;
import static org.sonar.server.rule.index.RuleIndex2.FACET_TAGS;

/**
* @since 4.4
*/
@@ -97,12 +106,12 @@ public class SearchAction implements RulesWsAction {

private final UserSession userSession;
private final DbClient dbClient;
private final RuleIndex ruleIndex;
private final RuleIndex2 ruleIndex;
private final ActiveRuleCompleter activeRuleCompleter;
private final RuleMapping mapping;
private final RuleMapper mapper;

public SearchAction(RuleIndex ruleIndex, ActiveRuleCompleter activeRuleCompleter, RuleMapping mapping, UserSession userSession, DbClient dbClient, RuleMapper mapper) {
public SearchAction(RuleIndex2 ruleIndex, ActiveRuleCompleter activeRuleCompleter, RuleMapping mapping, UserSession userSession, DbClient dbClient, RuleMapper mapper) {
this.userSession = userSession;
this.ruleIndex = ruleIndex;
this.activeRuleCompleter = activeRuleCompleter;
@@ -181,13 +190,14 @@ public class SearchAction implements RulesWsAction {
@CheckForNull
protected Collection<String> possibleFacets() {
return Arrays.asList(
RuleIndex.FACET_LANGUAGES,
RuleIndex.FACET_REPOSITORIES,
RuleIndex.FACET_TAGS,
RuleIndex.FACET_SEVERITIES,
RuleIndex.FACET_ACTIVE_SEVERITIES,
RuleIndex.FACET_STATUSES,
RuleIndex.FACET_OLD_DEFAULT);
FACET_LANGUAGES,
FACET_REPOSITORIES,
FACET_TAGS,
FACET_SEVERITIES,
FACET_ACTIVE_SEVERITIES,
FACET_STATUSES,
FACET_OLD_DEFAULT
);
}

/**
@@ -287,11 +297,9 @@ public class SearchAction implements RulesWsAction {
action
.createParam(Param.SORT)
.setDescription("Sort field")
.setPossibleValues(RuleNormalizer.RuleField.NAME.field(),
RuleNormalizer.RuleField.UPDATED_AT.field(),
RuleNormalizer.RuleField.CREATED_AT.field(),
RuleNormalizer.RuleField.KEY.field())
.setExampleValue(RuleNormalizer.RuleField.NAME.field());
.setPossibleValues(RuleIndexDefinition.SORT_FIELDS)
.setExampleValue(RuleIndexDefinition.SORT_FIELDS.iterator().next());


action
.createParam(Param.ASCENDING)
@@ -318,7 +326,7 @@ public class SearchAction implements RulesWsAction {

String sortParam = request.param(Param.SORT);
if (sortParam != null) {
query.setSortField(RuleNormalizer.RuleField.of(sortParam));
query.setSortField(sortParam);
query.setAscendingSort(request.mandatoryParamAsBoolean(Param.ASCENDING));
}
return query;
@@ -337,7 +345,7 @@ public class SearchAction implements RulesWsAction {
.setLimit(context.getLimit())
.setOffset(context.getOffset())
.setScroll(context.isScroll());
if (context.facets().contains(RuleIndex.FACET_OLD_DEFAULT)) {
if (context.facets().contains(RuleIndex2.FACET_OLD_DEFAULT)) {
searchQueryContext.addFacets(DEFAULT_FACETS);
} else {
searchQueryContext.addFacets(context.facets());
@@ -424,12 +432,12 @@ public class SearchAction implements RulesWsAction {
}

protected void writeFacets(SearchResponse.Builder response, Request request, QueryContext context, SearchResult results) {
addMandatoryFacetValues(results, RuleIndex.FACET_LANGUAGES, request.paramAsStrings(PARAM_LANGUAGES));
addMandatoryFacetValues(results, RuleIndex.FACET_REPOSITORIES, request.paramAsStrings(PARAM_REPOSITORIES));
addMandatoryFacetValues(results, RuleIndex.FACET_STATUSES, RuleIndex.ALL_STATUSES_EXCEPT_REMOVED);
addMandatoryFacetValues(results, RuleIndex.FACET_SEVERITIES, Severity.ALL);
addMandatoryFacetValues(results, RuleIndex.FACET_ACTIVE_SEVERITIES, Severity.ALL);
addMandatoryFacetValues(results, RuleIndex.FACET_TAGS, request.paramAsStrings(PARAM_TAGS));
addMandatoryFacetValues(results, FACET_LANGUAGES, request.paramAsStrings(PARAM_LANGUAGES));
addMandatoryFacetValues(results, FACET_REPOSITORIES, request.paramAsStrings(PARAM_REPOSITORIES));
addMandatoryFacetValues(results, FACET_STATUSES, ALL_STATUSES_EXCEPT_REMOVED);
addMandatoryFacetValues(results, FACET_SEVERITIES, Severity.ALL);
addMandatoryFacetValues(results, FACET_ACTIVE_SEVERITIES, Severity.ALL);
addMandatoryFacetValues(results, FACET_TAGS, request.paramAsStrings(PARAM_TAGS));

Common.Facet.Builder facet = Common.Facet.newBuilder();
Common.FacetValue.Builder value = Common.FacetValue.newBuilder();

+ 3
- 3
server/sonar-server/src/main/java/org/sonar/server/search/IndexSynchronizer.java View File

@@ -30,7 +30,6 @@ import org.sonar.server.db.DeprecatedDao;
import org.sonar.server.issue.index.IssueAuthorizationIndexer;
import org.sonar.server.issue.index.IssueIndexer;
import org.sonar.server.qualityprofile.index.ActiveRuleIndex;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.test.index.TestIndexer;
import org.sonar.server.user.index.UserIndexer;
import org.sonar.server.view.index.ViewIndexer;
@@ -56,7 +55,8 @@ public class IndexSynchronizer {
*/
public IndexSynchronizer(DbClient db, IndexClient index,
TestIndexer testIndexer, IssueAuthorizationIndexer issueAuthorizationIndexer, IssueIndexer issueIndexer,
UserIndexer userIndexer, ViewIndexer viewIndexer, ActivityIndexer activityIndexer, Settings settings) {
UserIndexer userIndexer, ViewIndexer viewIndexer, ActivityIndexer activityIndexer,
Settings settings) {
this.db = db;
this.index = index;
this.testIndexer = testIndexer;
@@ -71,7 +71,7 @@ public class IndexSynchronizer {
public void executeDeprecated() {
DbSession session = db.openSession(false);
try {
synchronize(session, db.deprecatedRuleDao(), index.get(RuleIndex.class));
// synchronize(session, db.deprecatedRuleDao(), index.get(RuleIndex.class));
synchronize(session, db.activeRuleDao(), index.get(ActiveRuleIndex.class));
session.commit();
} finally {

+ 5
- 6
server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java View File

@@ -23,6 +23,11 @@ import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import java.io.File;
import java.io.FileInputStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.commons.lang.reflect.ConstructorUtils;
@@ -47,12 +52,6 @@ import org.sonar.server.search.BaseDoc;
import org.sonar.server.search.SearchClient;
import org.sonar.test.TestUtils;

import java.io.File;
import java.io.FileInputStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;


+ 24
- 1
server/sonar-server/src/test/java/org/sonar/server/es/NewIndexTest.java View File

@@ -21,6 +21,7 @@ package org.sonar.server.es;

import com.google.common.collect.ImmutableMap;
import java.util.Map;
import org.assertj.core.data.MapEntry;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.settings.Settings;
import org.junit.Test;
@@ -66,8 +67,8 @@ public class NewIndexTest {
mapping.createUuidPathField("uuid_path_field");

mapping = index.getTypes().get("issue");
assertThat(mapping.getAttributes().get("dynamic")).isEqualTo("true");
assertThat(mapping).isNotNull();
assertThat(mapping.getAttributes().get("dynamic")).isEqualTo("true");
assertThat(mapping.getProperty("foo_field")).isInstanceOf(Map.class);
assertThat((Map) mapping.getProperty("foo_field")).containsEntry("type", "string");
assertThat((Map) mapping.getProperty("byte_field")).isNotEmpty();
@@ -184,4 +185,26 @@ public class NewIndexTest {
assertThat(index.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_SHARDS)).isEqualTo("3");
assertThat(index.getSettings().get(IndexMetaData.SETTING_NUMBER_OF_REPLICAS)).isEqualTo("1");
}

@Test
public void index_with_source() {
NewIndex index = new NewIndex("issues");
NewIndex.NewIndexType mapping = index.createType("issue");
mapping.setEnableSource(true);

mapping = index.getTypes().get("issue");
assertThat(mapping).isNotNull();
assertThat((Map<String, Object>)mapping.getAttributes().get("_source")).containsExactly(MapEntry.entry("enabled", true));
}

@Test
public void index_without_source() {
NewIndex index = new NewIndex("issues");
NewIndex.NewIndexType mapping = index.createType("issue");
mapping.setEnableSource(false);

mapping = index.getTypes().get("issue");
assertThat(mapping).isNotNull();
assertThat((Map<String, Object>)mapping.getAttributes().get("_source")).containsExactly(MapEntry.entry("enabled", false));
}
}

+ 0
- 418
server/sonar-server/src/test/java/org/sonar/server/rule/RuleBackendMediumTest.java View File

@@ -1,418 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.rule;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.util.Collection;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.server.rule.RuleParamType;
import org.sonar.db.DbSession;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.RuleParamDto;
import org.sonar.db.rule.RuleTesting;
import org.sonar.server.db.DbClient;
import org.sonar.server.platform.Platform;
import org.sonar.server.rule.db.RuleDao;
import org.sonar.server.rule.index.RuleDoc;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.rule.index.RuleQuery;
import org.sonar.server.search.QueryContext;
import org.sonar.server.tester.ServerTester;
import org.sonar.server.tester.UserSessionRule;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Test persistence in db and indexation in es (--> integration of DAOs and Indexes)
*/
public class RuleBackendMediumTest {

@ClassRule
public static ServerTester tester = new ServerTester();
@org.junit.Rule
public UserSessionRule userSessionRule = UserSessionRule.forServerTester(tester);

RuleDao dao = tester.get(RuleDao.class);
RuleIndex index = tester.get(RuleIndex.class);
DbClient db;
DbSession dbSession;

@Before
public void before() {
tester.clearDbAndIndexes();
db = tester.get(DbClient.class);
dbSession = tester.get(DbClient.class).openSession(false);
}

@After
public void after() {
dbSession.close();
}

@Test
public void insert_in_db_and_multiget_in_es() {
// insert db
RuleDto ruleDto = RuleTesting.newXooX1();
RuleDto ruleDto2 = RuleTesting.newXooX2();
dao.insert(dbSession, ruleDto, ruleDto2);
dbSession.commit();

// check that we get two rules
Collection<Rule> hits = index.getByKeys(RuleTesting.XOO_X1, RuleTesting.XOO_X2);
assertThat(hits).hasSize(2);
}

@Test
public void insert_in_db_and_index_in_es() {
// insert db
RuleDto ruleDto = RuleTesting.newXooX1();
dao.insert(dbSession, ruleDto);
dbSession.commit();

// verify that rule is persisted in db
RuleDto persistedDto = dao.getNullableByKey(dbSession, RuleTesting.XOO_X1);
assertThat(persistedDto).isNotNull();
assertThat(persistedDto.getId()).isGreaterThanOrEqualTo(0);
assertThat(persistedDto.getRuleKey()).isEqualTo(ruleDto.getRuleKey());
assertThat(persistedDto.getLanguage()).isEqualTo(ruleDto.getLanguage());
assertThat(persistedDto.getCreatedAt()).isNotNull();
assertThat(persistedDto.getUpdatedAt()).isNotNull();

// verify that rule is indexed in es
Rule hit = index.getByKey(RuleTesting.XOO_X1);
assertRuleEquivalent(ruleDto, hit);

// Verify Multi-get
Collection<Rule> hits = index.getByKeys(RuleTesting.XOO_X1);
assertThat(hits).hasSize(1);
assertRuleEquivalent(ruleDto, Iterables.getFirst(hits, null));

}

private void assertRuleEquivalent(RuleDto ruleDto, Rule hit) {
assertThat(hit).isNotNull();
assertThat(hit.key().repository()).isEqualTo(ruleDto.getRepositoryKey());
assertThat(hit.key().rule()).isEqualTo(ruleDto.getRuleKey());
assertThat(hit.language()).isEqualTo(ruleDto.getLanguage());
assertThat(hit.name()).isEqualTo(ruleDto.getName());
assertThat(hit.htmlDescription()).isEqualTo(ruleDto.getDescription());
assertThat(hit.status()).isEqualTo(RuleStatus.READY);
assertThat(hit.createdAt()).isNotNull();
assertThat(hit.updatedAt()).isNotNull();
assertThat(hit.internalKey()).isEqualTo(ruleDto.getConfigKey());
assertThat(hit.severity()).isEqualTo(ruleDto.getSeverityString());
assertThat(hit.isTemplate()).isFalse();
assertThat(hit.effortToFixDescription()).isEqualTo(ruleDto.getEffortToFixDescription());
}

@Test
public void insert_rule_tags_in_db_and_index_in_es() {
// insert db
RuleDto ruleDto = RuleTesting.newXooX1();
dao.insert(dbSession, ruleDto);
dbSession.commit();

RuleDto persistedDto = dao.getNullableByKey(dbSession, RuleTesting.XOO_X1);
assertThat(persistedDto.getTags().containsAll(ruleDto.getTags())).isTrue();
assertThat(persistedDto.getSystemTags().containsAll(ruleDto.getSystemTags())).isTrue();

Rule hit = index.getByKey(RuleTesting.XOO_X1);
assertThat(hit.tags().containsAll(ruleDto.getTags())).isTrue();
assertThat(hit.systemTags().containsAll(ruleDto.getSystemTags())).isTrue();
}

@Test
public void insert_and_index_rule_parameters() {
// insert db
RuleDto ruleDto = RuleTesting.newXooX1();
dao.insert(dbSession, ruleDto);
dbSession.commit();

RuleParamDto minParamDto = new RuleParamDto()
.setName("min")
.setType(RuleParamType.INTEGER.type())
.setDefaultValue("2")
.setDescription("Minimum");
dao.insertRuleParam(dbSession, ruleDto, minParamDto);
RuleParamDto maxParamDto = new RuleParamDto()
.setName("max")
.setType(RuleParamType.INTEGER.type())
.setDefaultValue("10")
.setDescription("Maximum");
dao.insertRuleParam(dbSession, ruleDto, maxParamDto);
dbSession.commit();

// Verify that RuleDto has date from insertion
RuleDto theRule = dao.getNullableByKey(dbSession, RuleTesting.XOO_X1);
assertThat(theRule.getCreatedAt()).isNotNull();
assertThat(theRule.getUpdatedAt()).isNotNull();

// verify that parameters are persisted in db
List<RuleParamDto> persistedDtos = dao.selectRuleParamsByRuleKey(dbSession, theRule.getKey());
assertThat(persistedDtos).hasSize(2);

// verify that parameters are indexed in es

Rule hit = index.getByKey(RuleTesting.XOO_X1);
assertThat(hit).isNotNull();
assertThat(hit.key()).isNotNull();

RuleService service = tester.get(RuleService.class);
Rule rule = service.getByKey(RuleTesting.XOO_X1);

assertThat(rule.params()).hasSize(2);
assertThat(Iterables.getLast(rule.params(), null).key()).isEqualTo("max");
}

@Test
public void insert_and_delete_rule_parameters() {
// insert db
RuleDto ruleDto = RuleTesting.newXooX1();
dao.insert(dbSession, ruleDto);
dbSession.commit();

RuleParamDto minParamDto = new RuleParamDto()
.setName("min")
.setType(RuleParamType.INTEGER.type())
.setDefaultValue("2")
.setDescription("Minimum");
dao.insertRuleParam(dbSession, ruleDto, minParamDto);
RuleParamDto maxParamDto = new RuleParamDto()
.setName("max")
.setType(RuleParamType.INTEGER.type())
.setDefaultValue("10")
.setDescription("Maximum");
dao.insertRuleParam(dbSession, ruleDto, maxParamDto);
dbSession.commit();

// 0. Verify that RuleDto has date from insertion
assertThat(dao.selectRuleParamsByRuleKey(dbSession, RuleTesting.XOO_X1)).hasSize(2);
assertThat(index.getByKey(RuleTesting.XOO_X1).params()).hasSize(2);

// 1. Delete parameter
dao.deleteRuleParam(dbSession, ruleDto, maxParamDto);
dbSession.commit();

// 2. assert only one param left
assertThat(dao.selectRuleParamsByRuleKey(dbSession, RuleTesting.XOO_X1)).hasSize(1);
assertThat(index.getByKey(RuleTesting.XOO_X1).params()).hasSize(1);
}

@Test
public void insert_and_update_rule() {
// insert db
RuleDto ruleDto = RuleTesting.newXooX1()
.setTags(ImmutableSet.of("hello"))
.setName("first name");
dao.insert(dbSession, ruleDto);
dbSession.commit();

// verify that parameters are indexed in es

Rule hit = index.getByKey(RuleTesting.XOO_X1);
assertThat(hit.tags()).containsExactly("hello");
assertThat(hit.name()).isEqualTo("first name");

// Update in DB
ruleDto.setTags(ImmutableSet.of("world"))
.setName("second name");
dao.update(dbSession, ruleDto);
dbSession.commit();

// verify that parameters are updated in es

hit = index.getByKey(RuleTesting.XOO_X1);
assertThat(hit.tags()).containsExactly("world");
assertThat(hit.name()).isEqualTo("second name");
}

@Test
public void insert_and_update_rule_param() {

// insert db
RuleDto ruleDto = RuleTesting.newXooX1();
dao.insert(dbSession, ruleDto);
dbSession.commit();

RuleParamDto minParamDto = new RuleParamDto()
.setName("min")
.setType(RuleParamType.INTEGER.type())
.setDefaultValue("2")
.setDescription("Minimum");
dao.insertRuleParam(dbSession, ruleDto, minParamDto);

RuleParamDto maxParamDto = new RuleParamDto()
.setName("max")
.setType(RuleParamType.INTEGER.type())
.setDefaultValue("10")
.setDescription("Maximum");
dao.insertRuleParam(dbSession, ruleDto, maxParamDto);
dbSession.commit();

// verify that parameters are indexed in es

Rule hit = index.getByKey(RuleTesting.XOO_X1);
assertThat(hit.params()).hasSize(2);

RuleParam param = hit.params().get(0);
assertThat(param.key()).isEqualTo("min");
assertThat(param.defaultValue()).isEqualTo("2");
assertThat(param.description()).isEqualTo("Minimum");

// Update in DB
minParamDto
.setDefaultValue("0.5")
.setDescription("new description");
dao.updateRuleParam(dbSession, ruleDto, minParamDto);
dbSession.commit();

// verify that parameters are updated in es

hit = index.getByKey(RuleTesting.XOO_X1);
assertThat(hit.params()).hasSize(2);

param = null;
for (RuleParam pparam : hit.params()) {
if (pparam.key().equals("min")) {
param = pparam;
}
}
assertThat(param).isNotNull();
assertThat(param.key()).isEqualTo("min");
assertThat(param.defaultValue()).isEqualTo("0.5");
assertThat(param.description()).isEqualTo("new description");
}

@Test
@Deprecated
public void has_id() {

RuleDto ruleDto = RuleTesting.newXooX1();
dao.insert(dbSession, ruleDto);
dbSession.commit();

assertThat(((RuleDoc) index.getByKey(RuleTesting.XOO_X1)).id()).isEqualTo(ruleDto.getId());
}

@Test
public void insert_update_debt_overload() {

RuleDto ruleDto = RuleTesting.newXooX1()
.setRemediationFunction(null)
.setRemediationCoefficient(null)
.setRemediationOffset(null);

RuleDto overloadedRuleDto = RuleTesting.newXooX2();

dao.insert(dbSession, ruleDto, overloadedRuleDto);
dbSession.commit();

// Assert is overloaded or not
assertThat(index.getByKey(RuleTesting.XOO_X1).debtOverloaded()).isFalse();
assertThat(index.getByKey(RuleTesting.XOO_X2).debtOverloaded()).isTrue();

// Assert overloaded value
Rule base = index.getByKey(RuleTesting.XOO_X1);
Rule overloaded = index.getByKey(RuleTesting.XOO_X2);

assertThat(base.debtRemediationFunction().type().toString())
.isEqualTo(ruleDto.getDefaultRemediationFunction());
assertThat(base.debtRemediationFunction().coefficient())
.isEqualTo(ruleDto.getDefaultRemediationCoefficient());
assertThat(base.debtRemediationFunction().offset())
.isEqualTo(ruleDto.getDefaultRemediationOffset());

assertThat(overloaded.debtRemediationFunction().type().toString())
.isEqualTo(overloadedRuleDto.getRemediationFunction());
assertThat(overloaded.debtRemediationFunction().coefficient())
.isEqualTo(overloadedRuleDto.getRemediationCoefficient());
assertThat(overloaded.debtRemediationFunction().offset())
.isEqualTo(overloadedRuleDto.getRemediationOffset());
}

@Test
public void should_not_find_removed() {
// insert db
dao.insert(dbSession,
RuleTesting.newXooX1(),
RuleTesting.newXooX2().setStatus(RuleStatus.REMOVED));
dbSession.commit();

// 0. Assert rules are in DB
assertThat(dao.selectAll(dbSession)).hasSize(2);

// 1. assert getBy for removed
assertThat(index.getByKey(RuleTesting.XOO_X2)).isNotNull();

// 2. assert find does not get REMOVED
List<Rule> rules = index.search(new RuleQuery(), new QueryContext(userSessionRule)).getHits();
assertThat(rules).hasSize(1);
assertThat(rules.get(0).key()).isEqualTo(RuleTesting.XOO_X1);
}

@Test
public void synchronize_after() {
// insert db
dao.insert(dbSession,
RuleTesting.newXooX1());
dbSession.commit();

// 0. Assert rules are in DB
assertThat(dao.selectAll(dbSession)).hasSize(1);
assertThat(index.countAll()).isEqualTo(1);

tester.clearIndexes();
assertThat(index.countAll()).isEqualTo(0);

tester.get(Platform.class).executeStartupTasks();
assertThat(index.countAll()).isEqualTo(1);

}

@Test
public void synchronize_after_with_nested() {
RuleDto rule = RuleTesting.newXooX1();

// insert db
dao.insert(dbSession, rule);

dao.insertRuleParam(dbSession, rule, RuleParamDto.createFor(rule).setName("MyParam").setType("STRING").setDefaultValue("test"));
dbSession.commit();

// 0. Assert rules are in DB
assertThat(dao.selectAll(dbSession)).hasSize(1);
assertThat(index.countAll()).isEqualTo(1);

tester.clearIndexes();
assertThat(index.countAll()).isEqualTo(0);

tester.get(Platform.class).executeStartupTasks();
assertThat(index.countAll()).isEqualTo(1);

assertThat(index.getByKey(rule.getKey()).param("MyParam").defaultValue()).isEqualTo("test");

}
}

+ 50
- 0
server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleDocTesting.java View File

@@ -0,0 +1,50 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.rule.index;

import com.google.common.collect.Maps;
import java.util.Arrays;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.db.rule.RuleTesting;

public class RuleDocTesting {

public static RuleDoc newDoc() {
return newDoc(RuleTesting.XOO_X1);
}

public static RuleDoc newDoc(RuleKey ruleKey) {
return new RuleDoc(Maps.<String, Object>newHashMap())
.setKey(ruleKey.toString())
.setRepository(ruleKey.repository())
.setRuleKey(ruleKey.rule())
.setName("Name " + ruleKey.toString())
.setHtmlDescription("Description " + ruleKey.rule())
.setSeverity(Severity.CRITICAL)
.setStatus(RuleStatus.READY.name())
.setLanguage("xoo")
.setIsTemplate(false)
.setAllTags(Arrays.asList("bug", "performance"))
.setCreatedAt(150000000L)
.setUpdatedAt(160000000L);
}
}

+ 732
- 0
server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndex2Test.java View File

@@ -0,0 +1,732 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.rule.index;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.config.Settings;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.server.es.EsTester;
import org.sonar.server.es.SearchIdResult;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.tester.UserSessionRule;

import static java.util.Arrays.asList;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.MapEntry.entry;
import static org.junit.Assert.fail;
import static org.sonar.api.rule.Severity.BLOCKER;
import static org.sonar.api.rule.Severity.INFO;
import static org.sonar.api.rule.Severity.MINOR;
import static org.sonar.server.rule.index.RuleDocTesting.newDoc;
import static org.sonar.server.rule.index.RuleIndex2.FACET_LANGUAGES;
import static org.sonar.server.rule.index.RuleIndex2.FACET_REPOSITORIES;
import static org.sonar.server.rule.index.RuleIndex2.FACET_TAGS;

public class RuleIndex2Test {

@ClassRule
public static EsTester tester = new EsTester().addDefinitions(new RuleIndexDefinition(new Settings()));

@Rule
public UserSessionRule userSessionRule = UserSessionRule.standalone();

RuleIndex2 index;

RuleIndexer ruleIndexer;

@Before
public void setUp() {
tester.truncateIndices();
ruleIndexer = new RuleIndexer(null, tester.client());
index = new RuleIndex2(tester.client());
}

@Test
public void search_all_rules() {
indexRules(
newDoc(RuleKey.of("javascript", "S001")),
newDoc(RuleKey.of("java", "S002")));

SearchIdResult results = index.search(new RuleQuery(), new SearchOptions());

assertThat(results.getTotal()).isEqualTo(2);
assertThat(results.getIds()).hasSize(2);
}

@Test
public void search_key_by_query() {
indexRules(
newDoc(RuleKey.of("javascript", "X001")),
newDoc(RuleKey.of("cobol", "X001")),
newDoc(RuleKey.of("php", "S002")));

// key
RuleQuery query = new RuleQuery().setQueryText("X001");
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(2);

// partial key does not match
query = new RuleQuery().setQueryText("X00");
assertThat(index.search(query, new SearchOptions()).getIds()).isEmpty();

// repo:key -> nice-to-have !
query = new RuleQuery().setQueryText("javascript:X001");
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(1);
}

@Test
public void filter_by_key() {
indexRules(
newDoc(RuleKey.of("javascript", "X001")),
newDoc(RuleKey.of("cobol", "X001")),
newDoc(RuleKey.of("php", "S002")));

// key
RuleQuery query = new RuleQuery().setKey(RuleKey.of("javascript", "X001").toString());

assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(1);

// partial key does not match
query = new RuleQuery().setKey("X001");
assertThat(index.search(query, new SearchOptions()).getIds()).isEmpty();
}

@Test
public void search_name_by_query() {
indexRules(newDoc(RuleKey.of("javascript", "S001"))
.setName("testing the partial match and matching of rule"));

// substring
RuleQuery query = new RuleQuery().setQueryText("test");
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(1);

// substring
query = new RuleQuery().setQueryText("partial match");
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(1);

// case-insensitive
query = new RuleQuery().setQueryText("TESTING");
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(1);

// not found
query = new RuleQuery().setQueryText("not present");
assertThat(index.search(query, new SearchOptions()).getIds()).isEmpty();
}

@Test
public void search_name_with_protected_chars() {
String nameWithProtectedChars = "ja#va&sc\"r:ipt";

indexRules(newDoc(RuleKey.of("javascript", "S001"))
.setName(nameWithProtectedChars));

RuleQuery protectedCharsQuery = new RuleQuery().setQueryText(nameWithProtectedChars);
List<RuleKey> results = index.search(protectedCharsQuery, new SearchOptions()).getIds();
assertThat(results).containsOnly(RuleKey.of("javascript", "S001"));
}

@Test
public void search_by_any_of_repositories() {
indexRules(
newDoc(RuleKey.of("findbugs", "S001")),
newDoc(RuleKey.of("pmd", "S002")));

RuleQuery query = new RuleQuery().setRepositories(asList("checkstyle", "pmd"));
SearchIdResult results = index.search(query, new SearchOptions());
assertThat(results.getIds()).containsOnly(RuleKey.of("pmd", "S002"));

// no results
query = new RuleQuery().setRepositories(singletonList("checkstyle"));
assertThat(index.search(query, new SearchOptions()).getIds()).isEmpty();

// empty list => no filter
query = new RuleQuery().setRepositories(Collections.<String>emptyList());
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(2);
}

@Test
public void search_by_tag() {
indexRules(
newDoc(RuleKey.of("java", "S001")).setAllTags(singleton("tag1")),
newDoc(RuleKey.of("java", "S002")).setAllTags(singleton("tag2")));

// find all
RuleQuery query = new RuleQuery();
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(2);

// tag1 in query
query = new RuleQuery().setQueryText("tag1");
assertThat(index.search(query, new SearchOptions()).getIds()).containsOnly(RuleKey.of("java", "S001"));

// tag1 and tag2 in query
query = new RuleQuery().setQueryText("tag1 tag2");
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(2);

// tag2 in filter
query = new RuleQuery().setTags(ImmutableSet.of("tag2"));
assertThat(index.search(query, new SearchOptions()).getIds()).containsOnly(RuleKey.of("java", "S002"));

// tag2 in filter and tag1 tag2 in query
query = new RuleQuery().setTags(ImmutableSet.of("tag2")).setQueryText("tag1");
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(0);

// tag2 in filter and tag1 in query
query = new RuleQuery().setTags(ImmutableSet.of("tag2")).setQueryText("tag1 tag2");
assertThat(index.search(query, new SearchOptions()).getIds()).containsOnly(RuleKey.of("java", "S002"));

// null list => no filter
query = new RuleQuery().setTags(Collections.<String>emptySet());
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(2);

// null list => no filter
query = new RuleQuery().setTags(null);
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(2);
}

@Test
public void search_by_is_template() {
indexRules(
newDoc(RuleKey.of("java", "S001")).setIsTemplate(false),
newDoc(RuleKey.of("java", "S002")).setIsTemplate(true));

// find all
RuleQuery query = new RuleQuery();
SearchIdResult results = index.search(query, new SearchOptions());
assertThat(results.getIds()).hasSize(2);

// Only template
query = new RuleQuery().setIsTemplate(true);
results = index.search(query, new SearchOptions());
assertThat(results.getIds()).containsOnly(RuleKey.of("java", "S002"));

// Only not template
query = new RuleQuery().setIsTemplate(false);
results = index.search(query, new SearchOptions());
assertThat(results.getIds()).containsOnly(RuleKey.of("java", "S001"));

// null => no filter
query = new RuleQuery().setIsTemplate(null);
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(2);
}

@Test
public void search_by_template_key() {
indexRules(
newDoc(RuleKey.of("java", "S001")).setIsTemplate(true),
newDoc(RuleKey.of("java", "S001_MY_CUSTOM")).setTemplateKey("java:S001"));

// find all
RuleQuery query = new RuleQuery();
SearchIdResult results = index.search(query, new SearchOptions());
assertThat(results.getIds()).hasSize(2);

// Only custom rule
query = new RuleQuery().setTemplateKey("java:S001");
results = index.search(query, new SearchOptions());
assertThat(results.getIds()).containsOnly(RuleKey.of("java", "S001_MY_CUSTOM"));

// null => no filter
query = new RuleQuery().setTemplateKey(null);
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(2);
}

@Test
public void search_by_any_of_languages() {
indexRules(
newDoc(RuleKey.of("java", "S001")).setLanguage("java"),
newDoc(RuleKey.of("javascript", "S002")).setLanguage("js"));

RuleQuery query = new RuleQuery().setLanguages(asList("cobol", "js"));
SearchIdResult results = index.search(query, new SearchOptions());
assertThat(results.getIds()).containsOnly(RuleKey.of("javascript", "S002"));

// no results
query = new RuleQuery().setLanguages(singletonList("cpp"));
assertThat(index.search(query, new SearchOptions()).getIds()).isEmpty();

// empty list => no filter
query = new RuleQuery().setLanguages(Collections.<String>emptyList());
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(2);

// null list => no filter
query = new RuleQuery().setLanguages(null);
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(2);
}

@Test
public void search_by_any_of_severities() {
indexRules(
newDoc(RuleKey.of("java", "S001")).setSeverity(BLOCKER),
newDoc(RuleKey.of("java", "S002")).setSeverity(INFO));

RuleQuery query = new RuleQuery().setSeverities(asList(INFO, MINOR));
SearchIdResult results = index.search(query, new SearchOptions());
assertThat(results.getIds()).containsOnly(RuleKey.of("java", "S002"));

// no results
query = new RuleQuery().setSeverities(singletonList(MINOR));
assertThat(index.search(query, new SearchOptions()).getIds()).isEmpty();

// empty list => no filter
query = new RuleQuery().setSeverities(Collections.<String>emptyList());
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(2);

// null list => no filter
query = new RuleQuery().setSeverities(null);
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(2);
}

@Test
public void search_by_any_of_statuses() {
indexRules(
newDoc(RuleKey.of("java", "S001")).setStatus(RuleStatus.BETA.name()),
newDoc(RuleKey.of("java", "S002")).setStatus(RuleStatus.READY.name()));

RuleQuery query = new RuleQuery().setStatuses(asList(RuleStatus.DEPRECATED, RuleStatus.READY));
SearchIdResult results = index.search(query, new SearchOptions());
assertThat(results.getIds()).containsOnly(RuleKey.of("java", "S002"));

// no results
query = new RuleQuery().setStatuses(singletonList(RuleStatus.DEPRECATED));
assertThat(index.search(query, new SearchOptions()).getIds()).isEmpty();

// empty list => no filter
query = new RuleQuery().setStatuses(Collections.<RuleStatus>emptyList());
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(2);

// null list => no filter
query = new RuleQuery().setStatuses(null);
assertThat(index.search(query, new SearchOptions()).getIds()).hasSize(2);
}

@Test
@Ignore
public void search_by_profile() {
// QualityProfileDto qualityProfileDto1 = QProfileTesting.newXooP1();
// QualityProfileDto qualityProfileDto2 = QProfileTesting.newXooP2();
// db.qualityProfileDao().insert(dbSession, qualityProfileDto1, qualityProfileDto2);
//
// RuleDto rule1 = RuleTesting.newXooX1();
// RuleDto rule2 = RuleTesting.newXooX2();
// RuleDto rule3 = RuleTesting.newXooX3();
// dao.insert(dbSession, rule1, rule2, rule3);
//
// db.activeRuleDao().insert(
// dbSession,
// ActiveRuleDto.createFor(qualityProfileDto1, rule1).setSeverity("BLOCKER"),
// ActiveRuleDto.createFor(qualityProfileDto2, rule1).setSeverity("BLOCKER"),
// ActiveRuleDto.createFor(qualityProfileDto1, rule2).setSeverity("BLOCKER"));
// dbSession.commit();
// dbSession.clearCache();
//
// // 1. get all active rules.
// Result<org.sonar.server.rule.Rule> result = index.search(new RuleQuery().setActivation(true),
// new SearchOptions());
// assertThat(result.getHits()).hasSize(2);
//
// // 2. get all inactive rules.
// result = index.search(new RuleQuery().setActivation(false),
// new SearchOptions());
// assertThat(result.getHits()).hasSize(1);
// assertThat(result.getHits().get(0).name()).isEqualTo(rule3.getName());
//
// // 3. get all rules not active on profile
// index.search(new RuleQuery().setActivation(false).setQProfileKey(qualityProfileDto2.getKey()),
// new SearchOptions());
// // TODO
// assertThat(result.getHits()).hasSize(1);
//
// // 4. get all active rules on profile
// result = index.search(new RuleQuery().setActivation(true)
// .setQProfileKey(qualityProfileDto2.getKey()),
// new SearchOptions());
// assertThat(result.getHits()).hasSize(1);
// assertThat(result.getHits().get(0).name()).isEqualTo(rule1.getName());
}

@Test
@Ignore
public void search_by_profile_and_inheritance() {
// QualityProfileDto qualityProfileDto1 = QProfileTesting.newXooP1();
// QualityProfileDto qualityProfileDto2 = QProfileTesting.newXooP2().setParentKee(QProfileTesting.XOO_P1_KEY);
// db.qualityProfileDao().insert(dbSession, qualityProfileDto1, qualityProfileDto2);
//
// RuleDto rule1 = RuleTesting.newDto(RuleKey.of("xoo", "S001"));
// RuleDto rule2 = RuleTesting.newDto(RuleKey.of("xoo", "S002"));
// RuleDto rule3 = RuleTesting.newDto(RuleKey.of("xoo", "S003"));
// RuleDto rule4 = RuleTesting.newDto(RuleKey.of("xoo", "S004"));
// dao.insert(dbSession, rule1, rule2, rule3, rule4);
//
// db.activeRuleDao().insert(
// dbSession,
// ActiveRuleDto.createFor(qualityProfileDto1, rule1)
// .setSeverity("BLOCKER"),
// ActiveRuleDto.createFor(qualityProfileDto1, rule2)
// .setSeverity("BLOCKER"),
// ActiveRuleDto.createFor(qualityProfileDto1, rule3)
// .setSeverity("BLOCKER"),
//
// ActiveRuleDto.createFor(qualityProfileDto2, rule1)
// .setSeverity("MINOR")
// .setInheritance(ActiveRule.Inheritance.INHERITED.name()),
// ActiveRuleDto.createFor(qualityProfileDto2, rule2)
// .setSeverity("BLOCKER")
// .setInheritance(ActiveRule.Inheritance.OVERRIDES.name()),
// ActiveRuleDto.createFor(qualityProfileDto2, rule3)
// .setSeverity("BLOCKER")
// .setInheritance(ActiveRule.Inheritance.INHERITED.name())
// );
//
// dbSession.commit();
//
// // 0. get all rules
// Result<org.sonar.server.rule.Rule> result = index.search(new RuleQuery(),
// new SearchOptions());
// assertThat(result.getHits()).hasSize(4);
//
// // 1. get all active rules
// result = index.search(new RuleQuery().setActivation(true),
// new SearchOptions());
// assertThat(result.getHits()).hasSize(3);
//
// // 2. get all inactive rules.
// result = index.search(new RuleQuery().setActivation(false),
// new SearchOptions());
// assertThat(result.getHits()).hasSize(1);
// assertThat(result.getHits().get(0).name()).isEqualTo(rule4.getName());
//
// // 3. get Inherited Rules on profile1
// result = index.search(new RuleQuery().setActivation(true)
// .setQProfileKey(qualityProfileDto1.getKey())
// .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())),
// new SearchOptions()
// );
// assertThat(result.getHits()).hasSize(0);
//
// // 4. get Inherited Rules on profile2
// result = index.search(new RuleQuery().setActivation(true)
// .setQProfileKey(qualityProfileDto2.getKey())
// .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())),
// new SearchOptions()
// );
// assertThat(result.getHits()).hasSize(2);
//
// // 5. get Overridden Rules on profile1
// result = index.search(new RuleQuery().setActivation(true)
// .setQProfileKey(qualityProfileDto1.getKey())
// .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())),
// new SearchOptions()
// );
// assertThat(result.getHits()).hasSize(0);
//
// // 6. get Overridden Rules on profile2
// result = index.search(new RuleQuery().setActivation(true)
// .setQProfileKey(qualityProfileDto2.getKey())
// .setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())),
// new SearchOptions()
// );
// assertThat(result.getHits()).hasSize(1);
//
// // 7. get Inherited AND Overridden Rules on profile1
// result = index.search(new RuleQuery().setActivation(true)
// .setQProfileKey(qualityProfileDto1.getKey())
// .setInheritance(ImmutableSet.of(
// ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())),
// new SearchOptions()
// );
// assertThat(result.getHits()).hasSize(0);
//
// // 8. get Inherited AND Overridden Rules on profile2
// result = index.search(new RuleQuery().setActivation(true)
// .setQProfileKey(qualityProfileDto2.getKey())
// .setInheritance(ImmutableSet.of(
// ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())),
// new SearchOptions()
// );
// assertThat(result.getHits()).hasSize(3);
}

@Test
@Ignore
public void search_by_profile_and_active_severity() {
// QualityProfileDto qualityProfileDto1 = QProfileTesting.newXooP1();
// QualityProfileDto qualityProfileDto2 = QProfileTesting.newXooP2();
// db.qualityProfileDao().insert(dbSession, qualityProfileDto1, qualityProfileDto2);
//
// RuleDto rule1 = RuleTesting.newXooX1().setSeverity("MAJOR");
// RuleDto rule2 = RuleTesting.newXooX2().setSeverity("MINOR");
// RuleDto rule3 = RuleTesting.newXooX3().setSeverity("INFO");
// dao.insert(dbSession, rule1, rule2, rule3);
//
// db.activeRuleDao().insert(
// dbSession,
// ActiveRuleDto.createFor(qualityProfileDto1, rule1).setSeverity("BLOCKER"),
// ActiveRuleDto.createFor(qualityProfileDto2, rule1).setSeverity("BLOCKER"),
// ActiveRuleDto.createFor(qualityProfileDto1, rule2).setSeverity("CRITICAL"));
// dbSession.commit();
// dbSession.clearCache();
//
// // 1. get all active rules.
// Result<org.sonar.server.rule.Rule> result = index.search(new
// RuleQuery().setActivation(true).setQProfileKey(qualityProfileDto1.getKey()),
// new SearchOptions());
// assertThat(result.getHits()).hasSize(2);
//
// // 2. get rules with active severity critical.
// result = index.search(new
// RuleQuery().setActivation(true).setQProfileKey(qualityProfileDto1.getKey()).setActiveSeverities(Arrays.asList("CRITICAL")),
// new SearchOptions().addFacets(Arrays.asList(RuleIndex.FACET_ACTIVE_SEVERITIES)));
// assertThat(result.getHits()).hasSize(1);
// assertThat(result.getHits().get(0).name()).isEqualTo(rule2.getName());
// // check stickyness of active severity facet
// assertThat(result.getFacetValues(RuleIndex.FACET_ACTIVE_SEVERITIES)).containsOnly(new FacetValue("BLOCKER", 1), new
// FacetValue("CRITICAL", 1));
//
// // 3. count activation severities of all active rules
// result = index.search(new RuleQuery(),
// new SearchOptions().addFacets(Arrays.asList(RuleIndex.FACET_ACTIVE_SEVERITIES)));
// assertThat(result.getHits()).hasSize(3);
// assertThat(result.getFacetValues(RuleIndex.FACET_ACTIVE_SEVERITIES)).containsOnly(new FacetValue("BLOCKER", 2), new
// FacetValue("CRITICAL", 1));
}

@Test
public void all_tags() {
indexRules(
newDoc(RuleKey.of("java", "S001")).setAllTags(asList("tag1", "sys1", "sys2")),
newDoc(RuleKey.of("java", "S002")).setAllTags(asList("tag2")));

assertThat(index.terms(RuleIndexDefinition.FIELD_RULE_ALL_TAGS, null, 10)).containsOnly("tag1", "tag2", "sys1", "sys2");
}

@Test
public void available_since() throws InterruptedException {
indexRules(
newDoc(RuleKey.of("java", "S001")).setCreatedAt(1000L),
newDoc(RuleKey.of("java", "S002")).setCreatedAt(2000L));

// 0. find all rules;
assertThat(index.search(new RuleQuery(), new SearchOptions()).getIds()).hasSize(2);

// 1. find all rules available since a date;
RuleQuery availableSinceQuery = new RuleQuery().setAvailableSince(2000L);
assertThat(index.search(availableSinceQuery, new SearchOptions()).getIds()).containsOnly(RuleKey.of("java", "S002"));

// 2. find no new rules since tomorrow.
RuleQuery availableSinceNowQuery = new RuleQuery().setAvailableSince(3000L);
assertThat(index.search(availableSinceNowQuery, new SearchOptions()).getIds()).hasSize(0);
}

@Test
public void global_facet_on_repositories_and_tags() {
indexRules(
newDoc(RuleKey.of("php", "S001")).setAllTags(singletonList("sysTag")),
newDoc(RuleKey.of("php", "S002")).setAllTags(singletonList("tag1")),
newDoc(RuleKey.of("javascript", "S002")).setAllTags(asList("tag1", "tag2")));

// should not have any facet!
RuleQuery query = new RuleQuery();
SearchIdResult result = index.search(query, new SearchOptions());
assertThat(result.getFacets().getAll()).isEmpty();

// should not have any facet on non matching query!
result = index.search(new RuleQuery().setQueryText("aeiou"), new SearchOptions().addFacets(singletonList("repositories")));
assertThat(result.getFacets().getAll()).hasSize(1);
assertThat(result.getFacets().getAll().get("repositories")).isEmpty();

// Repositories Facet is preset
result = index.search(query, new SearchOptions().addFacets(asList("repositories", "tags")));
assertThat(result.getFacets()).isNotNull();
assertThat(result.getFacets().getAll()).hasSize(2);

// Verify the value of a given facet
Map<String, Long> repoFacets = result.getFacets().get("repositories");
assertThat(repoFacets).containsOnly(entry("php", 2L), entry("javascript", 1L));

// Check that tag facet has both Tags and SystemTags values
Map<String, Long> tagFacets = result.getFacets().get("tags");
assertThat(tagFacets).containsOnly(entry("tag1", 2L), entry("sysTag", 1L), entry("tag2", 1L));
}

@Test
public void sticky_facets() {
indexRules(
newDoc(RuleKey.of("xoo", "S001")).setLanguage("java").setAllTags(Collections.<String>emptyList()),
newDoc(RuleKey.of("xoo", "S002")).setLanguage("java").setAllTags(Collections.<String>emptyList()),
newDoc(RuleKey.of("xoo", "S003")).setLanguage("java").setAllTags(asList("T1", "T2")),
newDoc(RuleKey.of("xoo", "S011")).setLanguage("cobol").setAllTags(Collections.<String>emptyList()),
newDoc(RuleKey.of("xoo", "S012")).setLanguage("cobol").setAllTags(Collections.<String>emptyList()),
newDoc(RuleKey.of("foo", "S013")).setLanguage("cobol").setAllTags(asList("T3", "T4")),
newDoc(RuleKey.of("foo", "S111")).setLanguage("cpp").setAllTags(Collections.<String>emptyList()),
newDoc(RuleKey.of("foo", "S112")).setLanguage("cpp").setAllTags(Collections.<String>emptyList()),
newDoc(RuleKey.of("foo", "S113")).setLanguage("cpp").setAllTags(asList("T2", "T3")));

// 0 assert Base
assertThat(index.search(new RuleQuery(), new SearchOptions()).getIds()).hasSize(9);

// 1 Facet with no filters at all
SearchIdResult result = index.search(new RuleQuery(), new SearchOptions().addFacets(asList("languages", "repositories", "tags")));
assertThat(result.getFacets().getAll()).hasSize(3);
assertThat(result.getFacets().getAll().get(FACET_LANGUAGES).keySet()).containsOnly("cpp", "java", "cobol");
assertThat(result.getFacets().getAll().get(FACET_REPOSITORIES).keySet()).containsOnly("xoo", "foo");
assertThat(result.getFacets().getAll().get(FACET_TAGS).keySet()).containsOnly("T1", "T2", "T3", "T4");

// 2 Facet with a language filter
// -- lang facet should still have all language
result = index.search(new RuleQuery().setLanguages(ImmutableList.of("cpp"))
, new SearchOptions().addFacets(asList("languages", "repositories", "tags")));
assertThat(result.getIds()).hasSize(3);
assertThat(result.getFacets().getAll()).hasSize(3);
assertThat(result.getFacets().get(FACET_LANGUAGES).keySet()).containsOnly("cpp", "java", "cobol");

// 3 facet with 2 filters
// -- lang facet for tag T2
// -- tag facet for lang cpp
// -- repository for cpp & T2
result = index.search(new RuleQuery()
.setLanguages(ImmutableList.of("cpp"))
.setTags(ImmutableList.of("T2"))
, new SearchOptions().addFacets(asList("languages", "repositories", "tags")));
assertThat(result.getIds()).hasSize(1);
assertThat(result.getFacets().getAll()).hasSize(3);
assertThat(result.getFacets().get(FACET_LANGUAGES).keySet()).containsOnly("cpp", "java");
assertThat(result.getFacets().get(FACET_REPOSITORIES).keySet()).containsOnly("foo");
assertThat(result.getFacets().get(FACET_TAGS).keySet()).containsOnly("T2", "T3");

// 4 facet with 2 filters
// -- lang facet for tag T2
// -- tag facet for lang cpp & java
// -- repository for (cpp || java) & T2
result = index.search(new RuleQuery()
.setLanguages(ImmutableList.of("cpp", "java"))
.setTags(ImmutableList.of("T2"))
, new SearchOptions().addFacets(asList("languages", "repositories", "tags")));
assertThat(result.getIds()).hasSize(2);
assertThat(result.getFacets().getAll()).hasSize(3);
assertThat(result.getFacets().get(FACET_LANGUAGES).keySet()).containsOnly("cpp", "java");
assertThat(result.getFacets().get(FACET_REPOSITORIES).keySet()).containsOnly("foo", "xoo");
assertThat(result.getFacets().get(FACET_TAGS).keySet()).containsOnly("T1", "T2", "T3");
}

@Test
public void sort_by_name() {
indexRules(
newDoc(RuleKey.of("java", "S001")).setName("abcd"),
newDoc(RuleKey.of("java", "S002")).setName("ABC"),
newDoc(RuleKey.of("java", "S003")).setName("FGH"));

// ascending
RuleQuery query = new RuleQuery().setSortField(RuleIndexDefinition.FIELD_RULE_NAME);
SearchIdResult<RuleKey> results = index.search(query, new SearchOptions());
assertThat(results.getIds()).containsExactly(RuleKey.of("java", "S002"), RuleKey.of("java", "S001"), RuleKey.of("java", "S003"));

// descending
query = new RuleQuery().setSortField(RuleIndexDefinition.FIELD_RULE_NAME).setAscendingSort(false);
results = index.search(query, new SearchOptions());
assertThat(results.getIds()).containsExactly(RuleKey.of("java", "S003"),RuleKey.of("java", "S001"), RuleKey.of("java", "S002"));
}

@Test
public void default_sort_is_by_updated_at_desc() {
indexRules(
newDoc(RuleKey.of("java", "S001")).setCreatedAt(1000L).setUpdatedAt(1000L),
newDoc(RuleKey.of("java", "S002")).setCreatedAt(1000L).setUpdatedAt(3000L),
newDoc(RuleKey.of("java", "S003")).setCreatedAt(1000L).setUpdatedAt(2000L));

SearchIdResult<RuleKey> results = index.search(new RuleQuery(), new SearchOptions());
assertThat(results.getIds()).containsExactly(RuleKey.of("java", "S002"), RuleKey.of("java", "S003"), RuleKey.of("java", "S001"));
}

@Test
public void fail_sort_by_language() {
try {
// Sorting on a field not tagged as sortable
new RuleQuery().setSortField(RuleIndexDefinition.FIELD_RULE_LANGUAGE);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Field 'lang' is not sortable");
}
}

@Test
public void paging() {
indexRules(
newDoc(RuleKey.of("java", "S001")),
newDoc(RuleKey.of("java", "S002")),
newDoc(RuleKey.of("java", "S003")));

// from 0 to 1 included
SearchOptions options = new SearchOptions();
options.setOffset(0).setLimit(2);
SearchIdResult results = index.search(new RuleQuery(), options);
assertThat(results.getTotal()).isEqualTo(3);
assertThat(results.getIds()).hasSize(2);

// from 0 to 9 included
options.setOffset(0).setLimit(10);
results = index.search(new RuleQuery(), options);
assertThat(results.getTotal()).isEqualTo(3);
assertThat(results.getIds()).hasSize(3);

// from 2 to 11 included
options.setOffset(2).setLimit(10);
results = index.search(new RuleQuery(), options);
assertThat(results.getTotal()).isEqualTo(3);
assertThat(results.getIds()).hasSize(1);

// from 2 to 11 included
options.setOffset(2).setLimit(0);
results = index.search(new RuleQuery(), options);
assertThat(results.getTotal()).isEqualTo(3);
assertThat(results.getIds()).hasSize(1);
}

@Test
public void search_all_keys_by_query() {
indexRules(
newDoc(RuleKey.of("javascript", "X001")),
newDoc(RuleKey.of("cobol", "X001")),
newDoc(RuleKey.of("php", "S002")));

// key
assertThat(index.searchAll(new RuleQuery().setQueryText("X001"))).hasSize(2);

// partial key does not match
assertThat(index.searchAll(new RuleQuery().setQueryText("X00"))).isEmpty();

// repo:key -> nice-to-have !
assertThat(index.searchAll(new RuleQuery().setQueryText("javascript:X001"))).hasSize(1);
}

private void indexRules(RuleDoc... rules) {
ruleIndexer.index(asList(rules).iterator());
}
}

+ 48
- 0
server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexDefinitionTest.java View File

@@ -0,0 +1,48 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.rule.index;

import org.junit.Test;
import org.sonar.api.config.Settings;
import org.sonar.server.es.IndexDefinition;
import org.sonar.server.es.NewIndex;

import static org.assertj.core.api.Assertions.assertThat;

public class RuleIndexDefinitionTest {

IndexDefinition.IndexDefinitionContext underTest = new IndexDefinition.IndexDefinitionContext();

@Test
public void define() {
RuleIndexDefinition def = new RuleIndexDefinition(new Settings());
def.define(underTest);

assertThat(underTest.getIndices()).hasSize(1);
NewIndex ruleIndex = underTest.getIndices().get("rules");
assertThat(ruleIndex).isNotNull();
assertThat(ruleIndex.getTypes().keySet()).containsOnly("rule");

// no cluster by default
assertThat(ruleIndex.getSettings().get("index.number_of_shards")).isEqualTo(String.valueOf(NewIndex.DEFAULT_NUMBER_OF_SHARDS));
assertThat(ruleIndex.getSettings().get("index.number_of_replicas")).isEqualTo("0");
}

}

+ 0
- 935
server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexMediumTest.java View File

@@ -1,935 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.rule.index;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import org.apache.commons.lang.time.DateUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.db.DbSession;
import org.sonar.db.qualityprofile.ActiveRuleDto;
import org.sonar.db.qualityprofile.QualityProfileDto;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.RuleParamDto;
import org.sonar.db.rule.RuleTesting;
import org.sonar.server.db.DbClient;
import org.sonar.server.qualityprofile.ActiveRule;
import org.sonar.server.qualityprofile.QProfileTesting;
import org.sonar.server.rule.Rule;
import org.sonar.server.rule.db.RuleDao;
import org.sonar.server.search.FacetValue;
import org.sonar.server.search.QueryContext;
import org.sonar.server.search.Result;
import org.sonar.server.tester.ServerTester;
import org.sonar.server.tester.UserSessionRule;

import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;

public class RuleIndexMediumTest {

@ClassRule
public static ServerTester tester = new ServerTester();
@org.junit.Rule
public UserSessionRule userSessionRule = UserSessionRule.forServerTester(tester);

protected DbClient db;

RuleDao dao;
RuleIndex index;
// IndexClient index;
DbSession dbSession;

@Before
public void before() {
dao = tester.get(RuleDao.class);
index = tester.get(RuleIndex.class);
tester.clearDbAndIndexes();
db = tester.get(DbClient.class);
// index = tester.get(IndexClient.class);
dbSession = tester.get(DbClient.class).openSession(false);

}

@After
public void after() {
if (dbSession != null) {
dbSession.close();
}
}

@Test
public void getByKey() {
RuleDto ruleDto = RuleTesting.newDto(RuleKey.of("javascript", "S001"));
dao.insert(dbSession, ruleDto);
dbSession.commit();

Rule rule = index.getByKey(RuleKey.of("javascript", "S001"));

assertThat(rule.htmlDescription()).isEqualTo(ruleDto.getDescription());
assertThat(rule.key()).isEqualTo(ruleDto.getKey());

assertThat(rule.debtRemediationFunction().type().name())
.isEqualTo(ruleDto.getRemediationFunction());

assertThat(Sets.newHashSet(rule.tags())).isEqualTo(ruleDto.getTags());
assertThat(Sets.newHashSet(rule.systemTags())).isEqualTo(ruleDto.getSystemTags());
}

@Test
public void getByKey_null_if_not_found() {
Rule rule = index.getNullableByKey(RuleKey.of("javascript", "unknown"));

assertThat(rule).isNull();
}

@Test
public void global_facet_on_repositories_and_tags() {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("php", "S001"))
.setSystemTags(ImmutableSet.of("sysTag")))
.setTags(ImmutableSet.<String>of());
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("php", "S002"))
.setSystemTags(ImmutableSet.<String>of()))
.setTags(ImmutableSet.of("tag1"));
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("javascript", "S002"))
.setTags(ImmutableSet.of("tag1", "tag2")))
.setSystemTags(ImmutableSet.<String>of());
dbSession.commit();

// should not have any facet!
RuleQuery query = new RuleQuery();
Result result = index.search(query, new QueryContext(userSessionRule));
assertThat(result.getFacets()).isEmpty();

// should not have any facet on non matching query!
result = index.search(new RuleQuery().setQueryText("aeiou"), new QueryContext(userSessionRule).addFacets(Arrays.asList("repositories")));
assertThat(result.getFacets()).isEmpty();

// Repositories Facet is preset
result = index.search(query, new QueryContext(userSessionRule).addFacets(Arrays.asList("repositories", "tags")));
assertThat(result.getFacets()).isNotNull();
assertThat(result.getFacets()).hasSize(2);

// Verify the value of a given facet
Collection<FacetValue> repoFacets = result.getFacetValues("repositories");
assertThat(repoFacets).hasSize(2);
assertThat(Iterables.get(repoFacets, 0).getKey()).isEqualTo("php");
assertThat(Iterables.get(repoFacets, 0).getValue()).isEqualTo(2);
assertThat(Iterables.get(repoFacets, 1).getKey()).isEqualTo("javascript");
assertThat(Iterables.get(repoFacets, 1).getValue()).isEqualTo(1);

// Check that tag facet has both Tags and SystemTags values
Collection<FacetValue> tagFacet = result.getFacetValues("tags");
assertThat(tagFacet).hasSize(3);
assertThat(Iterables.get(tagFacet, 0).getKey()).isEqualTo("tag1");
assertThat(Iterables.get(tagFacet, 0).getValue()).isEqualTo(2);
}

@Test
public void return_all_doc_fields_by_default() {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("javascript", "S001")));
dbSession.commit();

QueryContext options = new QueryContext(userSessionRule).setFieldsToReturn(null);
Result<Rule> results = index.search(new RuleQuery(), options);
assertThat(results.getHits()).hasSize(1);
Rule hit = Iterables.getFirst(results.getHits(), null);
assertThat(hit.key()).isNotNull();
assertThat(hit.htmlDescription()).isNotNull();
assertThat(hit.name()).isNotNull();

options = new QueryContext(userSessionRule).setFieldsToReturn(Collections.<String>emptyList());
results = index.search(new RuleQuery(), options);
assertThat(results.getHits()).hasSize(1);
hit = Iterables.getFirst(results.getHits(), null);
assertThat(hit.key()).isNotNull();
assertThat(hit.htmlDescription()).isNotNull();
assertThat(hit.name()).isNotNull();
}

@Test
public void select_doc_fields_to_return() {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("javascript", "S001")));
dbSession.commit();

QueryContext options = new QueryContext(userSessionRule);
options.addFieldsToReturn(RuleNormalizer.RuleField.LANGUAGE.field(), RuleNormalizer.RuleField.STATUS.field());
Result<Rule> results = index.search(new RuleQuery(), options);
assertThat(results.getHits()).hasSize(1);

Rule hit = Iterables.getFirst(results.getHits(), null);
assertThat(hit.language()).isEqualTo("js");
assertThat(hit.status()).isEqualTo(RuleStatus.READY);

try {
hit.htmlDescription();
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Field htmlDesc not specified in query options");
}
}

@Test
public void search_name_by_query() {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("javascript", "S001"))
.setName("testing the partial match and matching of rule"));
dbSession.commit();

// substring
RuleQuery query = new RuleQuery().setQueryText("test");
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(1);

// substring
query = new RuleQuery().setQueryText("partial match");
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(1);

// case-insensitive
query = new RuleQuery().setQueryText("TESTING");
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(1);

// not found
query = new RuleQuery().setQueryText("not present");
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).isEmpty();
}

@Test
public void search_key_by_query() {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("javascript", "X001")));
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("cobol", "X001")));
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("php", "S002")));
dbSession.commit();

// key
RuleQuery query = new RuleQuery().setQueryText("X001");
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(2);

// partial key does not match
query = new RuleQuery().setQueryText("X00");
// TODO fix non-partial match for Key search
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).isEmpty();

// repo:key -> nice-to-have !
query = new RuleQuery().setQueryText("javascript:X001");
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(1);
}

@Test
public void filter_by_key() {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("javascript", "X001")));
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("cobol", "X001")));
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("php", "S002")));
dbSession.commit();

// key
RuleQuery query = new RuleQuery().setKey(RuleKey.of("javascript", "X001").toString());
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(1);

// partial key does not match
query = new RuleQuery().setKey("X001");
// TODO fix non-partial match for Key search
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).isEmpty();
}

@Test
public void search_all_rules() {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("javascript", "S001")));
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S002")));
dbSession.commit();

Result results = index.search(new RuleQuery(), new QueryContext(userSessionRule));

assertThat(results.getTotal()).isEqualTo(2);
assertThat(results.getHits()).hasSize(2);
}

@Test
public void scroll_all_rules() {
int max = 100;
for (int i = 0; i < max; i++) {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "scroll_" + i)));
}
dbSession.commit();

Result results = index.search(new RuleQuery(), new QueryContext(userSessionRule).setScroll(true));

assertThat(results.getTotal()).isEqualTo(max);
assertThat(results.getHits()).hasSize(0);

Iterator<Rule> it = results.scroll();
int count = 0;
while (it.hasNext()) {
count++;
it.next();
}
assertThat(count).isEqualTo(max);

}

@Test
public void search_by_any_of_repositories() {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("findbugs", "S001")));
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("pmd", "S002")));
dbSession.commit();

RuleQuery query = new RuleQuery().setRepositories(Arrays.asList("checkstyle", "pmd"));
Result<Rule> results = index.search(query, new QueryContext(userSessionRule));
assertThat(results.getHits()).hasSize(1);
assertThat(Iterables.getFirst(results.getHits(), null).key().rule()).isEqualTo("S002");

// no results
query = new RuleQuery().setRepositories(Arrays.asList("checkstyle"));
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).isEmpty();

// empty list => no filter
query = new RuleQuery().setRepositories(Collections.<String>emptyList());
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(2);
}

@Test
public void search_by_any_of_languages() {
dao.insert(dbSession,
RuleTesting.newDto(RuleKey.of("java", "S001")).setLanguage("java"),
RuleTesting.newDto(RuleKey.of("javascript", "S002")).setLanguage("js"));
dbSession.commit();

RuleQuery query = new RuleQuery().setLanguages(Arrays.asList("cobol", "js"));
Result<Rule> results = index.search(query, new QueryContext(userSessionRule));

assertThat(results.getHits()).hasSize(1);
assertThat(Iterables.getFirst(results.getHits(), null).key().rule()).isEqualTo("S002");

// no results
query = new RuleQuery().setLanguages(Arrays.asList("cpp"));
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).isEmpty();

// empty list => no filter
query = new RuleQuery().setLanguages(Collections.<String>emptyList());
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(2);

// null list => no filter
query = new RuleQuery().setLanguages(null);
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(2);
}

@Test
public void search_by_any_of_severities() {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S001")).setSeverity(Severity.BLOCKER));
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S002")).setSeverity(Severity.INFO));
dbSession.commit();

RuleQuery query = new RuleQuery().setSeverities(Arrays.asList(Severity.INFO, Severity.MINOR));
Result<Rule> results = index.search(query, new QueryContext(userSessionRule));
assertThat(results.getHits()).hasSize(1);
assertThat(Iterables.getFirst(results.getHits(), null).key().rule()).isEqualTo("S002");

// no results
query = new RuleQuery().setSeverities(Arrays.asList(Severity.MINOR));
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).isEmpty();

// empty list => no filter
query = new RuleQuery().setSeverities(Collections.<String>emptyList());
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(2);

// null list => no filter
query = new RuleQuery().setSeverities(null);
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(2);
}

@Test
public void search_by_any_of_statuses() {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S001")).setStatus(RuleStatus.BETA));
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S002")).setStatus(RuleStatus.READY));
dbSession.commit();

RuleQuery query = new RuleQuery().setStatuses(Arrays.asList(RuleStatus.DEPRECATED, RuleStatus.READY));
Result<Rule> results = index.search(query, new QueryContext(userSessionRule));
assertThat(results.getHits()).hasSize(1);
assertThat(Iterables.getFirst(results.getHits(), null).key().rule()).isEqualTo("S002");

// no results
query = new RuleQuery().setStatuses(Arrays.asList(RuleStatus.DEPRECATED));
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).isEmpty();

// empty list => no filter
query = new RuleQuery().setStatuses(Collections.<RuleStatus>emptyList());
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(2);

// null list => no filter
query = new RuleQuery().setStatuses(null);
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(2);
}

@Test
public void sort_by_name() {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S001")).setName("abcd"));
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S002")).setName("ABC"));
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S003")).setName("FGH"));
dbSession.commit();

// ascending
RuleQuery query = new RuleQuery().setSortField(RuleNormalizer.RuleField.NAME);
Result<Rule> results = index.search(query, new QueryContext(userSessionRule));
assertThat(results.getHits()).hasSize(3);
assertThat(Iterables.getFirst(results.getHits(), null).key().rule()).isEqualTo("S002");
assertThat(Iterables.getLast(results.getHits(), null).key().rule()).isEqualTo("S003");

// descending
query = new RuleQuery().setSortField(RuleNormalizer.RuleField.NAME).setAscendingSort(false);
results = index.search(query, new QueryContext(userSessionRule));
assertThat(results.getHits()).hasSize(3);
assertThat(Iterables.getFirst(results.getHits(), null).key().rule()).isEqualTo("S003");
assertThat(Iterables.getLast(results.getHits(), null).key().rule()).isEqualTo("S002");
}

@Test
public void fail_sort_by_language() {
try {
// Sorting on a field not tagged as sortable
new RuleQuery().setSortField(RuleNormalizer.RuleField.LANGUAGE);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Field 'lang' is not sortable");
}
}

@Test
public void search_by_profile() {
QualityProfileDto qualityProfileDto1 = QProfileTesting.newXooP1();
QualityProfileDto qualityProfileDto2 = QProfileTesting.newXooP2();
db.qualityProfileDao().insert(dbSession, qualityProfileDto1, qualityProfileDto2);

RuleDto rule1 = RuleTesting.newXooX1();
RuleDto rule2 = RuleTesting.newXooX2();
RuleDto rule3 = RuleTesting.newXooX3();
dao.insert(dbSession, rule1, rule2, rule3);

db.activeRuleDao().insert(
dbSession,
ActiveRuleDto.createFor(qualityProfileDto1, rule1).setSeverity("BLOCKER"),
ActiveRuleDto.createFor(qualityProfileDto2, rule1).setSeverity("BLOCKER"),
ActiveRuleDto.createFor(qualityProfileDto1, rule2).setSeverity("BLOCKER"));
dbSession.commit();
dbSession.clearCache();

// 1. get all active rules.
Result<Rule> result = index.search(new RuleQuery().setActivation(true),
new QueryContext(userSessionRule));
assertThat(result.getHits()).hasSize(2);

// 2. get all inactive rules.
result = index.search(new RuleQuery().setActivation(false),
new QueryContext(userSessionRule));
assertThat(result.getHits()).hasSize(1);
assertThat(result.getHits().get(0).name()).isEqualTo(rule3.getName());

// 3. get all rules not active on profile
index.search(new RuleQuery().setActivation(false).setQProfileKey(qualityProfileDto2.getKey()),
new QueryContext(userSessionRule));
// TODO
assertThat(result.getHits()).hasSize(1);

// 4. get all active rules on profile
result = index.search(new RuleQuery().setActivation(true)
.setQProfileKey(qualityProfileDto2.getKey()),
new QueryContext(userSessionRule));
assertThat(result.getHits()).hasSize(1);
assertThat(result.getHits().get(0).name()).isEqualTo(rule1.getName());

}

@Test
public void search_by_profile_and_inheritance() {
QualityProfileDto qualityProfileDto1 = QProfileTesting.newXooP1();
QualityProfileDto qualityProfileDto2 = QProfileTesting.newXooP2().setParentKee(QProfileTesting.XOO_P1_KEY);
db.qualityProfileDao().insert(dbSession, qualityProfileDto1, qualityProfileDto2);

RuleDto rule1 = RuleTesting.newDto(RuleKey.of("xoo", "S001"));
RuleDto rule2 = RuleTesting.newDto(RuleKey.of("xoo", "S002"));
RuleDto rule3 = RuleTesting.newDto(RuleKey.of("xoo", "S003"));
RuleDto rule4 = RuleTesting.newDto(RuleKey.of("xoo", "S004"));
dao.insert(dbSession, rule1, rule2, rule3, rule4);

db.activeRuleDao().insert(
dbSession,
ActiveRuleDto.createFor(qualityProfileDto1, rule1)
.setSeverity("BLOCKER"),
ActiveRuleDto.createFor(qualityProfileDto1, rule2)
.setSeverity("BLOCKER"),
ActiveRuleDto.createFor(qualityProfileDto1, rule3)
.setSeverity("BLOCKER"),

ActiveRuleDto.createFor(qualityProfileDto2, rule1)
.setSeverity("MINOR")
.setInheritance(ActiveRule.Inheritance.INHERITED.name()),
ActiveRuleDto.createFor(qualityProfileDto2, rule2)
.setSeverity("BLOCKER")
.setInheritance(ActiveRule.Inheritance.OVERRIDES.name()),
ActiveRuleDto.createFor(qualityProfileDto2, rule3)
.setSeverity("BLOCKER")
.setInheritance(ActiveRule.Inheritance.INHERITED.name())
);

dbSession.commit();

// 0. get all rules
Result<Rule> result = index.search(new RuleQuery(),
new QueryContext(userSessionRule));
assertThat(result.getHits()).hasSize(4);

// 1. get all active rules
result = index.search(new RuleQuery().setActivation(true),
new QueryContext(userSessionRule));
assertThat(result.getHits()).hasSize(3);

// 2. get all inactive rules.
result = index.search(new RuleQuery().setActivation(false),
new QueryContext(userSessionRule));
assertThat(result.getHits()).hasSize(1);
assertThat(result.getHits().get(0).name()).isEqualTo(rule4.getName());

// 3. get Inherited Rules on profile1
result = index.search(new RuleQuery().setActivation(true)
.setQProfileKey(qualityProfileDto1.getKey())
.setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())),
new QueryContext(userSessionRule)
);
assertThat(result.getHits()).hasSize(0);

// 4. get Inherited Rules on profile2
result = index.search(new RuleQuery().setActivation(true)
.setQProfileKey(qualityProfileDto2.getKey())
.setInheritance(ImmutableSet.of(ActiveRule.Inheritance.INHERITED.name())),
new QueryContext(userSessionRule)
);
assertThat(result.getHits()).hasSize(2);

// 5. get Overridden Rules on profile1
result = index.search(new RuleQuery().setActivation(true)
.setQProfileKey(qualityProfileDto1.getKey())
.setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())),
new QueryContext(userSessionRule)
);
assertThat(result.getHits()).hasSize(0);

// 6. get Overridden Rules on profile2
result = index.search(new RuleQuery().setActivation(true)
.setQProfileKey(qualityProfileDto2.getKey())
.setInheritance(ImmutableSet.of(ActiveRule.Inheritance.OVERRIDES.name())),
new QueryContext(userSessionRule)
);
assertThat(result.getHits()).hasSize(1);

// 7. get Inherited AND Overridden Rules on profile1
result = index.search(new RuleQuery().setActivation(true)
.setQProfileKey(qualityProfileDto1.getKey())
.setInheritance(ImmutableSet.of(
ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())),
new QueryContext(userSessionRule)
);
assertThat(result.getHits()).hasSize(0);

// 8. get Inherited AND Overridden Rules on profile2
result = index.search(new RuleQuery().setActivation(true)
.setQProfileKey(qualityProfileDto2.getKey())
.setInheritance(ImmutableSet.of(
ActiveRule.Inheritance.INHERITED.name(), ActiveRule.Inheritance.OVERRIDES.name())),
new QueryContext(userSessionRule)
);
assertThat(result.getHits()).hasSize(3);
}

@Test
public void search_by_profile_and_active_severity() {
QualityProfileDto qualityProfileDto1 = QProfileTesting.newXooP1();
QualityProfileDto qualityProfileDto2 = QProfileTesting.newXooP2();
db.qualityProfileDao().insert(dbSession, qualityProfileDto1, qualityProfileDto2);

RuleDto rule1 = RuleTesting.newXooX1().setSeverity("MAJOR");
RuleDto rule2 = RuleTesting.newXooX2().setSeverity("MINOR");
RuleDto rule3 = RuleTesting.newXooX3().setSeverity("INFO");
dao.insert(dbSession, rule1, rule2, rule3);

db.activeRuleDao().insert(
dbSession,
ActiveRuleDto.createFor(qualityProfileDto1, rule1).setSeverity("BLOCKER"),
ActiveRuleDto.createFor(qualityProfileDto2, rule1).setSeverity("BLOCKER"),
ActiveRuleDto.createFor(qualityProfileDto1, rule2).setSeverity("CRITICAL"));
dbSession.commit();
dbSession.clearCache();

// 1. get all active rules.
Result<Rule> result = index.search(new RuleQuery().setActivation(true).setQProfileKey(qualityProfileDto1.getKey()),
new QueryContext(userSessionRule));
assertThat(result.getHits()).hasSize(2);

// 2. get rules with active severity critical.
result = index.search(new RuleQuery().setActivation(true).setQProfileKey(qualityProfileDto1.getKey()).setActiveSeverities(Arrays.asList("CRITICAL")),
new QueryContext(userSessionRule).addFacets(Arrays.asList(RuleIndex.FACET_ACTIVE_SEVERITIES)));
assertThat(result.getHits()).hasSize(1);
assertThat(result.getHits().get(0).name()).isEqualTo(rule2.getName());
// check stickyness of active severity facet
assertThat(result.getFacetValues(RuleIndex.FACET_ACTIVE_SEVERITIES)).containsOnly(new FacetValue("BLOCKER", 1), new FacetValue("CRITICAL", 1));

// 3. count activation severities of all active rules
result = index.search(new RuleQuery(),
new QueryContext(userSessionRule).addFacets(Arrays.asList(RuleIndex.FACET_ACTIVE_SEVERITIES)));
assertThat(result.getHits()).hasSize(3);
assertThat(result.getFacetValues(RuleIndex.FACET_ACTIVE_SEVERITIES)).containsOnly(new FacetValue("BLOCKER", 2), new FacetValue("CRITICAL", 1));
}

@Test
public void complex_param_value() {
String value = "//expression[primary/qualifiedIdentifier[count(IDENTIFIER) = 2]/IDENTIFIER[2]/@tokenValue = 'firstOf' and primary/identifierSuffix/arguments/expression[not(primary) or primary[not(qualifiedIdentifier) or identifierSuffix]]]";

QualityProfileDto profile = QProfileTesting.newXooP1();
db.qualityProfileDao().insert(dbSession, profile);

RuleDto rule = RuleTesting.newXooX1();
dao.insert(dbSession, rule);

RuleParamDto param = RuleParamDto.createFor(rule)
.setName("testing")
.setType("STRING")
.setDefaultValue(value);
dao.insertRuleParam(dbSession, rule, param);

dbSession.commit();

assertThat(index.getByKey(rule.getKey()).params()).hasSize(1);
assertThat(index.getByKey(rule.getKey()).params().get(0).defaultValue()).isEqualTo(value);
}

@Test
public void search_by_tag() {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S001")).setTags(ImmutableSet.of("tag1")));
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S002")).setTags(ImmutableSet.of("tag2")));
dbSession.commit();

// find all
RuleQuery query = new RuleQuery();
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(2);

// tag1 in query
query = new RuleQuery().setQueryText("tag1");
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(1);
assertThat(Iterables.getFirst(index.search(query, new QueryContext(userSessionRule)).getHits(), null).tags()).containsExactly("tag1");

// tag1 and tag2 in query
query = new RuleQuery().setQueryText("tag1 tag2");
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(2);

// tag2 in filter
query = new RuleQuery().setTags(ImmutableSet.of("tag2"));
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(1);
assertThat(Iterables.getFirst(index.search(query, new QueryContext(userSessionRule)).getHits(), null).tags()).containsExactly("tag2");

// tag2 in filter and tag1 tag2 in query
query = new RuleQuery().setTags(ImmutableSet.of("tag2")).setQueryText("tag1");
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(0);

// tag2 in filter and tag1 in query
query = new RuleQuery().setTags(ImmutableSet.of("tag2")).setQueryText("tag1 tag2");
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(1);
assertThat(Iterables.getFirst(index.search(query, new QueryContext(userSessionRule)).getHits(), null).tags()).containsExactly("tag2");

// null list => no filter
query = new RuleQuery().setTags(Collections.<String>emptySet());
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(2);

// null list => no filter
query = new RuleQuery().setTags(null);
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(2);
}

@Test
public void search_by_is_template() {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S001")).setIsTemplate(false));
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S002")).setIsTemplate(true));
dbSession.commit();

// find all
RuleQuery query = new RuleQuery();
Result<Rule> results = index.search(query, new QueryContext(userSessionRule));
assertThat(results.getHits()).hasSize(2);

// Only template
query = new RuleQuery().setIsTemplate(true);
results = index.search(query, new QueryContext(userSessionRule));
assertThat(results.getHits()).hasSize(1);
assertThat(Iterables.getFirst(results.getHits(), null).key().rule()).isEqualTo("S002");
assertThat(Iterables.getFirst(results.getHits(), null).isTemplate()).isTrue();

// Only not template
query = new RuleQuery().setIsTemplate(false);
results = index.search(query, new QueryContext(userSessionRule));
assertThat(results.getHits()).hasSize(1);
assertThat(Iterables.getFirst(results.getHits(), null).isTemplate()).isFalse();
assertThat(Iterables.getFirst(results.getHits(), null).key().rule()).isEqualTo("S001");

// null => no filter
query = new RuleQuery().setIsTemplate(null);
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(2);
}

@Test
public void search_by_template_key() {
RuleDto templateRule = RuleTesting.newDto(RuleKey.of("java", "S001")).setIsTemplate(true);
dao.insert(dbSession, templateRule);
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S001_MY_CUSTOM")).setTemplateId(templateRule.getId()));
dbSession.commit();

// find all
RuleQuery query = new RuleQuery();
Result<Rule> results = index.search(query, new QueryContext(userSessionRule));
assertThat(results.getHits()).hasSize(2);

// Only custom rule
query = new RuleQuery().setTemplateKey("java:S001");
results = index.search(query, new QueryContext(userSessionRule));
assertThat(results.getHits()).hasSize(1);
assertThat(Iterables.getFirst(results.getHits(), null).key().rule()).isEqualTo("S001_MY_CUSTOM");
assertThat(Iterables.getFirst(results.getHits(), null).templateKey()).isEqualTo(RuleKey.of("java", "S001"));

// null => no filter
query = new RuleQuery().setTemplateKey(null);
assertThat(index.search(query, new QueryContext(userSessionRule)).getHits()).hasSize(2);
}

@Test
public void search_by_template_key_with_params() {
RuleDto templateRule = RuleTesting.newDto(RuleKey.of("java", "S001")).setIsTemplate(true);
RuleParamDto ruleParamDto = RuleParamDto.createFor(templateRule).setName("regex").setType("STRING").setDescription("Reg ex").setDefaultValue(".*");
dao.insert(dbSession, templateRule);
dao.insertRuleParam(dbSession, templateRule, ruleParamDto);

RuleDto customRule = RuleTesting.newDto(RuleKey.of("java", "S001_MY_CUSTOM")).setTemplateId(templateRule.getId());
RuleParamDto customRuleParam = RuleParamDto.createFor(customRule).setName("regex").setType("STRING").setDescription("Reg ex").setDefaultValue("a.*");
dao.insert(dbSession, customRule);
dao.insertRuleParam(dbSession, customRule, customRuleParam);
dbSession.commit();

// find all
RuleQuery query = new RuleQuery();
Result<Rule> results = index.search(query, new QueryContext(userSessionRule));
assertThat(results.getHits()).hasSize(2);

// get params
assertThat(index.getByKey(templateRule.getKey()).params()).hasSize(1);
assertThat(index.getByKey(customRule.getKey()).params()).hasSize(1);
}

@Test
public void show_custom_rule() {
RuleDto templateRule = RuleTesting.newDto(RuleKey.of("java", "S001")).setIsTemplate(true);
dao.insert(dbSession, templateRule);
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S001_MY_CUSTOM")).setTemplateId(templateRule.getId()));
dbSession.commit();

// find all
RuleQuery query = new RuleQuery();
Result<Rule> results = index.search(query, new QueryContext(userSessionRule));
assertThat(results.getHits()).hasSize(2);

// find custom rule
assertThat(index.getByKey(RuleKey.of("java", "S001_MY_CUSTOM")).templateKey()).isEqualTo(RuleKey.of("java", "S001"));
}

@Test
public void paging() {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S001")));
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S002")));
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S003")));
dbSession.commit();

// from 0 to 1 included
QueryContext options = new QueryContext(userSessionRule);
options.setOffset(0).setLimit(2);
Result results = index.search(new RuleQuery(), options);
assertThat(results.getTotal()).isEqualTo(3);
assertThat(results.getHits()).hasSize(2);

// from 0 to 9 included
options.setOffset(0).setLimit(10);
results = index.search(new RuleQuery(), options);
assertThat(results.getTotal()).isEqualTo(3);
assertThat(results.getHits()).hasSize(3);

// from 2 to 11 included
options.setOffset(2).setLimit(10);
results = index.search(new RuleQuery(), options);
assertThat(results.getTotal()).isEqualTo(3);
assertThat(results.getHits()).hasSize(1);

// from 2 to 11 included
options.setOffset(2).setLimit(0);
results = index.search(new RuleQuery(), options);
assertThat(results.getTotal()).isEqualTo(3);
assertThat(results.getHits()).hasSize(0);
}

@Test
public void available_since() throws InterruptedException {
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S001")));
dbSession.commit();

Date since = new Date();
dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("java", "S002")));
dbSession.commit();

// 0. find all rules;
assertThat(index.search(new RuleQuery(), new QueryContext(userSessionRule)).getHits()).hasSize(2);

// 1. find all rules available since a date;
RuleQuery availableSinceQuery = new RuleQuery()
.setAvailableSince(since);
List<Rule> hits = index.search(availableSinceQuery, new QueryContext(userSessionRule)).getHits();
assertThat(hits).hasSize(1);
assertThat(hits.get(0).key()).isEqualTo(RuleKey.of("java", "S002"));

// 2. find no new rules since tomorrow.
RuleQuery availableSinceNowQuery = new RuleQuery()
.setAvailableSince(DateUtils.addDays(since, 1));
assertThat(index.search(availableSinceNowQuery, new QueryContext(userSessionRule)).getHits()).hasSize(0);
}

@Test
public void scroll_byIds() {
Set<Integer> ids = new HashSet<>();
for (int i = 0; i < 150; i++) {
RuleDto rule = RuleTesting.newDto(RuleKey.of("scroll", "r_" + i));
dao.insert(dbSession, rule);
dbSession.commit();
ids.add(rule.getId());
}
List<Rule> rules = index.getByIds(ids);
assertThat(rules).hasSize(ids.size());
}

@Test
public void search_protected_chars() {
String nameWithProtectedChars = "ja#va&sc\"r:ipt";

RuleDto ruleDto = RuleTesting.newXooX1().setName(nameWithProtectedChars);
dao.insert(dbSession, ruleDto);
dbSession.commit();

Rule rule = index.getByKey(RuleTesting.XOO_X1);
assertThat(rule.name()).isEqualTo(nameWithProtectedChars);

RuleQuery protectedCharsQuery = new RuleQuery().setQueryText(nameWithProtectedChars);
List<Rule> results = index.search(protectedCharsQuery, new QueryContext(userSessionRule)).getHits();
assertThat(results).hasSize(1);
assertThat(results.get(0).key()).isEqualTo(RuleTesting.XOO_X1);
}

@Test
public void sticky_facets() {

dao.insert(dbSession,
RuleTesting.newDto(RuleKey.of("xoo", "S001")).setLanguage("java").setTags(ImmutableSet.<String>of()),
RuleTesting.newDto(RuleKey.of("xoo", "S002")).setLanguage("java").setTags(ImmutableSet.<String>of()),
RuleTesting.newDto(RuleKey.of("xoo", "S003")).setLanguage("java").setTags(ImmutableSet.<String>of("T1", "T2")),
RuleTesting.newDto(RuleKey.of("xoo", "S011")).setLanguage("cobol").setTags(ImmutableSet.<String>of()),
RuleTesting.newDto(RuleKey.of("xoo", "S012")).setLanguage("cobol").setTags(ImmutableSet.<String>of()),
RuleTesting.newDto(RuleKey.of("foo", "S013")).setLanguage("cobol").setTags(ImmutableSet.<String>of("T3", "T4")),
RuleTesting.newDto(RuleKey.of("foo", "S111")).setLanguage("cpp").setTags(ImmutableSet.<String>of()),
RuleTesting.newDto(RuleKey.of("foo", "S112")).setLanguage("cpp").setTags(ImmutableSet.<String>of()),
RuleTesting.newDto(RuleKey.of("foo", "S113")).setLanguage("cpp").setTags(ImmutableSet.<String>of("T2", "T3")));
dbSession.commit();

// 0 assert Base
assertThat(index.countAll()).isEqualTo(9);
assertThat(index.search(new RuleQuery(), new QueryContext(userSessionRule)).getHits()).hasSize(9);

// 1 Facet with no filters at all
Map<String, Collection<FacetValue>> facets = index.search(new RuleQuery(), new QueryContext(userSessionRule).addFacets(Arrays.asList("languages", "repositories", "tags")))
.getFacets();
assertThat(facets.keySet()).hasSize(3);
assertThat(facets.get(RuleIndex.FACET_LANGUAGES)).extracting("key").containsOnly("cpp", "java", "cobol");
assertThat(facets.get(RuleIndex.FACET_REPOSITORIES)).extracting("key").containsOnly("xoo", "foo");
assertThat(facets.get(RuleIndex.FACET_TAGS)).extracting("key").containsOnly("systag1", "systag2", "T1", "T2", "T3", "T4");

// 2 Facet with a language filter
// -- lang facet should still have all language
Result<Rule> result = index.search(new RuleQuery()
.setLanguages(ImmutableList.<String>of("cpp"))
, new QueryContext(userSessionRule).addFacets(Arrays.asList("languages", "repositories", "tags")));
assertThat(result.getHits()).hasSize(3);
assertThat(result.getFacets()).hasSize(3);
assertThat(result.getFacets().get(RuleIndex.FACET_LANGUAGES)).extracting("key").containsOnly("cpp", "java", "cobol");

// 3 facet with 2 filters
// -- lang facet for tag T2
// -- tag facet for lang cpp
// -- repository for cpp & T2
result = index.search(new RuleQuery()
.setLanguages(ImmutableList.<String>of("cpp"))
.setTags(ImmutableList.<String>of("T2"))
, new QueryContext(userSessionRule).addFacets(Arrays.asList("languages", "repositories", "tags")));
assertThat(result.getHits()).hasSize(1);
assertThat(result.getFacets().keySet()).hasSize(3);
assertThat(result.getFacets().get(RuleIndex.FACET_LANGUAGES)).extracting("key").containsOnly("cpp", "java");
assertThat(result.getFacets().get(RuleIndex.FACET_REPOSITORIES)).extracting("key").containsOnly("foo");
assertThat(result.getFacets().get(RuleIndex.FACET_TAGS)).extracting("key").containsOnly("systag1", "systag2", "T2", "T3");

// 4 facet with 2 filters
// -- lang facet for tag T2
// -- tag facet for lang cpp & java
// -- repository for (cpp || java) & T2
result = index.search(new RuleQuery()
.setLanguages(ImmutableList.<String>of("cpp", "java"))
.setTags(ImmutableList.<String>of("T2"))
, new QueryContext(userSessionRule).addFacets(Arrays.asList("languages", "repositories", "tags")));
assertThat(result.getHits()).hasSize(2);
assertThat(result.getFacets().keySet()).hasSize(3);
assertThat(result.getFacets().get(RuleIndex.FACET_LANGUAGES)).extracting("key").containsOnly("cpp", "java");
assertThat(result.getFacets().get(RuleIndex.FACET_REPOSITORIES)).extracting("key").containsOnly("foo", "xoo");
assertThat(result.getFacets().get(RuleIndex.FACET_TAGS)).extracting("key").containsOnly("systag1", "systag2", "T1", "T2", "T3");
}

private static List<String> ruleKeys(List<Rule> rules) {
return newArrayList(Iterables.transform(rules, new Function<Rule, String>() {
@Override
public String apply(@Nullable Rule input) {
return input != null ? input.key().rule() : null;
}
}));
}
}

+ 109
- 0
server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexerTest.java View File

@@ -0,0 +1,109 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.rule.index;

import com.google.common.collect.Iterators;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.sonar.api.config.Settings;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.utils.System2;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.RuleTesting;
import org.sonar.server.es.EsTester;
import org.sonar.test.DbTests;

import static org.assertj.core.api.Assertions.assertThat;

@Category(DbTests.class)
public class RuleIndexerTest {

@ClassRule
public static EsTester esTester = new EsTester().addDefinitions(new RuleIndexDefinition(new Settings()));

@Rule
public DbTester dbTester = DbTester.create(System2.INSTANCE);

@Before
public void setUp() {
esTester.truncateIndices();
}

@Test
public void index_nothing() {
RuleIndexer indexer = createIndexer();
indexer.index(Iterators.<RuleDoc>emptyIterator());
assertThat(esTester.countDocuments(RuleIndexDefinition.INDEX, RuleIndexDefinition.TYPE_RULE)).isEqualTo(0L);
}

@Test
public void index_nothing_if_disabled() {
dbTester.prepareDbUnit(getClass(), "index.xml");

createIndexer().setEnabled(false).index();

assertThat(esTester.countDocuments(RuleIndexDefinition.INDEX, RuleIndexDefinition.TYPE_RULE)).isZero();
}

@Test
public void index() {
dbTester.prepareDbUnit(getClass(), "index.xml");

RuleIndexer indexer = createIndexer();
indexer.index();

assertThat(esTester.countDocuments(RuleIndexDefinition.INDEX, RuleIndexDefinition.TYPE_RULE)).isEqualTo(1);
}

@Test
public void removed_rule_is_removed_from_index() {
RuleIndexer indexer = createIndexer();

// Create and Index rule
RuleDto ruleDto = RuleTesting.newDto(RuleKey.of("xoo", "S001"))
.setStatus(RuleStatus.READY)
.setUpdatedAtInMs(1000L);
dbTester.getDbClient().ruleDao().insert(dbTester.getSession(), ruleDto);
dbTester.getSession().commit();
indexer.index();

// Remove rule
ruleDto.setStatus(RuleStatus.REMOVED);
ruleDto.setUpdatedAtInMs(2000L);
dbTester.getDbClient().ruleDao().update(dbTester.getSession(), ruleDto);
dbTester.getSession().commit();
indexer.index();

assertThat(esTester.countDocuments(RuleIndexDefinition.INDEX, RuleIndexDefinition.TYPE_RULE)).isZero();
}

private RuleIndexer createIndexer() {
RuleIndexer indexer = new RuleIndexer(new DbClient(dbTester.database(), dbTester.myBatis()), esTester.client());
indexer.setEnabled(true);
return indexer;
}

}

+ 169
- 0
server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleResultSetIteratorTest.java View File

@@ -0,0 +1,169 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.rule.index;

import com.google.common.base.Function;
import com.google.common.collect.Maps;
import java.util.Map;
import javax.annotation.Nonnull;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
import org.sonar.test.DbTests;

import static org.assertj.core.api.Assertions.assertThat;

@Category(DbTests.class)
public class RuleResultSetIteratorTest {

@Rule
public DbTester dbTester = DbTester.create(System2.INSTANCE);

@Before
public void setUp() {
dbTester.truncateTables();
}

@Test
public void iterator_over_one_rule() {
dbTester.prepareDbUnit(getClass(), "one_rule.xml");
RuleResultSetIterator it = RuleResultSetIterator.create(dbTester.getDbClient(), dbTester.getSession(), 0L);
Map<String, RuleDoc> rulesByKey = rulesByKey(it);
it.close();

assertThat(rulesByKey).hasSize(1);

RuleDoc rule = rulesByKey.get("S001");
assertThat(rule.key()).isEqualTo(RuleKey.of("xoo", "S001"));
assertThat(rule.keyAsList()).containsOnly("xoo", "S001");
assertThat(rule.ruleKey()).isEqualTo("S001");
assertThat(rule.repository()).isEqualTo("xoo");
assertThat(rule.internalKey()).isEqualTo("S1");
assertThat(rule.name()).isEqualTo("Null Pointer");
assertThat(rule.htmlDescription()).isEqualTo("S001 desc");
assertThat(rule.language()).isEqualTo("xoo");
assertThat(rule.severity()).isEqualTo(Severity.BLOCKER);
assertThat(rule.status()).isEqualTo(RuleStatus.READY);
assertThat(rule.isTemplate()).isFalse();
assertThat(rule.allTags()).containsOnly("bug", "performance", "cwe");
assertThat(rule.createdAtAsLong()).isEqualTo(1500000000000L);
assertThat(rule.updatedAtAtAsLong()).isEqualTo(1600000000000L);
}

@Test
public void select_after_date() {
dbTester.prepareDbUnit(getClass(), "shared.xml");
RuleResultSetIterator it = RuleResultSetIterator.create(dbTester.getDbClient(), dbTester.getSession(), 1_900_000_000_000L);

assertThat(it.hasNext()).isTrue();
RuleDoc issue = it.next();
assertThat(issue.key()).isEqualTo(RuleKey.of("xoo", "S002"));

assertThat(it.hasNext()).isFalse();
it.close();
}

@Test
public void iterator_over_rules() {
dbTester.prepareDbUnit(getClass(), "shared.xml");

RuleResultSetIterator it = RuleResultSetIterator.create(dbTester.getDbClient(), dbTester.getSession(), 0L);
Map<String, RuleDoc> rulesByKey = rulesByKey(it);
it.close();

assertThat(rulesByKey).hasSize(2);

RuleDoc rule = rulesByKey.get("S001");
assertThat(rule.key()).isEqualTo(RuleKey.of("xoo", "S001"));
assertThat(rule.keyAsList()).containsOnly("xoo", "S001");
assertThat(rule.ruleKey()).isEqualTo("S001");
assertThat(rule.repository()).isEqualTo("xoo");
assertThat(rule.internalKey()).isEqualTo("S1");
assertThat(rule.name()).isEqualTo("Null Pointer");
assertThat(rule.htmlDescription()).isEqualTo("S001 desc");
assertThat(rule.language()).isEqualTo("xoo");
assertThat(rule.severity()).isEqualTo(Severity.BLOCKER);
assertThat(rule.status()).isEqualTo(RuleStatus.READY);
assertThat(rule.isTemplate()).isFalse();
assertThat(rule.allTags()).containsOnly("bug", "performance", "cwe");
assertThat(rule.createdAtAsLong()).isEqualTo(1500000000000L);
assertThat(rule.updatedAtAtAsLong()).isEqualTo(1600000000000L);

rule = rulesByKey.get("S002");
assertThat(rule.key()).isEqualTo(RuleKey.of("xoo", "S002"));
assertThat(rule.keyAsList()).containsOnly("xoo", "S002");
assertThat(rule.ruleKey()).isEqualTo("S002");
assertThat(rule.repository()).isEqualTo("xoo");
assertThat(rule.internalKey()).isEqualTo("S2");
assertThat(rule.name()).isEqualTo("Slow");
assertThat(rule.htmlDescription()).isEqualTo("<strong>S002 desc</strong>");
assertThat(rule.language()).isEqualTo("xoo");
assertThat(rule.severity()).isEqualTo(Severity.CRITICAL);
assertThat(rule.status()).isEqualTo(RuleStatus.BETA);
assertThat(rule.isTemplate()).isTrue();
assertThat(rule.allTags()).isEmpty();
assertThat(rule.createdAtAsLong()).isEqualTo(2000000000000L);
assertThat(rule.updatedAtAtAsLong()).isEqualTo(2100000000000L);
}

@Test
public void custom_rule() {
dbTester.prepareDbUnit(getClass(), "custom_rule.xml");

RuleResultSetIterator it = RuleResultSetIterator.create(dbTester.getDbClient(), dbTester.getSession(), 0L);
Map<String, RuleDoc> rulesByKey = rulesByKey(it);
it.close();

assertThat(rulesByKey).hasSize(2);

RuleDoc rule = rulesByKey.get("S001");
assertThat(rule.isTemplate()).isTrue();
assertThat(rule.templateKey()).isNull();

rule = rulesByKey.get("S002");
assertThat(rule.isTemplate()).isFalse();
assertThat(rule.templateKey()).isEqualTo(RuleKey.of("xoo", "S001"));
}

@Test
public void removed_rule_is_returned() {
dbTester.prepareDbUnit(getClass(), "removed_rule.xml");
RuleResultSetIterator it = RuleResultSetIterator.create(dbTester.getDbClient(), dbTester.getSession(), 0L);
Map<String, RuleDoc> rulesByKey = rulesByKey(it);
it.close();

assertThat(rulesByKey).hasSize(1);
}

private static Map<String, RuleDoc> rulesByKey(RuleResultSetIterator it) {
return Maps.uniqueIndex(it, new Function<RuleDoc, String>() {
@Override
public String apply(@Nonnull RuleDoc rule) {
return rule.key().rule();
}
});
}
}

+ 1
- 1
server/sonar-server/src/test/resources/org/sonar/server/rule/db/RuleDaoTest/insert-result.xml View File

@@ -9,7 +9,7 @@
remediation_offset="5min" default_remediation_offset="10h"
effort_to_fix_description="squid.S115.effortToFix" description_format="MARKDOWN"
created_at="2013-12-16" updated_at="2013-12-16"
created_at_ms="[null]" updated_at_ms="[null]"
created_at_ms="0" updated_at_ms="0"
/>

</dataset>

+ 2
- 2
server/sonar-server/src/test/resources/org/sonar/server/rule/db/RuleDaoTest/insert_all-result.xml View File

@@ -8,7 +8,7 @@
remediation_coeff="1h" default_remediation_coeff="5d"
remediation_offset="5min" default_remediation_offset="10h"
effort_to_fix_description="squid.S115.effortToFix" description_format="HTML"
created_at_ms="[null]" updated_at_ms="[null]"
created_at_ms="0" updated_at_ms="0"
/>

<rules tags="[null]" system_tags="[null]" id="2" plugin_rule_key="NewRuleKey2" plugin_name="plugin2" name="new name2" description="new description2" status="BETA"
@@ -19,7 +19,7 @@
remediation_coeff="5d" default_remediation_coeff="1h"
remediation_offset="10h" default_remediation_offset="5min"
effort_to_fix_description="squid.S115.effortToFix2" description_format="MARKDOWN"
created_at_ms="[null]" updated_at_ms="[null]"
created_at_ms="0" updated_at_ms="0"
/>

</dataset>

+ 1
- 1
server/sonar-server/src/test/resources/org/sonar/server/rule/db/RuleDaoTest/update-result.xml View File

@@ -10,7 +10,7 @@
remediation_coeff="1h" default_remediation_coeff="5d"
remediation_offset="5min" default_remediation_offset="10h"
effort_to_fix_description="squid.S115.effortToFix" description_format="MARKDOWN"
created_at_ms="1500000000000" updated_at_ms="1600000000000"
created_at_ms="1500000000000" updated_at_ms="0"
/>

<rules tags="[null]" system_tags="[null]" id="2" plugin_rule_key="Parent1" plugin_name="checkstyle"

+ 13
- 0
server/sonar-server/src/test/resources/org/sonar/server/rule/index/RuleIndexerTest/index.xml View File

@@ -0,0 +1,13 @@
<dataset>

<rules id="1" name="Null Pointer" plugin_rule_key="S001"
plugin_config_key="S1" plugin_name="xoo"
description_format="HTML" description="S001 desc" language="xoo"
priority="4" status="READY"
is_template="[false]" template_id="[null]"
tags="bug,performance" system_tags="cwe"
created_at="2014-05-10" updated_at="2014-05-11"
created_at_ms="1500000000000" updated_at_ms="1600000000000"
/>

</dataset>

+ 13
- 0
server/sonar-server/src/test/resources/org/sonar/server/rule/index/RuleIndexerTest/removed_rule.xml View File

@@ -0,0 +1,13 @@
<dataset>

<rules id="1" name="Null Pointer" plugin_rule_key="S001"
plugin_config_key="S1" plugin_name="xoo"
description_format="HTML" description="S001 desc" language="xoo"
priority="4" status="REMOVED"
is_template="[false]" template_id="[null]"
tags="bug,performance" system_tags="cwe"
created_at="2014-05-10" updated_at="2014-05-11"
created_at_ms="1500000000000" updated_at_ms="1600000000000"
/>

</dataset>

+ 25
- 0
server/sonar-server/src/test/resources/org/sonar/server/rule/index/RuleResultSetIteratorTest/custom_rule.xml View File

@@ -0,0 +1,25 @@
<dataset>

<!-- Template rule -->
<rules id="1" name="Null Pointer" plugin_rule_key="S001"
plugin_config_key="S1" plugin_name="xoo"
description_format="HTML" description="S001 desc" language="xoo"
priority="4" status="READY"
is_template="[true]" template_id="[null]"
tags="bug,performance" system_tags="cwe"
created_at="2014-05-10" updated_at="2014-05-11"
created_at_ms="1500000000000" updated_at_ms="1600000000000"
/>

<!-- Custom rule -->
<rules id="2" name="Slow" plugin_rule_key="S002"
plugin_config_key="S2" plugin_name="xoo"
description_format="MARKDOWN" description="*S002 desc*" language="xoo"
priority="3" status="BETA"
is_template="[false]" template_id="1"
tags="[null]" system_tags="[null]"
created_at="2014-05-10" updated_at="2014-05-11"
created_at_ms="2000000000000" updated_at_ms="2100000000000"
/>

</dataset>

+ 13
- 0
server/sonar-server/src/test/resources/org/sonar/server/rule/index/RuleResultSetIteratorTest/one_rule.xml View File

@@ -0,0 +1,13 @@
<dataset>

<rules id="1" name="Null Pointer" plugin_rule_key="S001"
plugin_config_key="S1" plugin_name="xoo"
description_format="HTML" description="S001 desc" language="xoo"
priority="4" status="READY"
is_template="[false]" template_id="[null]"
tags="bug,performance" system_tags="cwe"
created_at="2014-05-10" updated_at="2014-05-11"
created_at_ms="1500000000000" updated_at_ms="1600000000000"
/>

</dataset>

+ 13
- 0
server/sonar-server/src/test/resources/org/sonar/server/rule/index/RuleResultSetIteratorTest/removed_rule.xml View File

@@ -0,0 +1,13 @@
<dataset>

<rules id="1" name="Null Pointer" plugin_rule_key="S001"
plugin_config_key="S1" plugin_name="xoo"
description_format="HTML" description="S001 desc" language="xoo"
priority="4" status="REMOVED"
is_template="[false]" template_id="[null]"
tags="bug,performance" system_tags="cwe"
created_at="2014-05-10" updated_at="2014-05-11"
created_at_ms="1500000000000" updated_at_ms="1600000000000"
/>

</dataset>

+ 22
- 0
server/sonar-server/src/test/resources/org/sonar/server/rule/index/RuleResultSetIteratorTest/shared.xml View File

@@ -0,0 +1,22 @@
<dataset>

<rules id="1" name="Null Pointer" plugin_rule_key="S001"
plugin_config_key="S1" plugin_name="xoo"
description_format="HTML" description="S001 desc" language="xoo"
priority="4" status="READY"
is_template="[false]" template_id="[null]"
tags="bug,performance" system_tags="cwe"
created_at="2014-05-10" updated_at="2014-05-11"
created_at_ms="1500000000000" updated_at_ms="1600000000000"
/>

<rules id="2" name="Slow" plugin_rule_key="S002"
plugin_config_key="S2" plugin_name="xoo"
description_format="MARKDOWN" description="*S002 desc*" language="xoo"
priority="3" status="BETA"
is_template="[true]" template_id="[null]"
tags="[null]" system_tags="[null]"
created_at="2014-05-10" updated_at="2014-05-11"
created_at_ms="2000000000000" updated_at_ms="2100000000000"
/>
</dataset>

+ 1
- 1
sonar-db/src/test/resources/org/sonar/db/version/v55/FeedRulesLongDateColumnsTest/execute.xml View File

@@ -8,7 +8,7 @@
created_at_ms="[null]" updated_at_ms="[null]"
/>

<!-- re-entrant migration - ignore the issues that are already fed with new dates -->
<!-- re-entrant migration - ignore the rules that are already fed with new dates -->
<rules id="2" name="Slow" plugin_rule_key="S002"
plugin_config_key="S2" plugin_name="java" description="[null]" priority="4" status="BETA"
is_template="[false]" template_id="[null]"

Loading…
Cancel
Save