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.issue.index;
22 import com.google.common.base.CharMatcher;
23 import com.google.common.base.Splitter;
24 import java.io.IOException;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.Optional;
29 import javax.annotation.CheckForNull;
30 import javax.annotation.Nullable;
31 import org.apache.ibatis.cursor.Cursor;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34 import org.sonar.api.resources.Qualifiers;
35 import org.sonar.api.resources.Scopes;
36 import org.sonar.api.rules.CleanCodeAttribute;
37 import org.sonar.api.rules.RuleType;
38 import org.sonar.db.DatabaseUtils;
39 import org.sonar.db.DbClient;
40 import org.sonar.db.DbSession;
41 import org.sonar.db.issue.IndexedIssueDto;
42 import org.sonar.server.security.SecurityStandards;
44 import static com.google.common.base.Preconditions.checkArgument;
45 import static org.sonar.api.utils.DateUtils.longToDate;
46 import static org.sonar.db.rule.RuleDto.deserializeSecurityStandardsString;
47 import static org.sonar.server.security.SecurityStandards.fromSecurityStandards;
50 * Scrolls over table ISSUES and reads documents to populate
53 class IssueIteratorForSingleChunk implements IssueIterator {
54 private static final Logger LOG = LoggerFactory.getLogger(IssueIteratorForSingleChunk.class);
56 static final Splitter STRING_LIST_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
58 private final DbSession session;
60 private final Cursor<IndexedIssueDto> indexCursor;
61 private final Iterator<IndexedIssueDto> iterator;
63 IssueIteratorForSingleChunk(DbClient dbClient, @Nullable String branchUuid, @Nullable Collection<String> issueKeys) {
64 checkArgument(issueKeys == null || issueKeys.size() <= DatabaseUtils.PARTITION_SIZE_FOR_ORACLE,
65 "Cannot search for more than " + DatabaseUtils.PARTITION_SIZE_FOR_ORACLE + " issue keys at once. Please provide the keys in smaller chunks.");
66 this.session = dbClient.openSession(false);
68 indexCursor = dbClient.issueDao().scrollIssuesForIndexation(session, branchUuid, issueKeys);
69 iterator = indexCursor.iterator();
70 } catch (Exception e) {
72 throw new IllegalStateException("Fail to prepare SQL request to select all issues", e);
77 public boolean hasNext() {
78 return iterator.hasNext();
82 public IssueDoc next() {
84 return toIssueDoc(iterator.next());
87 private static IssueDoc toIssueDoc(IndexedIssueDto indexedIssueDto) {
88 IssueDoc doc = new IssueDoc(new HashMap<>(30));
90 String key = indexedIssueDto.getIssueKey();
92 // all the fields must be present, even if value is null
94 doc.setAssigneeUuid(indexedIssueDto.getAssignee());
95 doc.setLine(indexedIssueDto.getLine());
96 doc.setResolution(indexedIssueDto.getResolution());
97 doc.setSeverity(indexedIssueDto.getSeverity());
98 String cleanCodeAttributeCategory = Optional.ofNullable(indexedIssueDto.getCleanCodeAttribute())
99 .map(CleanCodeAttribute::valueOf)
100 .map(CleanCodeAttribute::getAttributeCategory)
103 doc.setCleanCodeAttributeCategory(cleanCodeAttributeCategory);
104 doc.setStatus(indexedIssueDto.getStatus());
105 doc.setIssueStatus(indexedIssueDto.getIssueStatus());
106 doc.setEffort(indexedIssueDto.getEffort());
107 doc.setAuthorLogin(indexedIssueDto.getAuthorLogin());
109 doc.setFuncCloseDate(longToDate(indexedIssueDto.getIssueCloseDate()));
110 doc.setFuncCreationDate(longToDate(indexedIssueDto.getIssueCreationDate()));
111 doc.setFuncUpdateDate(longToDate(indexedIssueDto.getIssueUpdateDate()));
113 doc.setRuleUuid(indexedIssueDto.getRuleUuid());
114 doc.setLanguage(indexedIssueDto.getLanguage());
115 doc.setComponentUuid(indexedIssueDto.getComponentUuid());
116 String scope = indexedIssueDto.getScope();
117 String filePath = extractFilePath(indexedIssueDto.getPath(), scope);
118 doc.setFilePath(filePath);
119 doc.setDirectoryPath(extractDirPath(doc.filePath(), scope));
120 String branchUuid = indexedIssueDto.getBranchUuid();
121 boolean isMainBranch = indexedIssueDto.isMain();
122 String projectUuid = indexedIssueDto.getProjectUuid();
123 doc.setBranchUuid(branchUuid);
124 doc.setIsMainBranch(isMainBranch);
125 doc.setProjectUuid(projectUuid);
126 String tags = indexedIssueDto.getTags();
127 doc.setTags(STRING_LIST_SPLITTER.splitToList(tags == null ? "" : tags));
128 doc.setType(RuleType.valueOf(indexedIssueDto.getIssueType()));
129 doc.setImpacts(indexedIssueDto.getEffectiveImpacts());
130 SecurityStandards securityStandards = fromSecurityStandards(deserializeSecurityStandardsString(indexedIssueDto.getSecurityStandards()));
131 SecurityStandards.SQCategory sqCategory = securityStandards.getSqCategory();
132 doc.setOwaspTop10(securityStandards.getOwaspTop10());
133 doc.setOwaspTop10For2021(securityStandards.getOwaspTop10For2021());
134 doc.setPciDss32(securityStandards.getPciDss32());
135 doc.setPciDss40(securityStandards.getPciDss40());
136 doc.setOwaspAsvs40(securityStandards.getOwaspAsvs40());
137 doc.setCwe(securityStandards.getCwe());
138 doc.setSansTop25(securityStandards.getSansTop25());
139 doc.setSonarSourceSecurityCategory(sqCategory);
140 doc.setVulnerabilityProbability(sqCategory.getVulnerability());
142 doc.setScope(Qualifiers.UNIT_TEST_FILE.equals(indexedIssueDto.getQualifier()) ? IssueScope.TEST : IssueScope.MAIN);
143 doc.setIsNewCodeReference(indexedIssueDto.isNewCodeReferenceIssue());
144 String codeVariants = indexedIssueDto.getCodeVariants();
145 doc.setCodeVariants(STRING_LIST_SPLITTER.splitToList(codeVariants == null ? "" : codeVariants));
146 doc.setPrioritizedRule(indexedIssueDto.isPrioritizedRule());
152 private static String extractDirPath(@Nullable String filePath, String scope) {
153 if (filePath != null) {
154 if (Scopes.DIRECTORY.equals(scope)) {
157 int lastSlashIndex = CharMatcher.anyOf("/").lastIndexIn(filePath);
158 if (lastSlashIndex > 0) {
159 return filePath.substring(0, lastSlashIndex);
167 private static String extractFilePath(@Nullable String filePath, String scope) {
168 // On modules, the path contains the relative path of the module starting from its parent, and in E/S we're only interested in the
170 // of files and directories.
171 // That's why the file path should be null on modules and projects.
172 if (filePath != null && !Scopes.PROJECT.equals(scope)) {
179 public void close() {
182 } catch (IOException e) {
183 LOG.atWarn().setMessage("unable to close the cursor, this may lead to database connexion leak. error is : {}").addArgument(e).log();