]> source.dussan.org Git - sonarqube.git/blob
213a55599e16246018b1cbdaaa1c543e39e58f5a
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20 package org.sonar.server.issue.index;
21
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;
42
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;
49
50 /**
51  * Scrolls over table ISSUES and reads documents to populate
52  * the issues index
53  */
54 class IssueIteratorForSingleChunk implements IssueIterator {
55
56   private static final String[] FIELDS = {
57     // column 1
58     "i.kee",
59     "i.assignee",
60     "i.line",
61     "i.resolution",
62     "i.severity",
63     "i.status",
64     "i.effort",
65     "i.author_login",
66     "i.issue_close_date",
67     "i.issue_creation_date",
68
69     // column 11
70     "i.issue_update_date",
71     "r.uuid",
72     "r.language",
73     "c.uuid",
74     "c.path",
75     "c.scope",
76     "c.branch_uuid",
77     "pb.is_main",
78     "pb.project_uuid",
79
80     // column 22
81     "i.tags",
82     "i.issue_type",
83     "r.security_standards",
84     "c.qualifier",
85     "n.uuid",
86     "i.code_variants"
87   };
88
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 ";
93
94   private static final String SQL_NEW_CODE_JOIN = "left join new_code_reference_issues n on n.issue_key = i.kee ";
95
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 = ") ";
99
100   static final Splitter STRING_LIST_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
101
102   private final DbSession session;
103
104   @CheckForNull
105   private final String branchUuid;
106
107   @CheckForNull
108   private final Collection<String> issueKeys;
109
110   private final PreparedStatement stmt;
111   private final ResultSetIterator<IssueDoc> iterator;
112
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);
119
120     try {
121       String sql = createSql();
122       stmt = dbClient.getMyBatis().newScrollingSelectStatement(session, sql);
123       iterator = createIterator();
124     } catch (Exception e) {
125       session.close();
126       throw new IllegalStateException("Fail to prepare SQL request to select all issues", e);
127     }
128   }
129
130   private IssueIteratorInternal createIterator() {
131     try {
132       setParameters(stmt);
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);
137     }
138   }
139
140   @Override
141   public boolean hasNext() {
142     return iterator.hasNext();
143   }
144
145   @Override
146   public IssueDoc next() {
147     return iterator.next();
148   }
149
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;
157     }
158     sql += SQL_NEW_CODE_JOIN;
159     return sql;
160   }
161
162   private void setParameters(PreparedStatement stmt) throws SQLException {
163     int index = 1;
164     if (branchUuid != null) {
165       stmt.setString(index, branchUuid);
166       index++;
167       stmt.setString(index, branchUuid);
168       index++;
169     }
170     if (issueKeys != null) {
171       for (String key : issueKeys) {
172         stmt.setString(index, key);
173         index++;
174       }
175     }
176   }
177
178   @Override
179   public void close() {
180     try {
181       iterator.close();
182     } finally {
183       DatabaseUtils.closeQuietly(stmt);
184       session.close();
185     }
186   }
187
188   private static final class IssueIteratorInternal extends ResultSetIterator<IssueDoc> {
189
190     public IssueIteratorInternal(PreparedStatement stmt) throws SQLException {
191       super(stmt);
192     }
193
194     @Override
195     protected IssueDoc read(ResultSet rs) throws SQLException {
196       IssueDoc doc = new IssueDoc(new HashMap<>(30));
197
198       String key = rs.getString(1);
199
200       // all the fields must be present, even if value is null
201       doc.setKey(key);
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)));
228
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());
240
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));
245       return doc;
246     }
247
248     @CheckForNull
249     private static String extractDirPath(@Nullable String filePath, String scope) {
250       if (filePath != null) {
251         if (Scopes.DIRECTORY.equals(scope)) {
252           return filePath;
253         }
254         int lastSlashIndex = CharMatcher.anyOf("/").lastIndexIn(filePath);
255         if (lastSlashIndex > 0) {
256           return filePath.substring(0, lastSlashIndex);
257         }
258         return "/";
259       }
260       return null;
261     }
262
263     @CheckForNull
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
266       // path
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)) {
270         return filePath;
271       }
272       return null;
273     }
274
275   }
276 }