3 * Copyright (C) 2009-2020 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.es.textsearch;
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;
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;
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;
39 * This class is used in order to do some advanced full text search in an index on component key and component name
41 * The index must contains at least one field for the component key and one field for the component name
43 public class ComponentTextSearchQueryFactory {
45 private ComponentTextSearchQueryFactory() {
46 // Only static methods
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);
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);
70 return Optional.empty();
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;
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;
90 public String getQueryText() {
94 public List<String> getQueryTextTokens() {
95 return queryTextTokens;
98 public String getFieldKey() {
102 public String getFieldName() {
106 public Set<String> getRecentlyBrowsedKeys() {
107 return recentlyBrowsedKeys;
110 public static Builder builder() {
111 return new Builder();
114 public Set<String> getFavoriteKeys() {
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();
126 * The text search query
128 public Builder setQueryText(String queryText) {
129 this.queryText = queryText;
134 * The index field that contains the component key
136 public Builder setFieldKey(String fieldKey) {
137 this.fieldKey = fieldKey;
142 * The index field that contains the component name
144 public Builder setFieldName(String fieldName) {
145 this.fieldName = fieldName;
150 * Component keys of recently browsed items
152 public Builder setRecentlyBrowsedKeys(Set<String> recentlyBrowsedKeys) {
153 this.recentlyBrowsedKeys = ImmutableSet.copyOf(recentlyBrowsedKeys);
158 * Component keys of favorite items
160 public Builder setFavoriteKeys(Set<String> favoriteKeys) {
161 this.favoriteKeys = ImmutableSet.copyOf(favoriteKeys);
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);