3 * Copyright (C) 2009-2016 SonarSource SA
4 * mailto:contact 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.
21 package org.sonar.server.component.es;
23 import com.google.common.base.Joiner;
24 import java.sql.PreparedStatement;
25 import java.sql.ResultSet;
26 import java.sql.SQLException;
27 import java.util.Date;
28 import java.util.HashMap;
30 import java.util.Optional;
31 import javax.annotation.Nullable;
32 import org.apache.commons.lang.StringUtils;
33 import org.sonar.api.resources.Qualifiers;
34 import org.sonar.api.resources.Scopes;
35 import org.sonar.db.DbClient;
36 import org.sonar.db.DbSession;
37 import org.sonar.db.ResultSetIterator;
39 import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
40 import static org.sonar.api.measures.Metric.ValueType.DATA;
41 import static org.sonar.api.measures.Metric.ValueType.DISTRIB;
42 import static org.sonar.db.DatabaseUtils.repeatCondition;
44 public class ProjectMeasuresResultSetIterator extends ResultSetIterator<ProjectMeasuresDoc> {
46 private static final Joiner METRICS_JOINER = Joiner.on("','");
48 private static final String SQL_PROJECTS = "SELECT p.uuid, p.kee, p.name, s.uuid, s.created_at FROM projects p " +
49 "LEFT OUTER JOIN snapshots s ON s.component_uuid=p.uuid AND s.islast=? " +
50 "WHERE p.enabled=? AND p.scope=? AND p.qualifier=?";
52 private static final String DATE_FILTER = " AND s.created_at>?";
54 private static final String PROJECT_FILTER = " AND p.uuid=?";
56 private static final String SQL_METRICS = "SELECT m.id, m.name FROM metrics m " +
57 "WHERE m.val_type NOT IN ('" + METRICS_JOINER.join(DATA.name(), DISTRIB.name()) + "') " +
58 "AND m.enabled=? AND m.hidden=?";
60 private static final String SQL_MEASURES = "SELECT pm.metric_id, pm.value, pm.variation_value_1, pm.text_value FROM project_measures pm " +
61 "WHERE pm.component_uuid = ? AND pm.analysis_uuid = ? " +
62 "AND pm.metric_id IN ({metricIds}) " +
63 "AND (pm.value IS NOT NULL OR pm.variation_value_1 IS NOT NULL OR pm.text_value IS NOT NULL) " +
64 "AND pm.person_id IS NULL ";
66 private final DbSession dbSession;
67 private final Map<Long, String> metrics;
69 private ProjectMeasuresResultSetIterator(PreparedStatement stmt, DbSession dbSession, Map<Long, String> metrics) throws SQLException {
71 this.dbSession = dbSession;
72 this.metrics = metrics;
75 static ProjectMeasuresResultSetIterator create(DbClient dbClient, DbSession session, long afterDate, @Nullable String projectUuid) {
77 PreparedStatement projectsStatement = createProjectsStatement(dbClient, session, afterDate, projectUuid);
78 Map<Long, String> metricIds = selectMetricIds(session);
79 return new ProjectMeasuresResultSetIterator(projectsStatement, session, metricIds);
80 } catch (SQLException e) {
81 throw new IllegalStateException("Fail to execute request to select all project measures", e);
85 private static PreparedStatement createProjectsStatement(DbClient dbClient, DbSession session, long afterDate, @Nullable String projectUuid) {
87 String sql = SQL_PROJECTS;
88 sql += afterDate <= 0L ? "" : DATE_FILTER;
89 sql += projectUuid == null ? "" : PROJECT_FILTER;
90 PreparedStatement stmt = dbClient.getMyBatis().newScrollingSelectStatement(session, sql);
91 stmt.setBoolean(1, true);
92 stmt.setBoolean(2, true);
93 stmt.setString(3, Scopes.PROJECT);
94 stmt.setString(4, Qualifiers.PROJECT);
97 stmt.setLong(index, afterDate);
100 if (projectUuid != null) {
101 stmt.setString(index, projectUuid);
104 } catch (SQLException e) {
105 throw new IllegalStateException("Fail to prepare SQL request to select all project measures", e);
109 private static Map<Long, String> selectMetricIds(DbSession session) {
110 Map<Long, String> metrics = new HashMap<>();
111 try (PreparedStatement stmt = createMetricsStatement(session);
112 ResultSet rs = stmt.executeQuery()) {
114 metrics.put(rs.getLong(1), rs.getString(2));
117 } catch (SQLException e) {
118 throw new IllegalStateException("Fail to execute request to select all metrics", e);
122 private static PreparedStatement createMetricsStatement(DbSession session) throws SQLException {
123 PreparedStatement stmt = session.getConnection().prepareStatement(SQL_METRICS);
124 stmt.setBoolean(1, true);
125 stmt.setBoolean(2, false);
130 protected ProjectMeasuresDoc read(ResultSet rs) throws SQLException {
131 String projectUuid = rs.getString(1);
132 Measures measures = selectMeasures(projectUuid, rs.getString(4));
133 ProjectMeasuresDoc doc = new ProjectMeasuresDoc()
135 .setKey(rs.getString(2))
136 .setName(rs.getString(3))
137 .setQualityGate(measures.qualityGateStatus)
138 .setMeasuresFromMap(measures.numericMeasures);
139 long analysisDate = rs.getLong(5);
140 doc.setAnalysedAt(rs.wasNull() ? null : new Date(analysisDate));
144 private Measures selectMeasures(String projectUuid, String analysisUuid) {
145 Measures measures = new Measures();
146 try (PreparedStatement stmt = createMeasuresStatement(projectUuid, analysisUuid);
147 ResultSet rs = stmt.executeQuery()) {
149 readMeasure(rs, measures);
152 } catch (Exception e) {
153 throw new IllegalStateException(String.format("Fail to execute request to select measures of project %s, analysis %s", projectUuid, analysisUuid), e);
157 private void readMeasure(ResultSet rs, Measures measures) throws SQLException {
158 String metricKey = metrics.get(rs.getLong(1));
159 Optional<Double> value = metricKey.startsWith("new_") ? getDouble(rs, 3) : getDouble(rs, 2);
160 if (value.isPresent()) {
161 measures.addNumericMeasure(metricKey, value.get());
163 } else if (ALERT_STATUS_KEY.equals(metricKey)) {
164 String textValue = rs.getString(4);
166 measures.setQualityGateStatus(textValue);
170 throw new IllegalArgumentException("Measure has no value");
173 private PreparedStatement createMeasuresStatement(String projectUuid, String analysisUuid) throws SQLException {
174 String sql = StringUtils.replace(SQL_MEASURES, "{metricIds}", repeatCondition("?", metrics.size(), ","));
175 PreparedStatement stmt = dbSession.getConnection().prepareStatement(sql);
176 stmt.setString(1, projectUuid);
177 stmt.setString(2, analysisUuid);
179 for (Long metricId : metrics.keySet()) {
180 stmt.setLong(index, metricId);
186 private static Optional<Double> getDouble(ResultSet rs, int index) {
188 Double value = rs.getDouble(index);
190 return Optional.of(value);
192 return Optional.empty();
193 } catch (SQLException e) {
194 throw new IllegalStateException("Fail to get double value", e);
198 private static class Measures {
199 private Map<String, Object> numericMeasures = new HashMap<>();
200 private String qualityGateStatus;
202 Measures addNumericMeasure(String metricKey, double value) {
203 numericMeasures.put(metricKey, value);
207 Measures setQualityGateStatus(String qualityGateStatus) {
208 this.qualityGateStatus = qualityGateStatus;