1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
|
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.startup;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.platform.ServerUpgradeStatus;
import org.sonar.api.rules.Rule;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.System2;
import org.sonar.core.persistence.Database;
import org.sonar.core.rule.RuleDto;
import org.sonar.core.technicaldebt.db.RequirementDao;
import org.sonar.core.technicaldebt.db.RequirementDto;
import org.sonar.server.db.migrations.MassUpdater;
import org.sonar.server.db.migrations.SqlUtil;
import org.sonar.server.rule.RuleRegistration;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import java.sql.*;
import java.util.Collection;
import java.util.List;
/**
* This script copy every requirements from characteristics table (every row where rule_id is not null) to the rules table.
*
* This script need to be executed after rules registration because default debt columns (characteristics, function, factor and offset) has to be populated
* in order to be able to compare default values with overridden values.
*
* @since 4.3 this component could be removed after 4 or 5 releases.
*/
public class CopyRequirementsFromCharacteristicsToRules {
private static final Logger LOGGER = LoggerFactory.getLogger(CopyRequirementsFromCharacteristicsToRules.class);
private final System2 system2;
private final Database db;
private final ServerUpgradeStatus status;
private final RequirementDao requirementDao;
/**
* @param ruleRegistration used only to be started after init of rules
*/
public CopyRequirementsFromCharacteristicsToRules(Database database, RequirementDao requirementDao, ServerUpgradeStatus status, RuleRegistration ruleRegistration) {
this(database, requirementDao, status, System2.INSTANCE);
}
@VisibleForTesting
CopyRequirementsFromCharacteristicsToRules(Database database, RequirementDao requirementDao, ServerUpgradeStatus status, System2 system2) {
this.db = database;
this.system2 = system2;
this.status = status;
this.requirementDao = requirementDao;
}
public void start() {
if (mustDoPurge()) {
doPurge();
}
}
private boolean mustDoPurge() {
return status.isUpgraded() && status.getInitialDbVersion() <= 520;
}
private void doPurge() {
LOGGER.info("Copying requirement from characteristics to rules");
copyRequirementsFromCharacteristicsToRules();
LOGGER.info("Deleting requirements data");
removeRequirementsDataFromCharacteristics();
}
private void copyRequirementsFromCharacteristicsToRules() {
List<RequirementDto> requirementDtos = requirementDao.selectRequirements();
final Multimap<Integer, RequirementDto> requirementsByRuleId = ArrayListMultimap.create();
for (RequirementDto requirementDto : requirementDtos) {
requirementsByRuleId.put(requirementDto.getRuleId(), requirementDto);
}
new MassUpdater(db).execute(
new RuleInputLoader(),
new RuleInputConvertor(requirementsByRuleId, system2)
);
}
private static class RuleInputLoader implements MassUpdater.InputLoader<RuleRow>{
@Override
public String selectSql() {
return "SELECT r.id,r.characteristic_id,r.remediation_function,r.remediation_factor,r.remediation_offset," +
"r.default_characteristic_id,r.default_remediation_function,r.default_remediation_factor,r.default_remediation_offset,r.status " +
"FROM rules r";
}
@Override
public RuleRow load(ResultSet rs) throws SQLException {
RuleRow ruleRow = new RuleRow();
ruleRow.setId(SqlUtil.getInt(rs, 1));
ruleRow.setCharacteristicId(SqlUtil.getInt(rs, 2));
ruleRow.setFunction(rs.getString(3));
ruleRow.setFactor(rs.getString(4));
ruleRow.setOffset(rs.getString(5));
ruleRow.setDefaultCharacteristicId(SqlUtil.getInt(rs, 6));
ruleRow.setDefaultFunction(rs.getString(7));
ruleRow.setDefaultFactor(rs.getString(8));
ruleRow.setDefaultOffset(rs.getString(9));
ruleRow.setStatus(rs.getString(10));
return ruleRow;
}
}
private static class RuleInputConvertor implements MassUpdater.InputConverter<RuleRow>{
private final Multimap<Integer, RequirementDto> requirementsByRuleId;
private final System2 system2;
private RuleInputConvertor(Multimap<Integer, RequirementDto> requirementsByRuleId, System2 system2) {
this.requirementsByRuleId = requirementsByRuleId;
this.system2 = system2;
}
@Override
public String updateSql() {
return "UPDATE rules SET characteristic_id=?,remediation_function=?,remediation_factor=?,remediation_offset=?,updated_at=? WHERE id=?";
}
@Override
public boolean convert(RuleRow ruleRow, PreparedStatement updateStatement) throws SQLException {
Collection<RequirementDto> requirementsForRule = requirementsByRuleId.get(ruleRow.id);
if (!requirementsForRule.isEmpty()) {
return convert(ruleRow, updateStatement, requirementsForRule);
}
// Nothing to do when no requirements for current rule
return false;
}
private boolean convert(RuleRow ruleRow, PreparedStatement updateStatement, Collection<RequirementDto> requirementsForRule) throws SQLException {
RequirementDto enabledRequirement = Iterables.find(requirementsForRule, new Predicate<RequirementDto>() {
@Override
public boolean apply(RequirementDto input) {
return input.isEnabled();
}
}, null);
if (enabledRequirement == null && !Rule.STATUS_REMOVED.equals(ruleRow.getStatus())) {
// If no requirements are enable, it means that the requirement has been disabled for this rule
updateStatement.setInt(1, RuleDto.DISABLED_CHARACTERISTIC_ID);
updateStatement.setNull(2, Types.VARCHAR);
updateStatement.setNull(3, Types.VARCHAR);
updateStatement.setNull(4, Types.VARCHAR);
updateStatement.setTimestamp(5, new Timestamp(system2.now()));
updateStatement.setInt(6, ruleRow.getId());
return true;
} else if (enabledRequirement != null) {
// If one requirement is enable, it means either that this requirement has been set from SQALE, or that it come from a XML model definition
ruleRow.setCharacteristicId(enabledRequirement.getParentId());
ruleRow.setFunction(enabledRequirement.getFunction().toUpperCase());
ruleRow.setFactor(convertDuration(enabledRequirement.getFactorValue(), enabledRequirement.getFactorUnit()));
ruleRow.setOffset(convertDuration(enabledRequirement.getOffsetValue(), enabledRequirement.getOffsetUnit()));
if (!isDebtDefaultValuesSameAsOverriddenValues(ruleRow)) {
// Default values on debt are not the same that ones set by SQALE, update the rule
updateStatement.setInt(1, ruleRow.getCharacteristicId());
updateStatement.setString(2, ruleRow.getFunction());
updateStatement.setString(3, ruleRow.getFactor());
updateStatement.setString(4, ruleRow.getOffset());
updateStatement.setTimestamp(5, new Timestamp(system2.now()));
updateStatement.setInt(6, ruleRow.getId());
return true;
}
// When default values on debt are the same that ones set by SQALE, nothing to do
}
return false;
}
}
@CheckForNull
@VisibleForTesting
static String convertDuration(@Nullable Double oldValue, @Nullable String oldUnit) {
if (oldValue != null && oldValue > 0) {
String unit = oldUnit != null ? oldUnit : Duration.DAY;
// min is replaced by mn
unit = "mn".equals(unit) ? Duration.MINUTE : unit;
// As value is stored in double, we have to round it in order to have an integer (for instance, if it was 1.6, we'll use 2)
return Integer.toString((int) Math.round(oldValue)) + unit;
}
return null;
}
@VisibleForTesting
static boolean isDebtDefaultValuesSameAsOverriddenValues(RuleRow ruleRow) {
return new EqualsBuilder()
.append(ruleRow.getDefaultCharacteristicId(), ruleRow.getCharacteristicId())
.append(ruleRow.getDefaultFunction(), ruleRow.getFunction())
.append(ruleRow.getDefaultFactor(), ruleRow.getFactor())
.append(ruleRow.getDefaultOffset(), ruleRow.getOffset())
.isEquals();
}
private void removeRequirementsDataFromCharacteristics() {
Connection connection = null;
Statement stmt = null;
try {
connection = db.getDataSource().getConnection();
stmt = connection.createStatement();
stmt.executeUpdate("DELETE FROM characteristics WHERE rule_id IS NOT NULL");
} catch (SQLException e) {
throw new IllegalStateException("Fail to remove requirements data from characteristics", e);
} finally {
DbUtils.closeQuietly(stmt);
DbUtils.closeQuietly(connection);
}
}
@VisibleForTesting
static class RuleRow {
private Integer id;
private Integer characteristicId;
private Integer defaultCharacteristicId;
private String function;
private String defaultFunction;
private String factor;
private String defaultFactor;
private String offset;
private String defaultOffset;
private String status;
Integer getId() {
return id;
}
RuleRow setId(Integer id) {
this.id = id;
return this;
}
@CheckForNull
Integer getCharacteristicId() {
return characteristicId;
}
RuleRow setCharacteristicId(@Nullable Integer characteristicId) {
this.characteristicId = characteristicId;
return this;
}
@CheckForNull
Integer getDefaultCharacteristicId() {
return defaultCharacteristicId;
}
RuleRow setDefaultCharacteristicId(@Nullable Integer defaultCharacteristicId) {
this.defaultCharacteristicId = defaultCharacteristicId;
return this;
}
@CheckForNull
String getFunction() {
return function;
}
RuleRow setFunction(@Nullable String function) {
this.function = function;
return this;
}
@CheckForNull
String getDefaultFunction() {
return defaultFunction;
}
RuleRow setDefaultFunction(@Nullable String defaultFunction) {
this.defaultFunction = defaultFunction;
return this;
}
@CheckForNull
String getFactor() {
return factor;
}
RuleRow setFactor(@Nullable String factor) {
this.factor = factor;
return this;
}
@CheckForNull
String getDefaultFactor() {
return defaultFactor;
}
RuleRow setDefaultFactor(@Nullable String defaultFactor) {
this.defaultFactor = defaultFactor;
return this;
}
@CheckForNull
String getOffset() {
return offset;
}
RuleRow setOffset(@Nullable String offset) {
this.offset = offset;
return this;
}
@CheckForNull
String getDefaultOffset() {
return defaultOffset;
}
RuleRow setDefaultOffset(@Nullable String defaultOffset) {
this.defaultOffset = defaultOffset;
return this;
}
String getStatus() {
return status;
}
RuleRow setStatus(String status) {
this.status = status;
return this;
}
}
}
|