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.qualityprofile.index;
22 import com.google.common.collect.ImmutableSet;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.List;
28 import java.util.stream.Collectors;
29 import javax.annotation.Nullable;
30 import org.elasticsearch.action.index.IndexRequest;
31 import org.elasticsearch.action.search.SearchRequest;
32 import org.elasticsearch.index.query.QueryBuilders;
33 import org.elasticsearch.search.builder.SearchSourceBuilder;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36 import org.sonar.db.DbClient;
37 import org.sonar.db.DbSession;
38 import org.sonar.db.es.EsQueueDto;
39 import org.sonar.db.qualityprofile.IndexedActiveRuleDto;
40 import org.sonar.db.qualityprofile.QProfileDto;
41 import org.sonar.db.qualityprofile.RulesProfileDto;
42 import org.sonar.db.rule.SeverityUtil;
43 import org.sonar.server.es.BulkIndexer;
44 import org.sonar.server.es.BulkIndexer.Size;
45 import org.sonar.server.es.EsClient;
46 import org.sonar.server.es.IndexType;
47 import org.sonar.server.es.IndexingListener;
48 import org.sonar.server.es.IndexingResult;
49 import org.sonar.server.es.OneToOneResilientIndexingListener;
50 import org.sonar.server.es.ResilientIndexer;
51 import org.sonar.server.qualityprofile.ActiveRuleChange;
52 import org.sonar.server.qualityprofile.ActiveRuleInheritance;
54 import static org.elasticsearch.index.query.QueryBuilders.termQuery;
55 import static org.sonar.server.qualityprofile.index.ActiveRuleDoc.docIdOf;
56 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_PROFILE_UUID;
57 import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_ACTIVE_RULE;
59 public class ActiveRuleIndexer implements ResilientIndexer {
61 private static final Logger LOGGER = LoggerFactory.getLogger(ActiveRuleIndexer.class);
62 private static final String ID_TYPE_ACTIVE_RULE_UUID = "activeRuleUuid";
63 private static final String ID_TYPE_RULE_PROFILE_UUID = "ruleProfileUuid";
65 private final DbClient dbClient;
66 private final EsClient esClient;
68 public ActiveRuleIndexer(DbClient dbClient, EsClient esClient) {
69 this.dbClient = dbClient;
70 this.esClient = esClient;
74 public void indexOnStartup(Set<IndexType> uninitializedIndexTypes) {
78 public void indexAll() {
79 indexAll(Size.REGULAR);
82 private void indexAll(Size bulkSize) {
83 try (DbSession dbSession = dbClient.openSession(false)) {
84 BulkIndexer bulkIndexer = createBulkIndexer(bulkSize, IndexingListener.FAIL_ON_ERROR);
86 dbClient.activeRuleDao().scrollAllForIndexing(dbSession, ar -> bulkIndexer.add(newIndexRequest(ar)));
92 public Set<IndexType> getIndexTypes() {
93 return ImmutableSet.of(TYPE_ACTIVE_RULE);
96 public void commitAndIndex(DbSession dbSession, Collection<ActiveRuleChange> changes) {
97 List<EsQueueDto> items = changes.stream()
98 .map(ActiveRuleChange::getActiveRule)
99 .map(ar -> newQueueDto(docIdOf(ar.getUuid()), ID_TYPE_ACTIVE_RULE_UUID, ar.getRuleUuid()))
102 dbClient.esQueueDao().insert(dbSession, items);
104 postCommit(dbSession, items);
107 public void commitDeletionOfProfiles(DbSession dbSession, Collection<QProfileDto> profiles) {
108 List<EsQueueDto> items = profiles.stream()
109 .map(QProfileDto::getRulesProfileUuid)
111 .map(ruleProfileUuid -> newQueueDto(ruleProfileUuid, ID_TYPE_RULE_PROFILE_UUID, null))
114 dbClient.esQueueDao().insert(dbSession, items);
116 postCommit(dbSession, items);
120 * Entry point for Byteman tests. See directory tests/resilience.
122 private void postCommit(DbSession dbSession, Collection<EsQueueDto> items) {
123 index(dbSession, items);
127 * @return the number of items that have been successfully indexed
130 public IndexingResult index(DbSession dbSession, Collection<EsQueueDto> items) {
131 IndexingResult result = new IndexingResult();
133 if (items.isEmpty()) {
137 Map<String, EsQueueDto> activeRuleItems = new HashMap<>();
138 Map<String, EsQueueDto> ruleProfileItems = new HashMap<>();
140 if (ID_TYPE_RULE_PROFILE_UUID.equals(i.getDocIdType())) {
141 ruleProfileItems.put(i.getDocId(), i);
142 } else if (ID_TYPE_ACTIVE_RULE_UUID.equals(i.getDocIdType())) {
143 activeRuleItems.put(i.getDocId(), i);
145 LOGGER.error("Unsupported es_queue.doc_id_type. Removing row from queue: " + i);
146 deleteQueueDto(dbSession, i);
150 if (!activeRuleItems.isEmpty()) {
151 result.add(doIndexActiveRules(dbSession, activeRuleItems));
153 if (!ruleProfileItems.isEmpty()) {
154 result.add(doIndexRuleProfiles(dbSession, ruleProfileItems));
159 private IndexingResult doIndexActiveRules(DbSession dbSession, Map<String, EsQueueDto> activeRuleItems) {
160 OneToOneResilientIndexingListener listener = new OneToOneResilientIndexingListener(dbClient, dbSession, activeRuleItems.values());
161 BulkIndexer bulkIndexer = createBulkIndexer(Size.REGULAR, listener);
163 Map<String, EsQueueDto> remaining = new HashMap<>(activeRuleItems);
164 dbClient.activeRuleDao().scrollByUuidsForIndexing(dbSession, toActiveRuleUuids(activeRuleItems),
166 remaining.remove(docIdOf(i.getUuid()));
167 bulkIndexer.add(newIndexRequest(i));
170 // the remaining ids reference rows that don't exist in db. They must
171 // be deleted from index.
172 remaining.values().forEach(item -> bulkIndexer.addDeletion(TYPE_ACTIVE_RULE,
173 item.getDocId(), item.getDocRouting()));
174 return bulkIndexer.stop();
177 private static Collection<String> toActiveRuleUuids(Map<String, EsQueueDto> activeRuleItems) {
178 Set<String> docIds = activeRuleItems.keySet();
179 return docIds.stream()
180 .map(ActiveRuleDoc::activeRuleUuidOf)
181 .collect(Collectors.toSet());
184 private IndexingResult doIndexRuleProfiles(DbSession dbSession, Map<String, EsQueueDto> ruleProfileItems) {
185 IndexingResult result = new IndexingResult();
187 for (Map.Entry<String, EsQueueDto> entry : ruleProfileItems.entrySet()) {
188 String ruleProfileUUid = entry.getKey();
189 EsQueueDto item = entry.getValue();
190 IndexingResult profileResult;
192 RulesProfileDto profile = dbClient.qualityProfileDao().selectRuleProfile(dbSession, ruleProfileUUid);
193 if (profile == null) {
194 // profile does not exist anymore in db --> related documents must be deleted from index rules/activeRule
195 SearchRequest search = EsClient.prepareSearch(TYPE_ACTIVE_RULE.getMainType())
196 .source(new SearchSourceBuilder().query(QueryBuilders.boolQuery().must(termQuery(FIELD_ACTIVE_RULE_PROFILE_UUID, ruleProfileUUid))));
197 profileResult = BulkIndexer.delete(esClient, TYPE_ACTIVE_RULE, search);
200 BulkIndexer bulkIndexer = createBulkIndexer(Size.REGULAR, IndexingListener.FAIL_ON_ERROR);
202 dbClient.activeRuleDao().scrollByRuleProfileForIndexing(dbSession, ruleProfileUUid, i -> bulkIndexer.add(newIndexRequest(i)));
203 profileResult = bulkIndexer.stop();
206 if (profileResult.isSuccess()) {
207 deleteQueueDto(dbSession, item);
209 result.add(profileResult);
215 private void deleteQueueDto(DbSession dbSession, EsQueueDto item) {
216 dbClient.esQueueDao().delete(dbSession, item);
220 private BulkIndexer createBulkIndexer(Size size, IndexingListener listener) {
221 return new BulkIndexer(esClient, TYPE_ACTIVE_RULE, size, listener);
224 private static IndexRequest newIndexRequest(IndexedActiveRuleDto dto) {
225 ActiveRuleDoc doc = new ActiveRuleDoc(dto.getUuid())
226 .setRuleUuid(dto.getRuleUuid())
227 .setRuleProfileUuid(dto.getRuleProfileUuid())
228 .setSeverity(SeverityUtil.getSeverityFromOrdinal(dto.getSeverity()));
229 // all the fields must be present, even if value is null
230 String inheritance = dto.getInheritance();
231 doc.setInheritance(inheritance == null ? ActiveRuleInheritance.NONE.name() : inheritance);
232 return doc.toIndexRequest();
235 private static EsQueueDto newQueueDto(String docId, String docIdType, @Nullable String routing) {
236 return EsQueueDto.create(TYPE_ACTIVE_RULE.format(), docId, docIdType, routing);