All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.db.version.v51.AddNewCharacteristics Maven / Gradle / Ivy

/*
 * SonarQube
 * Copyright (C) 2009-2016 SonarSource SA
 * mailto:contact AT sonarsource DOT com
 *
 * This program 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.
 *
 * This program 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.db.version.v51;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.utils.MessageException;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.Database;
import org.sonar.db.version.BaseDataChange;
import org.sonar.db.version.Select;

/**
 * See http://jira.sonarsource.com/browse/SONAR-6187
 *
 * Add a new Characteristic 'Usability' with 2 sub-characteristics 'Accessibility' and 'Ease of Use'
 * and add a new sub-characteristic 'Compliance' for all characteristics.
 *
 * 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}
 *
 * 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)
 *
 */
public class AddNewCharacteristics extends BaseDataChange {

  private static final Logger LOGGER = Loggers.get(AddNewCharacteristics.class);

  private static final String COMPLIANCE_NAME = "Compliance";
  private static final String COMPLIANCE_KEY_SUFFIX = "_COMPLIANCE";

  private static final String SECURITY_KEY = "SECURITY";
  private static final String USABILITY_KEY = "USABILITY";

  private static final String ERROR_SUFFIX = "Please restore your DB backup, start the previous version of SonarQube " +
    "and update your SQALE model to fix this issue before trying again to run the migration.";

  private final System2 system;

  public AddNewCharacteristics(Database db, System2 system) {
    super(db);
    this.system = system;
  }

  @Override
  public void execute(Context context) throws SQLException {
    CharacteristicsContext characteristicsContext = new CharacteristicsContext(context, system);

    // On an empty DB, there are no characteristics, they're all gonna be created after in RegisterDebtModel
    if (!characteristicsContext.characteristics().isEmpty()) {
      int usabilityOder = moveCharacteristicsDownToBeAbleToInsertUsability(characteristicsContext);
      createOrUpdateUsabilityCharacteristicAndItsSubCharacteristic(characteristicsContext, usabilityOder);

      createSubCharacteristic(characteristicsContext, "REUSABILITY" + COMPLIANCE_KEY_SUFFIX, "Reusability " + COMPLIANCE_NAME, "REUSABILITY");
      createSubCharacteristic(characteristicsContext, "PORTABILITY" + COMPLIANCE_KEY_SUFFIX, "Portability " + COMPLIANCE_NAME, "PORTABILITY");
      createSubCharacteristic(characteristicsContext, "MAINTAINABILITY" + COMPLIANCE_KEY_SUFFIX, "Maintainability " + COMPLIANCE_NAME, "MAINTAINABILITY");
      createSubCharacteristic(characteristicsContext, SECURITY_KEY + COMPLIANCE_KEY_SUFFIX, "Security " + COMPLIANCE_NAME, SECURITY_KEY);
      createSubCharacteristic(characteristicsContext, "EFFICIENCY" + COMPLIANCE_KEY_SUFFIX, "Efficiency " + COMPLIANCE_NAME, "EFFICIENCY");
      createSubCharacteristic(characteristicsContext, "CHANGEABILITY" + COMPLIANCE_KEY_SUFFIX, "Changeability " + COMPLIANCE_NAME, "CHANGEABILITY");
      createSubCharacteristic(characteristicsContext, "RELIABILITY" + COMPLIANCE_KEY_SUFFIX, "Reliability " + COMPLIANCE_NAME, "RELIABILITY");
      createSubCharacteristic(characteristicsContext, "TESTABILITY" + COMPLIANCE_KEY_SUFFIX, "Testability " + COMPLIANCE_NAME, "TESTABILITY");
    }
  }

