3 * Copyright (C) 2009-2024 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.measure.index;
22 import java.util.Arrays;
23 import java.util.Locale;
24 import java.util.concurrent.atomic.AtomicBoolean;
25 import java.util.stream.Stream;
26 import org.apache.commons.lang.StringUtils;
27 import org.elasticsearch.index.query.BoolQueryBuilder;
28 import org.elasticsearch.index.query.MatchQueryBuilder;
29 import org.elasticsearch.index.query.QueryBuilder;
30 import org.sonar.server.es.newindex.DefaultIndexSettings;
32 import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
33 import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
34 import static org.elasticsearch.index.query.QueryBuilders.prefixQuery;
35 import static org.elasticsearch.index.query.QueryBuilders.wildcardQuery;
36 import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SEARCH_GRAMS_ANALYZER;
37 import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
38 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_KEY;
39 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_NAME;
42 * This class is used in order to do some advanced full text search on projects key and name
44 class ProjectsTextSearchQueryFactory {
46 private ProjectsTextSearchQueryFactory() {
47 // Only static methods
50 static QueryBuilder createQuery(String queryText) {
51 BoolQueryBuilder featureQuery = boolQuery();
52 Arrays.stream(ComponentTextSearchFeature.values())
53 .map(f -> f.getQuery(queryText))
54 .forEach(featureQuery::should);
58 private enum ComponentTextSearchFeature {
62 QueryBuilder getQuery(String queryText) {
63 return matchQuery(SORTABLE_ANALYZER.subField(FIELD_NAME), queryText)
69 QueryBuilder getQuery(String queryText) {
70 return prefixAndPartialQuery(queryText, FIELD_NAME, FIELD_NAME)
76 QueryBuilder getQuery(String queryText) {
77 String lowerCaseQueryText = queryText.toLowerCase(Locale.ENGLISH);
78 return prefixAndPartialQuery(lowerCaseQueryText, SORTABLE_ANALYZER.subField(FIELD_NAME), FIELD_NAME)
84 QueryBuilder getQuery(String queryText) {
85 BoolQueryBuilder queryBuilder = boolQuery();
87 .map(text -> partialTermQuery(text, FIELD_NAME))
88 .forEach(queryBuilder::must);
95 QueryBuilder getQuery(String queryText) {
96 return wildcardQuery(SORTABLE_ANALYZER.subField(FIELD_KEY), "*" + queryText + "*")
97 .caseInsensitive(true)
102 abstract QueryBuilder getQuery(String queryText);
104 protected Stream<String> split(String queryText) {
105 return Arrays.stream(
106 queryText.split(DefaultIndexSettings.SEARCH_TERM_TOKENIZER_PATTERN))
107 .filter(StringUtils::isNotEmpty);
110 protected BoolQueryBuilder prefixAndPartialQuery(String queryText, String fieldName, String originalFieldName) {
111 BoolQueryBuilder queryBuilder = boolQuery();
112 AtomicBoolean first = new AtomicBoolean(true);
115 if (first.getAndSet(false)) {
116 return prefixQuery(fieldName, queryTerm);
118 return partialTermQuery(queryTerm, originalFieldName);
120 .forEach(queryBuilder::must);
124 protected MatchQueryBuilder partialTermQuery(String queryTerm, String fieldName) {
125 // We will truncate the search to the maximum length of nGrams in the index.
126 // Otherwise the search would for sure not find any results.
127 String truncatedQuery = StringUtils.left(queryTerm, DefaultIndexSettings.MAXIMUM_NGRAM_LENGTH);
128 return matchQuery(SEARCH_GRAMS_ANALYZER.subField(fieldName), truncatedQuery);