org.sonar.server.component.es.ProjectMeasuresIndex.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.server.component.es.ProjectMeasuresIndex.java

Source

/*
 * 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.component.es;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.IntStream;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.range.RangeBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.sonar.api.measures.Metric;
import org.sonar.server.component.es.ProjectMeasuresQuery.MetricCriterion;
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.es.StickyFacetBuilder;
import org.sonar.server.user.UserSession;

import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
import static org.elasticsearch.search.aggregations.AggregationBuilders.filters;
import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
import static org.sonar.api.measures.CoreMetrics.COVERAGE_KEY;
import static org.sonar.api.measures.CoreMetrics.DUPLICATED_LINES_DENSITY_KEY;
import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
import static org.sonar.api.measures.CoreMetrics.RELIABILITY_RATING_KEY;
import static org.sonar.api.measures.CoreMetrics.SECURITY_RATING_KEY;
import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY;
import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_AUTHORIZATION_GROUPS;
import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_AUTHORIZATION_USERS;
import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES_KEY;
import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_MEASURES_VALUE;
import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_NAME;
import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.FIELD_QUALITY_GATE;
import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.TYPE_AUTHORIZATION;
import static org.sonar.server.component.es.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;

public class ProjectMeasuresIndex extends BaseIndex {

    public static final List<String> SUPPORTED_FACETS = ImmutableList.of(NCLOC_KEY, DUPLICATED_LINES_DENSITY_KEY,
            COVERAGE_KEY, SQALE_RATING_KEY, RELIABILITY_RATING_KEY, SECURITY_RATING_KEY, ALERT_STATUS_KEY);

    private static final String FIELD_KEY = FIELD_MEASURES + "." + FIELD_MEASURES_KEY;
    private static final String FIELD_VALUE = FIELD_MEASURES + "." + FIELD_MEASURES_VALUE;

    private final UserSession userSession;

    public ProjectMeasuresIndex(EsClient client, UserSession userSession) {
        super(client);
        this.userSession = userSession;
    }

    public SearchIdResult<String> search(ProjectMeasuresQuery query, SearchOptions searchOptions) {
        SearchRequestBuilder requestBuilder = getClient().prepareSearch(INDEX_PROJECT_MEASURES)
                .setTypes(TYPE_PROJECT_MEASURES).setFetchSource(false).setFrom(searchOptions.getOffset())
                .setSize(searchOptions.getLimit()).addSort(FIELD_NAME + "." + SORT_SUFFIX, SortOrder.ASC);

        BoolQueryBuilder esFilter = boolQuery();
        Map<String, QueryBuilder> filters = createFilters(query);
        filters.values().forEach(esFilter::must);
        requestBuilder.setQuery(esFilter);

        addFacets(requestBuilder, searchOptions, filters);
        return new SearchIdResult<>(requestBuilder.get(), id -> id);
    }

    private static void addFacets(SearchRequestBuilder esSearch, SearchOptions options,
            Map<String, QueryBuilder> filters) {
        if (!options.getFacets().isEmpty()) {
            if (options.getFacets().contains(NCLOC_KEY)) {
                addRangeFacet(esSearch, NCLOC_KEY, ImmutableList.of(1_000d, 10_000d, 100_000d, 500_000d), filters);
            }
            if (options.getFacets().contains(DUPLICATED_LINES_DENSITY_KEY)) {
                addRangeFacet(esSearch, DUPLICATED_LINES_DENSITY_KEY, ImmutableList.of(3d, 5d, 10d, 20d), filters);
            }
            if (options.getFacets().contains(COVERAGE_KEY)) {
                addRangeFacet(esSearch, COVERAGE_KEY, ImmutableList.of(30d, 50d, 70d, 80d), filters);
            }
            if (options.getFacets().contains(SQALE_RATING_KEY)) {
                addRatingFacet(esSearch, SQALE_RATING_KEY, filters);
            }
            if (options.getFacets().contains(RELIABILITY_RATING_KEY)) {
                addRatingFacet(esSearch, RELIABILITY_RATING_KEY, filters);
            }
            if (options.getFacets().contains(SECURITY_RATING_KEY)) {
                addRatingFacet(esSearch, SECURITY_RATING_KEY, filters);
            }
            if (options.getFacets().contains(ALERT_STATUS_KEY)) {
                esSearch.addAggregation(createStickyFacet(ALERT_STATUS_KEY, filters, createQualityGateFacet()));
            }
        }
    }

    private static void addRangeFacet(SearchRequestBuilder esSearch, String metricKey, List<Double> thresholds,
            Map<String, QueryBuilder> filters) {
        esSearch.addAggregation(createStickyFacet(metricKey, filters, createRangeFacet(metricKey, thresholds)));
    }

    private static void addRatingFacet(SearchRequestBuilder esSearch, String metricKey,
            Map<String, QueryBuilder> filters) {
        esSearch.addAggregation(createStickyFacet(metricKey, filters, createRatingFacet(metricKey)));
    }

    private static AggregationBuilder createStickyFacet(String metricKey, Map<String, QueryBuilder> filters,
            AggregationBuilder aggregationBuilder) {
        StickyFacetBuilder facetBuilder = new StickyFacetBuilder(matchAllQuery(), filters);
        BoolQueryBuilder facetFilter = facetBuilder.getStickyFacetFilter(metricKey);
        return AggregationBuilders.global(metricKey).subAggregation(AggregationBuilders
                .filter("facet_filter_" + metricKey).filter(facetFilter).subAggregation(aggregationBuilder));
    }

    private static AggregationBuilder createRangeFacet(String metricKey, List<Double> thresholds) {
        RangeBuilder rangeAgg = AggregationBuilders.range(metricKey).field(FIELD_VALUE);
        final int lastIndex = thresholds.size() - 1;
        IntStream.range(0, thresholds.size()).forEach(i -> {
            if (i == 0) {
                rangeAgg.addUnboundedTo(thresholds.get(0));
                rangeAgg.addRange(thresholds.get(0), thresholds.get(1));
            } else if (i == lastIndex) {
                rangeAgg.addUnboundedFrom(thresholds.get(lastIndex));
            } else {
                rangeAgg.addRange(thresholds.get(i), thresholds.get(i + 1));
            }
        });

        return AggregationBuilders.nested("nested_" + metricKey).path(FIELD_MEASURES)
                .subAggregation(AggregationBuilders.filter("filter_" + metricKey)
                        .filter(termsQuery(FIELD_KEY, metricKey)).subAggregation(rangeAgg));
    }

    private static AggregationBuilder createRatingFacet(String metricKey) {
        return AggregationBuilders.nested("nested_" + metricKey).path(FIELD_MEASURES)
                .subAggregation(AggregationBuilders.filter("filter_" + metricKey)
                        .filter(termsQuery(FIELD_KEY, metricKey))
                        .subAggregation(filters(metricKey).filter("1", termQuery(FIELD_VALUE, 1d))
                                .filter("2", termQuery(FIELD_VALUE, 2d)).filter("3", termQuery(FIELD_VALUE, 3d))
                                .filter("4", termQuery(FIELD_VALUE, 4d)).filter("5", termQuery(FIELD_VALUE, 5d))));
    }

    private static AggregationBuilder createQualityGateFacet() {
        return AggregationBuilders.filters(ALERT_STATUS_KEY)
                .filter(Metric.Level.ERROR.name(), termQuery(FIELD_QUALITY_GATE, Metric.Level.ERROR.name()))
                .filter(Metric.Level.WARN.name(), termQuery(FIELD_QUALITY_GATE, Metric.Level.WARN.name()))
                .filter(Metric.Level.OK.name(), termQuery(FIELD_QUALITY_GATE, Metric.Level.OK.name()));
    }

    private Map<String, QueryBuilder> createFilters(ProjectMeasuresQuery query) {
        Map<String, QueryBuilder> filters = new HashMap<>();
        filters.put("__authorization", createAuthorizationFilter());
        Multimap<String, MetricCriterion> metricCriterionMultimap = ArrayListMultimap.create();
        query.getMetricCriteria().forEach(
                metricCriterion -> metricCriterionMultimap.put(metricCriterion.getMetricKey(), metricCriterion));
        metricCriterionMultimap.asMap().entrySet().forEach(entry -> {
            BoolQueryBuilder metricFilters = boolQuery();
            entry.getValue().stream().map(criterion -> nestedQuery(FIELD_MEASURES, boolQuery()
                    .filter(termQuery(FIELD_KEY, criterion.getMetricKey())).filter(toValueQuery(criterion))))
                    .forEach(metricFilters::must);
            filters.put(entry.getKey(), metricFilters);

        });
        if (query.hasQualityGateStatus()) {
            filters.put(ALERT_STATUS_KEY, termQuery(FIELD_QUALITY_GATE, query.getQualityGateStatus().name()));
        }
        if (query.doesFilterOnProjectUuids()) {
            filters.put("ids", termsQuery("_id", query.getProjectUuids()));
        }
        return filters;
    }

    private static QueryBuilder toValueQuery(MetricCriterion criterion) {
        String fieldName = FIELD_VALUE;

        switch (criterion.getOperator()) {
        case GT:
            return rangeQuery(fieldName).gt(criterion.getValue());
        case GTE:
            return rangeQuery(fieldName).gte(criterion.getValue());
        case LT:
            return rangeQuery(fieldName).lt(criterion.getValue());
        case LTE:
            return rangeQuery(fieldName).lte(criterion.getValue());
        case EQ:
            return termQuery(fieldName, criterion.getValue());
        default:
            throw new IllegalStateException("Metric criteria non supported: " + criterion.getOperator().name());
        }
    }

    private QueryBuilder createAuthorizationFilter() {
        Integer userLogin = userSession.getUserId();
        Set<String> userGroupNames = userSession.getUserGroups();
        BoolQueryBuilder groupsAndUser = boolQuery();
        if (userLogin != null) {
            groupsAndUser.should(termQuery(FIELD_AUTHORIZATION_USERS, userLogin.longValue()));
        }
        for (String group : userGroupNames) {
            groupsAndUser.should(termQuery(FIELD_AUTHORIZATION_GROUPS, group));
        }
        return QueryBuilders.hasParentQuery(TYPE_AUTHORIZATION,
                QueryBuilders.boolQuery().must(matchAllQuery()).filter(groupsAndUser));
    }
}