  /**
   * If the characteristic 'Security' exists, the new characteristic 'Usability' should be inserted just below it,
   * so every existing characteristics below Security should move down.
   *
   * If the characteristic 'Security' does not exists, the new characteristic 'Usability' should be the first one,
   * so every existing characteristics should move down.
   *
   * If the characteristic 'Usability' is already at the right place, nothing will be done.
   */
  private static int moveCharacteristicsDownToBeAbleToInsertUsability(CharacteristicsContext characteristicsContext) throws SQLException {
    Characteristic security = characteristicsContext.findCharacteristicByKey(SECURITY_KEY);
    Characteristic usability = characteristicsContext.findCharacteristicByKey(USABILITY_KEY);

    int usabilityOder = 1;
    int indexToStart = 0;
    if (security != null) {
      indexToStart = characteristicsContext.characteristics().indexOf(security) + 1;
      usabilityOder = security.getOrder() + 1;
    }

    if (usability == null || usability.getOrder() != usabilityOder) {
      // Move root characteristics one step lower
      for (int i = indexToStart; i < characteristicsContext.characteristics().size(); i++) {
        Characteristic characteristic = characteristicsContext.characteristics().get(i);
        if (characteristic.getParentId() == null) {
          characteristicsContext.updateCharacteristicOrder(characteristic.getKey(), characteristic.getOrder() + 1);
        }
      }
    }
    return usabilityOder;
  }

  private void createOrUpdateUsabilityCharacteristicAndItsSubCharacteristic(CharacteristicsContext characteristicsContext, int newUsabilityOrder)
    throws SQLException {
    String usabilityKey = USABILITY_KEY;
    Characteristic usability = characteristicsContext.findCharacteristicByKey(usabilityKey);
    if (usability != null) {
      if (usability.getOrder() != newUsabilityOrder) {
        usability.setOrder(newUsabilityOrder);
        characteristicsContext.updateCharacteristicOrder(usability.getKey(), usability.getOrder());
      }
    } else {
      usability = new Characteristic().setKey(usabilityKey).setName("Usability").setOrder(newUsabilityOrder);
      characteristicsContext.insertCharacteristic(usability);
    }

    createSubCharacteristic(characteristicsContext, "USABILITY_ACCESSIBILITY", "Accessibility", usabilityKey);
    createSubCharacteristic(characteristicsContext, "USABILITY_EASE_OF_USE", "Ease of Use", usabilityKey);
    createSubCharacteristic(characteristicsContext, USABILITY_KEY + COMPLIANCE_KEY_SUFFIX, "Usability " + COMPLIANCE_NAME, usabilityKey);
  }

  private void createSubCharacteristic(CharacteristicsContext characteristicsContext,
    String subCharacteristicKey, String subCharacteristicName, String parentKey) throws SQLException {
    Characteristic parent = characteristicsContext.findCharacteristicByKey(parentKey);
    if (parent != null) {
      Characteristic subCharacteristic = characteristicsContext.findSubCharacteristicByKey(subCharacteristicKey, parent);
      if (subCharacteristic == null) {
        characteristicsContext.insertCharacteristic(new Characteristic().setKey(subCharacteristicKey).setName(subCharacteristicName).setParentId(parent.getId()));
      }
    }
    // If the characteristic parent does not exits, the sub-characteristic is not added
  }

  private static class Characteristic {
    private Integer id;
    private String key;
    private String name;
    private Integer order;
    private Integer parentId;

    public Integer getId() {
      return id;
    }

    public Characteristic setId(Integer id) {
      this.id = id;
      return this;
    }

    public String getKey() {
      return key;
    }

    public Characteristic setKey(String key) {
      this.key = key;
      return this;
    }

    public String getName() {
      return name;
    }

    public Characteristic setName(String name) {
      this.name = name;
      return this;
    }

    /**
     * On a characteristic, the order can never be null
     */
    public Integer getOrder() {
      return parentId == null && order != null ? order : null;
    }

    public Characteristic setOrder(@Nullable Integer order) {
      this.order = order;
      return this;
    }

    @CheckForNull
    public Integer getParentId() {
      return parentId;
    }

    public Characteristic setParentId(@Nullable Integer parentId) {
      this.parentId = parentId;
      return this;
    }
  }

  private static class CharacteristicsContext {
    private final System2 system;
    Context context;
    Date now;
    List characteristics;

