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;
38 import static com.google.common.collect.Lists.newArrayList;
41 * See http://jira.codehaus.org/browse/SONAR-6187
43 * Add a new Characteristic 'Usability' with 2 sub-characteristics 'Accessibility' and 'Ease of Use'
44 * and add a new sub-characteristic 'Compliance' for all characteristics.
48 public class AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigration extends BaseDataChange {
50 private static final Logger LOGGER = LoggerFactory.getLogger(AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigration.class);
52 private static final String COMPLIANCE_NAME = "Compliance";
53 private static final String COMPLIANCE_KEY_SUFFIX = "_COMPLIANCE";
55 private final System2 system;
57 public AddCharacteristicUsabilityAndSubCharacteristicsComplianceMigration(Database db, System2 system) {
63 public void execute(Context context) throws SQLException {
64 CharacteristicsContext characteristicsContext = new CharacteristicsContext(context, system);
66 int usabilityOder = moveCharacteristicsDownToBeAbleToInsertUsability(characteristicsContext);
67 createOrUpdateUsabilityCharacteristicAndItsSubCharacteristic(characteristicsContext, usabilityOder);
69 createSubCharacteristic(characteristicsContext, "REUSABILITY" + COMPLIANCE_KEY_SUFFIX, "Reusability " + COMPLIANCE_NAME, "REUSABILITY");
70 createSubCharacteristic(characteristicsContext, "PORTABILITY" + COMPLIANCE_KEY_SUFFIX, "Portability " + COMPLIANCE_NAME, "PORTABILITY");
71 createSubCharacteristic(characteristicsContext, "MAINTAINABILITY" + COMPLIANCE_KEY_SUFFIX, "Maintainability " + COMPLIANCE_NAME, "MAINTAINABILITY");
72 createSubCharacteristic(characteristicsContext, "SECURITY" + COMPLIANCE_KEY_SUFFIX, "Security " + COMPLIANCE_NAME, "SECURITY");
73 createSubCharacteristic(characteristicsContext, "EFFICIENCY" + COMPLIANCE_KEY_SUFFIX, "Efficiency " + COMPLIANCE_NAME, "EFFICIENCY");
74 createSubCharacteristic(characteristicsContext, "CHANGEABILITY" + COMPLIANCE_KEY_SUFFIX, "Changeability " + COMPLIANCE_NAME, "CHANGEABILITY");
75 createSubCharacteristic(characteristicsContext, "RELIABILITY" + COMPLIANCE_KEY_SUFFIX, "Reliability " + COMPLIANCE_NAME, "RELIABILITY");
76 createSubCharacteristic(characteristicsContext, "TESTABILITY" + COMPLIANCE_KEY_SUFFIX, "Testability " + COMPLIANCE_NAME, "TESTABILITY");
80 * If the characteristic 'Security' exists, the new characteristic 'Usability' should be inserted just below it,
81 * so every existing characteristics below Security should move down.
83 * If the characteristic 'Security' does not exists, the new characteristic 'Usability' should be the first one,
84 * so every existing characteristics should move down.
86 * If the characteristic 'Usability' is already at the right place, nothing will be done.
88 private int moveCharacteristicsDownToBeAbleToInsertUsability(CharacteristicsContext characteristicsContext) throws SQLException {
89 Characteristic security = characteristicsContext.findCharacteristicByKey("SECURITY");
90 Characteristic usability = characteristicsContext.findCharacteristicByKey("USABILITY");
92 int usabilityOder = 1;
94 if (security != null) {
95 indexToStart = characteristicsContext.characteristics().indexOf(security) + 1;
96 usabilityOder = security.getOrder() + 1;
99 if (usability == null || usability.getOrder() != usabilityOder) {
100 // Move root characteristics one step lower
101 for (int i = indexToStart; i < characteristicsContext.characteristics().size(); i++) {
102 Characteristic characteristic = characteristicsContext.characteristics().get(i);
103 if (characteristic.getParentId() == null) {
104 characteristicsContext.updateCharacteristicOrder(characteristic.getKey(), characteristic.getOrder() + 1);
108 return usabilityOder;
111 private void createOrUpdateUsabilityCharacteristicAndItsSubCharacteristic(CharacteristicsContext characteristicsContext, int newUsabilityOrder)
112 throws SQLException {
113 String usabilityKey = "USABILITY";
114 Characteristic usability = characteristicsContext.findCharacteristicByKey(usabilityKey);
115 if (usability != null) {
116 if (usability.getOrder() != newUsabilityOrder) {
117 usability.setOrder(newUsabilityOrder);
118 characteristicsContext.updateCharacteristicOrder(usability.getKey(), usability.getOrder());
121 usability = new Characteristic().setKey(usabilityKey).setName("Usability").setOrder(newUsabilityOrder);
122 characteristicsContext.insertCharacteristic(usability);
125 createSubCharacteristic(characteristicsContext, "USABILITY_ACCESSIBILITY", "Accessibility", usabilityKey);
126 createSubCharacteristic(characteristicsContext, "USABILITY_EASE_OF_USE", "Ease of Use", usabilityKey);
127 createSubCharacteristic(characteristicsContext, "USABILITY" + COMPLIANCE_KEY_SUFFIX, "Usability " + COMPLIANCE_NAME, usabilityKey);
130 private void createSubCharacteristic(CharacteristicsContext characteristicsContext,
131 String subCharacteristicKey, String subCharacteristicName, String parentKey) throws SQLException {
132 Characteristic parent = characteristicsContext.findCharacteristicByKey(parentKey);
133 if (parent != null) {
134 Characteristic subCharacteristic = characteristicsContext.findSubCharacteristicByKey(subCharacteristicKey, parent);
135 if (subCharacteristic == null) {
136 characteristicsContext.insertCharacteristic(new Characteristic().setKey(subCharacteristicKey).setName(subCharacteristicName).setParentId(parent.getId()));
139 // If the characteristic parent does not exits, the sub-characteristic is not added
142 private static class Characteristic {
146 private Integer order;
147 private Integer parentId;
149 public Characteristic setId(Integer id) {
154 public Integer getId() {
158 public Characteristic setKey(String key) {
163 public String getKey() {
167 public Characteristic setName(String name) {
172 public String getName() {
176 public Characteristic setOrder(@Nullable Integer order) {
182 public Integer getOrder() {
186 public Characteristic setParentId(@Nullable Integer parentId) {
187 this.parentId = parentId;
192 public Integer getParentId() {
197 private static class CharacteristicsContext {
199 private final System2 system;
201 List<Characteristic> characteristics;
203 public CharacteristicsContext(Context context, System2 system) throws SQLException {
204 this.context = context;
205 this.system = system;
209 private void init() throws SQLException {
210 now = new Date(system.now());
211 characteristics = selectEnabledCharacteristics();
214 public List<Characteristic> characteristics() {
215 return characteristics;
219 public Characteristic findCharacteristicByKey(final String key) {
220 Characteristic characteristic = Iterables.find(characteristics, new Predicate<Characteristic>() {
222 public boolean apply(@Nullable Characteristic input) {
223 return input != null && input.key.equals(key);
226 if (characteristic != null) {
227 if (characteristic.getParentId() != null) {
228 throw new IllegalStateException(String.format("'%s' must be a characteristic", characteristic.getName()));
231 return characteristic;
235 public Characteristic findSubCharacteristicByKey(final String key, Characteristic parent) {
236 Characteristic characteristic = Iterables.find(characteristics, new Predicate<Characteristic>() {
238 public boolean apply(@Nullable Characteristic input) {
239 return input != null && input.key.equals(key);
242 if (characteristic != null) {
243 Integer parentId = characteristic.getParentId();
244 if (parentId == null) {
245 throw new IllegalStateException(String.format("'%s' must be a sub-characteristic", characteristic.getName()));
246 } else if (!characteristic.getParentId().equals(parent.getId())) {
247 throw new IllegalStateException(String.format("'%s' must be defined under '%s'", characteristic.getName(), parent.getName()));
250 return characteristic;
253 private List<Characteristic> selectEnabledCharacteristics() throws SQLException {
254 return context.prepareSelect(
255 "SELECT c.id, c.kee, c.name, c.characteristic_order, c.parent_id FROM characteristics c WHERE c.enabled=? ORDER BY c.characteristic_order")
257 .list(new CharacteristicReader());
260 private int selectCharacteristicId(String key) throws SQLException {
261 return context.prepareSelect(
262 "SELECT c.id FROM characteristics c WHERE c.kee = ? AND c.enabled=?")
265 .get(Select.LONG_READER).intValue();
268 public void insertCharacteristic(Characteristic characteristic) throws SQLException {
269 if (characteristic.getParentId() == null) {
270 LOGGER.info("Insert new characteristic '{}'", characteristic.getKey());
272 LOGGER.info("Insert new sub characteristic '{}'", characteristic.getKey());
275 context.prepareUpsert("INSERT INTO characteristics (kee, name, parent_id, characteristic_order, enabled, created_at) VALUES (?, ?, ?, ?, ?, ?)")
276 .setString(1, characteristic.getKey())
277 .setString(2, characteristic.getName())
278 .setInt(3, characteristic.getParentId())
279 .setInt(4, characteristic.getOrder())
284 characteristic.setId(selectCharacteristicId(characteristic.getKey()));
286 characteristics.add(characteristic);
289 public void updateCharacteristicOrder(String key, Integer order) throws SQLException {
290 LOGGER.info("Update characteristic '{}' order to {}", key, order);
292 context.prepareUpsert("UPDATE characteristics SET characteristic_order=?, updated_at=? WHERE kee=?")
300 private static class CharacteristicReader implements Select.RowReader<Characteristic> {
302 public Characteristic read(Select.Row row) throws SQLException {
303 return new Characteristic()
304 .setId(row.getInt(1))
305 .setKey(row.getString(2))
306 .setName(row.getString(3))
307 .setOrder(row.getInt(4))
308 .setParentId(row.getInt(5));