]> source.dussan.org Git - sonarqube.git/blob
060b0504956c4f24b44060b1721d32dbd657038b
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2020 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20 package org.sonar.server.es.textsearch;
21
22 import com.google.common.collect.ImmutableSet;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Optional;
27 import java.util.Set;
28 import java.util.concurrent.atomic.AtomicBoolean;
29 import org.elasticsearch.index.query.BoolQueryBuilder;
30 import org.elasticsearch.index.query.QueryBuilder;
31 import org.sonar.server.es.textsearch.ComponentTextSearchFeature.UseCase;
32
33 import static com.google.common.base.Preconditions.checkArgument;
34 import static java.util.Objects.requireNonNull;
35 import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
36 import static org.sonar.server.es.textsearch.JavaTokenizer.split;
37
38 /**
39  * This class is used in order to do some advanced full text search in an index on component key and component name
40  *
41  * The index must contains at least one field for the component key and one field for the component name
42  */
43 public class ComponentTextSearchQueryFactory {
44
45   private ComponentTextSearchQueryFactory() {
46     // Only static methods
47   }
48
49   public static QueryBuilder createQuery(ComponentTextSearchQuery query, ComponentTextSearchFeature... features) {
50     checkArgument(features.length > 0, "features cannot be empty");
51     BoolQueryBuilder esQuery = boolQuery().must(
52       createQuery(query, features, UseCase.GENERATE_RESULTS)
53         .orElseThrow(() -> new IllegalStateException("No text search features found to generate search results. Features: " + Arrays.toString(features))));
54     createQuery(query, features, UseCase.CHANGE_ORDER_OF_RESULTS)
55       .ifPresent(esQuery::should);
56     return esQuery;
57   }
58
59   private static Optional<QueryBuilder> createQuery(ComponentTextSearchQuery query, ComponentTextSearchFeature[] features, UseCase useCase) {
60     BoolQueryBuilder generateResults = boolQuery();
61     AtomicBoolean anyFeatures = new AtomicBoolean();
62     Arrays.stream(features)
63       .filter(f -> f.getUseCase() == useCase)
64       .peek(f -> anyFeatures.set(true))
65       .flatMap(f -> f.getQueries(query))
66       .forEach(generateResults::should);
67     if (anyFeatures.get()) {
68       return Optional.of(generateResults);
69     }
70     return Optional.empty();
71   }
72
73   public static class ComponentTextSearchQuery {
74     private final String queryText;
75     private final List<String> queryTextTokens;
76     private final String fieldKey;
77     private final String fieldName;
78     private final Set<String> recentlyBrowsedKeys;
79     private final Set<String> favoriteKeys;
80
81     private ComponentTextSearchQuery(Builder builder) {
82       this.queryText = builder.queryText;
83       this.queryTextTokens = split(builder.queryText);
84       this.fieldKey = builder.fieldKey;
85       this.fieldName = builder.fieldName;
86       this.recentlyBrowsedKeys = builder.recentlyBrowsedKeys;
87       this.favoriteKeys = builder.favoriteKeys;
88     }
89
90     public String getQueryText() {
91       return queryText;
92     }
93
94     public List<String> getQueryTextTokens() {
95       return queryTextTokens;
96     }
97
98     public String getFieldKey() {
99       return fieldKey;
100     }
101
102     public String getFieldName() {
103       return fieldName;
104     }
105
106     public Set<String> getRecentlyBrowsedKeys() {
107       return recentlyBrowsedKeys;
108     }
109
110     public static Builder builder() {
111       return new Builder();
112     }
113
114     public Set<String> getFavoriteKeys() {
115       return favoriteKeys;
116     }
117
118     public static class Builder {
119       private String queryText;
120       private String fieldKey;
121       private String fieldName;
122       private Set<String> recentlyBrowsedKeys = Collections.emptySet();
123       private Set<String> favoriteKeys = Collections.emptySet();
124
125       /**
126        * The text search query
127        */
128       public Builder setQueryText(String queryText) {
129         this.queryText = queryText;
130         return this;
131       }
132
133       /**
134        * The index field that contains the component key
135        */
136       public Builder setFieldKey(String fieldKey) {
137         this.fieldKey = fieldKey;
138         return this;
139       }
140
141       /**
142        * The index field that contains the component name
143        */
144       public Builder setFieldName(String fieldName) {
145         this.fieldName = fieldName;
146         return this;
147       }
148
149       /**
150        * Component keys of recently browsed items
151        */
152       public Builder setRecentlyBrowsedKeys(Set<String> recentlyBrowsedKeys) {
153         this.recentlyBrowsedKeys = ImmutableSet.copyOf(recentlyBrowsedKeys);
154         return this;
155       }
156
157       /**
158        * Component keys of favorite items
159        */
160       public Builder setFavoriteKeys(Set<String> favoriteKeys) {
161         this.favoriteKeys = ImmutableSet.copyOf(favoriteKeys);
162         return this;
163       }
164
165       public ComponentTextSearchQuery build() {
166         requireNonNull(queryText, "query text cannot be null");
167         requireNonNull(fieldKey, "field key cannot be null");
168         requireNonNull(fieldName, "field name cannot be null");
169         requireNonNull(recentlyBrowsedKeys, "field recentlyBrowsedKeys cannot be null");
170         requireNonNull(favoriteKeys, "field favoriteKeys cannot be null");
171         return new ComponentTextSearchQuery(this);
172       }
173     }
174   }
175 }