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.issue.index;
22 import com.google.common.base.CharMatcher;
23 import com.google.common.base.Splitter;
24 import com.google.common.collect.Iterators;
25 import java.sql.PreparedStatement;
26 import java.sql.ResultSet;
27 import java.sql.SQLException;
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.stream.Collectors;
32 import java.util.stream.IntStream;
33 import javax.annotation.CheckForNull;
34 import javax.annotation.Nullable;
35 import org.apache.commons.lang.StringUtils;
36 import org.sonar.api.resources.Scopes;
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.ResultSetIterator;
43 import static com.google.common.base.Preconditions.checkArgument;
44 import static org.sonar.api.utils.DateUtils.longToDate;
45 import static org.sonar.db.DatabaseUtils.getLong;
46 import static org.sonar.server.security.SecurityStandardHelper.getCwe;
47 import static org.sonar.server.security.SecurityStandardHelper.getOwaspTop10;
48 import static org.sonar.server.security.SecurityStandardHelper.getSansTop25;
49 import static org.sonar.server.security.SecurityStandardHelper.getSecurityStandards;
50 import static org.sonar.server.security.SecurityStandardHelper.getSonarSourceSecurityCategories;
53 * Scrolls over table ISSUES and reads documents to populate
56 class IssueIteratorForSingleChunk implements IssueIterator {
58 private static final String[] FIELDS = {
69 "i.issue_creation_date",
72 "i.issue_update_date",
79 "c.organization_uuid",
81 "c.main_branch_project_uuid",
86 "r.security_standards"
89 private static final String SQL_ALL = "select " + StringUtils.join(FIELDS, ",") + " from issues i " +
90 "inner join rules r on r.id = i.rule_id " +
91 "inner join projects c on c.uuid = i.component_uuid ";
93 private static final String PROJECT_FILTER = " and c.project_uuid = ? and i.project_uuid = ? ";
94 private static final String ISSUE_KEY_FILTER_PREFIX = " and i.kee in (";
95 private static final String ISSUE_KEY_FILTER_SUFFIX = ")";
97 static final Splitter TAGS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
98 static final Splitter MODULE_PATH_SPLITTER = Splitter.on('.').trimResults().omitEmptyStrings();
100 private final DbSession session;
103 private final String projectUuid;
106 private final Collection<String> issueKeys;
108 private final PreparedStatement stmt;
109 private final ResultSetIterator<IssueDoc> iterator;
111 IssueIteratorForSingleChunk(DbClient dbClient, @Nullable String projectUuid, @Nullable Collection<String> issueKeys) {
112 checkArgument(issueKeys == null || issueKeys.size() <= DatabaseUtils.PARTITION_SIZE_FOR_ORACLE,
113 "Cannot search for more than " + DatabaseUtils.PARTITION_SIZE_FOR_ORACLE + " issue keys at once. Please provide the keys in smaller chunks.");
114 this.projectUuid = projectUuid;
115 this.issueKeys = issueKeys;
116 this.session = dbClient.openSession(false);
119 String sql = createSql();
120 stmt = dbClient.getMyBatis().newScrollingSelectStatement(session, sql);
121 iterator = createIterator();
122 } catch (Exception e) {
124 throw new IllegalStateException("Fail to prepare SQL request to select all issues", e);
128 private IssueIteratorInternal createIterator() {
131 return new IssueIteratorInternal(stmt);
132 } catch (SQLException e) {
133 DatabaseUtils.closeQuietly(stmt);
134 throw new IllegalStateException("Fail to prepare SQL request to select all issues", e);
139 public boolean hasNext() {
140 return iterator.hasNext();
144 public IssueDoc next() {
145 return iterator.next();
148 private String createSql() {
149 String sql = SQL_ALL;
150 sql += projectUuid == null ? "" : PROJECT_FILTER;
151 if (issueKeys != null && !issueKeys.isEmpty()) {
152 sql += ISSUE_KEY_FILTER_PREFIX;
153 sql += IntStream.range(0, issueKeys.size()).mapToObj(i -> "?").collect(Collectors.joining(","));
154 sql += ISSUE_KEY_FILTER_SUFFIX;
159 private void setParameters(PreparedStatement stmt) throws SQLException {
161 if (projectUuid != null) {
162 stmt.setString(index, projectUuid);
164 stmt.setString(index, projectUuid);
167 if (issueKeys != null) {
168 for (String key : issueKeys) {
169 stmt.setString(index, key);
176 public void close() {
180 DatabaseUtils.closeQuietly(stmt);
185 private static final class IssueIteratorInternal extends ResultSetIterator<IssueDoc> {
187 public IssueIteratorInternal(PreparedStatement stmt) throws SQLException {
192 protected IssueDoc read(ResultSet rs) throws SQLException {
193 IssueDoc doc = new IssueDoc(new HashMap<>(30));
195 String key = rs.getString(1);
197 // all the fields must be present, even if value is null
199 doc.setAssigneeUuid(rs.getString(2));
200 doc.setLine(DatabaseUtils.getInt(rs, 3));
201 doc.setResolution(rs.getString(4));
202 doc.setSeverity(rs.getString(5));
203 doc.setStatus(rs.getString(6));
204 doc.setEffort(getLong(rs, 7));
205 doc.setAuthorLogin(rs.getString(8));
206 doc.setFuncCloseDate(longToDate(getLong(rs, 9)));
207 doc.setFuncCreationDate(longToDate(getLong(rs, 10)));
208 doc.setFuncUpdateDate(longToDate(getLong(rs, 11)));
209 Integer ruleId = rs.getInt(12);
210 doc.setRuleId(ruleId);
211 doc.setLanguage(rs.getString(13));
212 doc.setComponentUuid(rs.getString(14));
213 String moduleUuidPath = rs.getString(15);
214 doc.setModuleUuid(extractModule(moduleUuidPath));
215 doc.setModuleUuidPath(moduleUuidPath);
216 String scope = rs.getString(17);
217 String filePath = extractFilePath(rs.getString(16), scope);
218 doc.setFilePath(filePath);
219 doc.setDirectoryPath(extractDirPath(doc.filePath(), scope));
220 doc.setOrganizationUuid(rs.getString(18));
221 String branchUuid = rs.getString(19);
222 String mainBranchProjectUuid = DatabaseUtils.getString(rs, 20);
223 doc.setBranchUuid(branchUuid);
224 if (mainBranchProjectUuid == null) {
225 doc.setProjectUuid(branchUuid);
226 doc.setIsMainBranch(true);
228 doc.setProjectUuid(mainBranchProjectUuid);
229 doc.setIsMainBranch(false);
231 String tags = rs.getString(21);
232 doc.setTags(IssueIteratorForSingleChunk.TAGS_SPLITTER.splitToList(tags == null ? "" : tags));
233 doc.setType(RuleType.valueOf(rs.getInt(22)));
234 String securityStandards = rs.getString(23);
236 List<String> standards = getSecurityStandards(securityStandards);
237 doc.setOwaspTop10(getOwaspTop10(standards));
238 List<String> cwe = getCwe(standards);
240 doc.setSansTop25(getSansTop25(cwe));
241 doc.setSonarSourceSecurityCategories(getSonarSourceSecurityCategories(cwe));
246 private static String extractDirPath(@Nullable String filePath, String scope) {
247 if (filePath != null) {
248 if (Scopes.DIRECTORY.equals(scope)) {
251 int lastSlashIndex = CharMatcher.anyOf("/").lastIndexIn(filePath);
252 if (lastSlashIndex > 0) {
253 return filePath.substring(0, lastSlashIndex);
261 private static String extractFilePath(@Nullable String filePath, String scope) {
262 // 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
264 // of files and directories.
265 // That's why the file path should be null on modules and projects.
266 if (filePath != null && !Scopes.PROJECT.equals(scope)) {
272 private static String extractModule(String moduleUuidPath) {
273 return Iterators.getLast(IssueIteratorForSingleChunk.MODULE_PATH_SPLITTER.split(moduleUuidPath).iterator());