3 * Copyright (C) 2009-2023 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.sql.PreparedStatement;
25 import java.sql.ResultSet;
26 import java.sql.SQLException;
27 import java.util.Collection;
28 import java.util.HashMap;
29 import java.util.stream.Collectors;
30 import java.util.stream.IntStream;
31 import javax.annotation.CheckForNull;
32 import javax.annotation.Nullable;
33 import org.apache.commons.lang.StringUtils;
34 import org.sonar.api.resources.Qualifiers;
35 import org.sonar.api.resources.Scopes;
36 import org.sonar.api.rules.RuleType;
37 import org.sonar.db.DatabaseUtils;
38 import org.sonar.db.DbClient;
39 import org.sonar.db.DbSession;
40 import org.sonar.db.ResultSetIterator;
41 import org.sonar.server.security.SecurityStandards;
43 import static com.google.common.base.Preconditions.checkArgument;
44 import static org.elasticsearch.common.Strings.isNullOrEmpty;
45 import static org.sonar.api.utils.DateUtils.longToDate;
46 import static org.sonar.db.DatabaseUtils.getLong;
47 import static org.sonar.db.rule.RuleDto.deserializeSecurityStandardsString;
48 import static org.sonar.server.security.SecurityStandards.fromSecurityStandards;
51 * Scrolls over table ISSUES and reads documents to populate
54 class IssueIteratorForSingleChunk implements IssueIterator {
56 private static final String[] FIELDS = {
67 "i.issue_creation_date",
70 "i.issue_update_date",
83 "r.security_standards",
89 private static final String SQL_ALL = "select " + StringUtils.join(FIELDS, ",") + " from issues i " +
90 "inner join rules r on r.uuid = i.rule_uuid " +
91 "inner join components c on c.uuid = i.component_uuid " +
92 "inner join project_branches pb on c.branch_uuid = pb.uuid ";
94 private static final String SQL_NEW_CODE_JOIN = "left join new_code_reference_issues n on n.issue_key = i.kee ";
96 private static final String BRANCH_FILTER = " and c.branch_uuid = ? and i.project_uuid = ? ";
97 private static final String ISSUE_KEY_FILTER_PREFIX = " and i.kee in (";
98 private static final String ISSUE_KEY_FILTER_SUFFIX = ") ";
100 static final Splitter STRING_LIST_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
102 private final DbSession session;
105 private final String branchUuid;
108 private final Collection<String> issueKeys;
110 private final PreparedStatement stmt;
111 private final ResultSetIterator<IssueDoc> iterator;
113 IssueIteratorForSingleChunk(DbClient dbClient, @Nullable String branchUuid, @Nullable Collection<String> issueKeys) {
114 checkArgument(issueKeys == null || issueKeys.size() <= DatabaseUtils.PARTITION_SIZE_FOR_ORACLE,
115 "Cannot search for more than " + DatabaseUtils.PARTITION_SIZE_FOR_ORACLE + " issue keys at once. Please provide the keys in smaller chunks.");
116 this.branchUuid = branchUuid;
117 this.issueKeys = issueKeys;
118 this.session = dbClient.openSession(false);
121 String sql = createSql();
122 stmt = dbClient.getMyBatis().newScrollingSelectStatement(session, sql);
123 iterator = createIterator();
124 } catch (Exception e) {
126 throw new IllegalStateException("Fail to prepare SQL request to select all issues", e);
130 private IssueIteratorInternal createIterator() {
133 return new IssueIteratorInternal(stmt);
134 } catch (SQLException e) {
135 DatabaseUtils.closeQuietly(stmt);
136 throw new IllegalStateException("Fail to prepare SQL request to select all issues", e);
141 public boolean hasNext() {
142 return iterator.hasNext();
146 public IssueDoc next() {
147 return iterator.next();
150 private String createSql() {
151 String sql = SQL_ALL;
152 sql += branchUuid == null ? "" : BRANCH_FILTER;
153 if (issueKeys != null && !issueKeys.isEmpty()) {
154 sql += ISSUE_KEY_FILTER_PREFIX;
155 sql += IntStream.range(0, issueKeys.size()).mapToObj(i -> "?").collect(Collectors.joining(","));
156 sql += ISSUE_KEY_FILTER_SUFFIX;
158 sql += SQL_NEW_CODE_JOIN;
162 private void setParameters(PreparedStatement stmt) throws SQLException {
164 if (branchUuid != null) {
165 stmt.setString(index, branchUuid);
167 stmt.setString(index, branchUuid);
170 if (issueKeys != null) {
171 for (String key : issueKeys) {
172 stmt.setString(index, key);
179 public void close() {
183 DatabaseUtils.closeQuietly(stmt);
188 private static final class IssueIteratorInternal extends ResultSetIterator<IssueDoc> {
190 public IssueIteratorInternal(PreparedStatement stmt) throws SQLException {
195 protected IssueDoc read(ResultSet rs) throws SQLException {
196 IssueDoc doc = new IssueDoc(new HashMap<>(30));
198 String key = rs.getString(1);
200 // all the fields must be present, even if value is null
202 doc.setAssigneeUuid(rs.getString(2));
203 doc.setLine(DatabaseUtils.getInt(rs, 3));
204 doc.setResolution(rs.getString(4));
205 doc.setSeverity(rs.getString(5));
206 doc.setStatus(rs.getString(6));
207 doc.setEffort(getLong(rs, 7));
208 doc.setAuthorLogin(rs.getString(8));
209 doc.setFuncCloseDate(longToDate(getLong(rs, 9)));
210 doc.setFuncCreationDate(longToDate(getLong(rs, 10)));
211 doc.setFuncUpdateDate(longToDate(getLong(rs, 11)));
212 doc.setRuleUuid(rs.getString(12));
213 doc.setLanguage(rs.getString(13));
214 doc.setComponentUuid(rs.getString(14));
215 String scope = rs.getString(16);
216 String filePath = extractFilePath(rs.getString(15), scope);
217 doc.setFilePath(filePath);
218 doc.setDirectoryPath(extractDirPath(doc.filePath(), scope));
219 String branchUuid = rs.getString(17);
220 boolean isMainBranch = rs.getBoolean( 18);
221 String projectUuid = rs.getString(19);
222 doc.setBranchUuid(branchUuid);
223 doc.setIsMainBranch(isMainBranch);
224 doc.setProjectUuid(projectUuid);
225 String tags = rs.getString(20);
226 doc.setTags(STRING_LIST_SPLITTER.splitToList(tags == null ? "" : tags));
227 doc.setType(RuleType.valueOf(rs.getInt(21)));
229 SecurityStandards securityStandards = fromSecurityStandards(deserializeSecurityStandardsString(rs.getString(22)));
230 SecurityStandards.SQCategory sqCategory = securityStandards.getSqCategory();
231 doc.setOwaspTop10(securityStandards.getOwaspTop10());
232 doc.setOwaspTop10For2021(securityStandards.getOwaspTop10For2021());
233 doc.setPciDss32(securityStandards.getPciDss32());
234 doc.setPciDss40(securityStandards.getPciDss40());
235 doc.setOwaspAsvs40(securityStandards.getOwaspAsvs40());
236 doc.setCwe(securityStandards.getCwe());
237 doc.setSansTop25(securityStandards.getSansTop25());
238 doc.setSonarSourceSecurityCategory(sqCategory);
239 doc.setVulnerabilityProbability(sqCategory.getVulnerability());
241 doc.setScope(Qualifiers.UNIT_TEST_FILE.equals(rs.getString(23)) ? IssueScope.TEST : IssueScope.MAIN);
242 doc.setIsNewCodeReference(!isNullOrEmpty(rs.getString(24)));
243 String codeVariants = rs.getString(25);
244 doc.setCodeVariants(STRING_LIST_SPLITTER.splitToList(codeVariants == null ? "" : codeVariants));
249 private static String extractDirPath(@Nullable String filePath, String scope) {
250 if (filePath != null) {
251 if (Scopes.DIRECTORY.equals(scope)) {
254 int lastSlashIndex = CharMatcher.anyOf("/").lastIndexIn(filePath);
255 if (lastSlashIndex > 0) {
256 return filePath.substring(0, lastSlashIndex);
264 private static String extractFilePath(@Nullable String filePath, String scope) {
265 // 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
267 // of files and directories.
268 // That's why the file path should be null on modules and projects.
269 if (filePath != null && !Scopes.PROJECT.equals(scope)) {