    public CharacteristicsContext(Context context, System2 system) throws SQLException {
      this.context = context;
      this.system = system;
      init();
    }

    private void init() throws SQLException {
      now = new Date(system.now());
      characteristics = selectEnabledCharacteristics();
    }

    public List characteristics() {
      return characteristics;
    }

    @CheckForNull
    public Characteristic findCharacteristicByKey(final String key) {
      Characteristic characteristic = Iterables.find(characteristics, new CharacteristicKey(key), null);
      if (characteristic != null && characteristic.getParentId() != null) {
        throw MessageException.of(String.format("'%s' must be a characteristic. " + ERROR_SUFFIX, characteristic.getName()));
      }
      return characteristic;
    }

    @CheckForNull
    public Characteristic findSubCharacteristicByKey(final String key, Characteristic parent) {
      Characteristic characteristic = Iterables.find(characteristics, new CharacteristicKey(key), null);
      if (characteristic != null) {
        Integer parentId = characteristic.getParentId();
        if (parentId == null) {
          throw MessageException.of(String.format("'%s' must be a sub-characteristic. " + ERROR_SUFFIX, characteristic.getName()));
        } else if (!parentId.equals(parent.getId())) {
          throw MessageException.of(String.format("'%s' must be defined under '%s'. " + ERROR_SUFFIX, characteristic.getName(), parent.getName()));
        }
      }
      return characteristic;
    }

    private List selectEnabledCharacteristics() throws SQLException {
      return context.prepareSelect(
        // Exclude requirements (to not fail when coming from a version older than 4.3)
        "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")
        .setBoolean(1, true)
        .list(new CharacteristicReader());
    }

    private int selectCharacteristicId(String key) throws SQLException {
      Long id = context.prepareSelect(
        "SELECT c.id FROM characteristics c WHERE c.kee = ? AND c.enabled=?")
        .setString(1, key)
        .setBoolean(2, true)
        .get(Select.LONG_READER);
      if (id != null) {
        return id.intValue();
      } else {
        throw new IllegalStateException(String.format("Characteristic '%s' could not be inserted", key));
      }
    }

    public void insertCharacteristic(Characteristic characteristic) throws SQLException {
      if (characteristic.getParentId() == null) {
        LOGGER.info("Insert new characteristic '{}'", characteristic.getKey());
      } else {
        LOGGER.info("Insert new sub characteristic '{}'", characteristic.getKey());
      }

      context.prepareUpsert("INSERT INTO characteristics (kee, name, parent_id, characteristic_order, enabled, created_at) VALUES (?, ?, ?, ?, ?, ?)")
        .setString(1, characteristic.getKey())
        .setString(2, characteristic.getName())
        .setInt(3, characteristic.getParentId())
        .setInt(4, characteristic.getOrder())
        .setBoolean(5, true)
        .setDate(6, now)
        .execute()
        .commit();
      characteristic.setId(selectCharacteristicId(characteristic.getKey()));

      characteristics.add(characteristic);
    }

    public void updateCharacteristicOrder(String key, Integer order) throws SQLException {
      LOGGER.info("Update characteristic '{}' order to {}", key, order);

      context.prepareUpsert("UPDATE characteristics SET characteristic_order=?, updated_at=? WHERE kee=?")
        .setInt(1, order)
        .setDate(2, now)
        .setString(3, key)
        .execute()
        .commit();
    }

    private static class CharacteristicReader implements Select.RowReader {
      @Override
      public Characteristic read(Select.Row row) throws SQLException {
        return new Characteristic()
          .setId(row.getInt(1))
          .setKey(row.getString(2))
          .setName(row.getString(3))
          .setOrder(row.getNullableInt(4))
          .setParentId(row.getNullableInt(5));
      }
    }
  }

  private static class CharacteristicKey implements Predicate {
    private final String key;

    public CharacteristicKey(String key) {
      this.key = key;
    }

    @Override
    public boolean apply(@Nullable Characteristic input) {
      return input != null && input.key.equals(key);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy