2 * SonarQube, open source software quality management tool.
3 * Copyright (C) 2008-2014 SonarSource
4 * mailto:contact AT sonarsource DOT com
6 * SonarQube 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 * SonarQube 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.db.migrations.v501;
22 import com.google.common.base.Predicate;
23 import com.google.common.collect.Iterables;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26 import org.sonar.api.utils.System2;
27 import org.sonar.core.persistence.Database;
28 import org.sonar.server.db.migrations.BaseDataChange;
29 import org.sonar.server.db.migrations.Select;
31 import javax.annotation.CheckForNull;
32 import javax.annotation.Nullable;
34 import java.sql.SQLException;
35 import java.util.Date;
36 import java.util.List;
39 * See http://jira.codehaus.org/browse/SONAR-6187
41 * Add a new Characteristic 'Usability' with 2 sub-characteristics 'Accessibility' and 'Ease of Use'
42 * and add a new sub-characteristic 'Compliance' for all characteristics.
44 * Nothing will be done if there's no characteristics in db, as they're all gonna be created by {@link org.sonar.server.startup.RegisterDebtModel}
46 * Before 4.3 the characteristics table contains requirements, then when selecting characteristics we should not forget to exclude them (with a filter on rule_id IS NULL)
49 public class AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigration extends BaseDataChange {
51 private static final Logger LOGGER = LoggerFactory.getLogger(AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigration.class);
53 private static final String COMPLIANCE_NAME = "Compliance";
54 private static final String COMPLIANCE_KEY_SUFFIX = "_COMPLIANCE";
56 private final System2 system;
58 public AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigration(Database db, System2 system) {
64 public void execute(Context context) throws SQLException {
65 CharacteristicsContext characteristicsContext = new CharacteristicsContext(context, system);
67 // On an empty DB, there are no characteristics, they're all gonna be created after in RegisterDebtModel
68 if (!characteristicsContext.characteristics().isEmpty()) {
69 int usabilityOder = moveCharacteristicsDownToBeAbleToInsertUsability(characteristicsContext);
70 createOrUpdateUsabilityCharacteristicAndItsSubCharacteristic(characteristicsContext, usabilityOder);
72 createSubCharacteristic(characteristicsContext, "REUSABILITY" + COMPLIANCE_KEY_SUFFIX, "Reusability " + COMPLIANCE_NAME, "REUSABILITY");
73 createSubCharacteristic(characteristicsContext, "PORTABILITY" + COMPLIANCE_KEY_SUFFIX, "Portability " + COMPLIANCE_NAME, "PORTABILITY");
74 createSubCharacteristic(characteristicsContext, "MAINTAINABILITY" + COMPLIANCE_KEY_SUFFIX, "Maintainability " + COMPLIANCE_NAME, "MAINTAINABILITY");
75 createSubCharacteristic(characteristicsContext, "SECURITY" + COMPLIANCE_KEY_SUFFIX, "Security " + COMPLIANCE_NAME, "SECURITY");
76 createSubCharacteristic(characteristicsContext, "EFFICIENCY" + COMPLIANCE_KEY_SUFFIX, "Efficiency " + COMPLIANCE_NAME, "EFFICIENCY");
77 createSubCharacteristic(characteristicsContext, "CHANGEABILITY" + COMPLIANCE_KEY_SUFFIX, "Changeability " + COMPLIANCE_NAME, "CHANGEABILITY");
78 createSubCharacteristic(characteristicsContext, "RELIABILITY" + COMPLIANCE_KEY_SUFFIX, "Reliability " + COMPLIANCE_NAME, "RELIABILITY");
79 createSubCharacteristic(characteristicsContext, "TESTABILITY" + COMPLIANCE_KEY_SUFFIX, "Testability " + COMPLIANCE_NAME, "TESTABILITY");
84 * If the characteristic 'Security' exists, the new characteristic 'Usability' should be inserted just below it,
85 * so every existing characteristics below Security should move down.
87 * If the characteristic 'Security' does not exists, the new characteristic 'Usability' should be the first one,
88 * so every existing characteristics should move down.
90 * If the characteristic 'Usability' is already at the right place, nothing will be done.
92 private int moveCharacteristicsDownToBeAbleToInsertUsability(CharacteristicsContext characteristicsContext) throws SQLException {
93 Characteristic security = characteristicsContext.findCharacteristicByKey("SECURITY");
94 Characteristic usability = characteristicsContext.findCharacteristicByKey("USABILITY");
96 int usabilityOder = 1;
98 if (security != null) {
99 indexToStart = characteristicsContext.characteristics().indexOf(security) + 1;
100 usabilityOder = security.getOrder() + 1;
103 if (usability == null || usability.getOrder() != usabilityOder) {
104 // Move root characteristics one step lower
105 for (int i = indexToStart; i < characteristicsContext.characteristics().size(); i++) {
106 Characteristic characteristic = characteristicsContext.characteristics().get(i);
107 if (characteristic.getParentId() == null) {
108 characteristicsContext.updateCharacteristicOrder(characteristic.getKey(), characteristic.getOrder() + 1);
112 return usabilityOder;
115 private void createOrUpdateUsabilityCharacteristicAndItsSubCharacteristic(CharacteristicsContext characteristicsContext, int newUsabilityOrder)
116 throws SQLException {
117 String usabilityKey = "USABILITY";
118 Characteristic usability = characteristicsContext.findCharacteristicByKey(usabilityKey);
119 if (usability != null) {
120 if (usability.getOrder() != newUsabilityOrder) {
121 usability.setOrder(newUsabilityOrder);
122 characteristicsContext.updateCharacteristicOrder(usability.getKey(), usability.getOrder());
125 usability = new Characteristic().setKey(usabilityKey).setName("Usability").setOrder(newUsabilityOrder);
126 characteristicsContext.insertCharacteristic(usability);
129 createSubCharacteristic(characteristicsContext, "USABILITY_ACCESSIBILITY", "Accessibility", usabilityKey);
130 createSubCharacteristic(characteristicsContext, "USABILITY_EASE_OF_USE", "Ease of Use", usabilityKey);
131 createSubCharacteristic(characteristicsContext, "USABILITY" + COMPLIANCE_KEY_SUFFIX, "Usability " + COMPLIANCE_NAME, usabilityKey);
134 private void createSubCharacteristic(CharacteristicsContext characteristicsContext,
135 String subCharacteristicKey, String subCharacteristicName, String parentKey) throws SQLException {
136 Characteristic parent = characteristicsContext.findCharacteristicByKey(parentKey);
137 if (parent != null) {
138 Characteristic subCharacteristic = characteristicsContext.findSubCharacteristicByKey(subCharacteristicKey, parent);
139 if (subCharacteristic == null) {
140 characteristicsContext.insertCharacteristic(new Characteristic().setKey(subCharacteristicKey).setName(subCharacteristicName).setParentId(parent.getId()));
143 // If the characteristic parent does not exits, the sub-characteristic is not added
146 private static class Characteristic {
150 private Integer order;
151 private Integer parentId;
153 public Integer getId() {
157 public Characteristic setId(Integer id) {
162 public String getKey() {
166 public Characteristic setKey(String key) {
171 public String getName() {
175 public Characteristic setName(String name) {
181 public Integer getOrder() {
185 public Characteristic setOrder(@Nullable Integer order) {
191 public Integer getParentId() {
195 public Characteristic setParentId(@Nullable Integer parentId) {
196 this.parentId = parentId;
201 private static class CharacteristicsContext {
202 private final System2 system;
205 List<Characteristic> characteristics;
207 public CharacteristicsContext(Context context, System2 system) throws SQLException {
208 this.context = context;
209 this.system = system;
213 private void init() throws SQLException {
214 now = new Date(system.now());
215 characteristics = selectEnabledCharacteristics();
218 public List<Characteristic> characteristics() {
219 return characteristics;
223 public Characteristic findCharacteristicByKey(final String key) {
224 Characteristic characteristic = Iterables.find(characteristics, new Predicate<Characteristic>() {
226 public boolean apply(@Nullable Characteristic input) {
227 return input != null && input.key.equals(key);
230 if (characteristic != null) {
231 if (characteristic.getParentId() != null) {
232 throw new IllegalStateException(String.format("'%s' must be a characteristic", characteristic.getName()));
235 return characteristic;
239 public Characteristic findSubCharacteristicByKey(final String key, Characteristic parent) {
240 Characteristic characteristic = Iterables.find(characteristics, new Predicate<Characteristic>() {
242 public boolean apply(@Nullable Characteristic input) {
243 return input != null && input.key.equals(key);
246 if (characteristic != null) {
247 Integer parentId = characteristic.getParentId();
248 if (parentId == null) {
249 throw new IllegalStateException(String.format("'%s' must be a sub-characteristic", characteristic.getName()));
250 } else if (!characteristic.getParentId().equals(parent.getId())) {
251 throw new IllegalStateException(String.format("'%s' must be defined under '%s'", characteristic.getName(), parent.getName()));
254 return characteristic;
257 private List<Characteristic> selectEnabledCharacteristics() throws SQLException {
258 return context.prepareSelect(
259 // Exclude requirements (to not fail when coming from a version older than 4.3)
260 "SELECT c.id, c.kee, c.name, c.characteristic_order, c.parent_id FROM characteristics c WHERE c.enabled=? AND c.rule_id IS NULL ORDER BY c.characteristic_order")
262 .list(new CharacteristicReader());
265 private int selectCharacteristicId(String key) throws SQLException {
266 return context.prepareSelect(
267 "SELECT c.id FROM characteristics c WHERE c.kee = ? AND c.enabled=?")
270 .get(Select.LONG_READER).intValue();
273 public void insertCharacteristic(Characteristic characteristic) throws SQLException {
274 if (characteristic.getParentId() == null) {
275 LOGGER.info("Insert new characteristic '{}'", characteristic.getKey());
277 LOGGER.info("Insert new sub characteristic '{}'", characteristic.getKey());
280 context.prepareUpsert("INSERT INTO characteristics (kee, name, parent_id, characteristic_order, enabled, created_at) VALUES (?, ?, ?, ?, ?, ?)")
281 .setString(1, characteristic.getKey())
282 .setString(2, characteristic.getName())
283 .setInt(3, characteristic.getParentId())
284 .setInt(4, characteristic.getOrder())
289 characteristic.setId(selectCharacteristicId(characteristic.getKey()));
291 characteristics.add(characteristic);
294 public void updateCharacteristicOrder(String key, Integer order) throws SQLException {
295 LOGGER.info("Update characteristic '{}' order to {}", key, order);
297 context.prepareUpsert("UPDATE characteristics SET characteristic_order=?, updated_at=? WHERE kee=?")
305 private static class CharacteristicReader implements Select.RowReader<Characteristic> {
307 public Characteristic read(Select.Row row) throws SQLException {
308 return new Characteristic()
309 .setId(row.getInt(1))
310 .setKey(row.getString(2))
311 .setName(row.getString(3))
312 .setOrder(row.getInt(4))
313 .setParentId(row.getInt(5));