org.sonar.server.startup.CopyRequirementsFromCharacteristicsToRules.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.server.startup.CopyRequirementsFromCharacteristicsToRules.java

Source

/*
 * 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.lang.builder.EqualsBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.ServerComponent;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.utils.Duration;
import org.sonar.core.persistence.DbSession;
import org.sonar.core.persistence.MyBatis;
import org.sonar.core.rule.RuleDto;
import org.sonar.core.technicaldebt.db.CharacteristicMapper;
import org.sonar.core.technicaldebt.db.RequirementMigrationDto;
import org.sonar.core.template.LoadedTemplateDto;
import org.sonar.server.db.DbClient;
import org.sonar.server.rule.RegisterRules;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

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.
 * <p/>
 * This script need to be executed after rules registration because default debt columns (characteristics, function, coefficient and offset) has to be populated
 * in order to be able to compare default values with overridden values.
 * <p/>
 * WARNING : When updating this class, please take time to test on ALL databases!
 *
 * @since 4.3 this component could be removed after 4 or 5 releases.
 */
public class CopyRequirementsFromCharacteristicsToRules implements ServerComponent {

    private static final Logger LOGGER = LoggerFactory.getLogger(CopyRequirementsFromCharacteristicsToRules.class);

    private static final String TEMPLATE_KEY = "CopyRequirementsFromCharacteristicsToRules";

    private final DbClient dbClient;

    /**
     * @param registerRules used only to be started after init of rules
     */
    public CopyRequirementsFromCharacteristicsToRules(DbClient dbClient, RegisterRules registerRules) {
        this.dbClient = dbClient;
    }

    public void start() {
        doExecute();
    }

    private void doExecute() {
        if (dbClient.loadedTemplateDao().countByTypeAndKey(LoadedTemplateDto.ONE_SHOT_TASK_TYPE,
                TEMPLATE_KEY) == 0) {
            LOGGER.info("Copying requirement from characteristics to rules");
            copyRequirementsFromCharacteristicsToRules();

            LOGGER.info("Deleting requirements from characteristics");
            removeRequirementsDataFromCharacteristics();

            dbClient.loadedTemplateDao()
                    .insert(new LoadedTemplateDto(TEMPLATE_KEY, LoadedTemplateDto.ONE_SHOT_TASK_TYPE));
        }
    }

    private void copyRequirementsFromCharacteristicsToRules() {
        DbSession dbSession = dbClient.openSession(true);

        try {
            List<RequirementMigrationDto> requirementDtos = dbSession.getMapper(CharacteristicMapper.class)
                    .selectDeprecatedRequirements();
            if (requirementDtos.isEmpty()) {
                LOGGER.info("No requirement need to be copied", requirementDtos);

            } else {
                int requirementCopied = 0;

                final Multimap<Integer, RequirementMigrationDto> requirementsByRuleId = ArrayListMultimap.create();
                for (RequirementMigrationDto requirementDto : requirementDtos) {
                    requirementsByRuleId.put(requirementDto.getRuleId(), requirementDto);
                }

                List<RuleDto> rules = dbClient.ruleDao().findAll(dbSession);
                for (RuleDto rule : rules) {
                    Collection<RequirementMigrationDto> requirementsForRule = requirementsByRuleId
                            .get(rule.getId());
                    if (!requirementsForRule.isEmpty()) {
                        convert(rule, requirementsForRule, dbSession);
                        requirementCopied++;
                    }
                }
                dbSession.commit();

                LOGGER.info("{} requirements have been found, {} have been copied", requirementDtos.size(),
                        requirementCopied);
            }
        } finally {
            MyBatis.closeQuietly(dbSession);
        }
    }

    private void convert(RuleDto rule, Collection<RequirementMigrationDto> requirementsForRule, DbSession session) {
        RequirementMigrationDto enabledRequirement = enabledRequirement(requirementsForRule);

        if (enabledRequirement == null && RuleStatus.REMOVED != rule.getStatus()) {
            // If no enabled requirement is found, it means that the requirement has been disabled for this rule
            convertDisableRequirement(rule, session);

        } 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
            convertEnabledRequirement(rule, enabledRequirement, session);

            // When default values on debt are the same that ones set by SQALE, nothing to do
        }
    }

    private static RequirementMigrationDto enabledRequirement(
            Collection<RequirementMigrationDto> requirementsForRule) {
        return Iterables.find(requirementsForRule, new Predicate<RequirementMigrationDto>() {
            @Override
            public boolean apply(@Nullable RequirementMigrationDto input) {
                return input != null && input.isEnabled();
            }
        }, null);
    }

    private void convertDisableRequirement(RuleDto rule, DbSession session) {
        rule.setSubCharacteristicId(RuleDto.DISABLED_CHARACTERISTIC_ID);
        rule.setRemediationFunction(null);
        rule.setRemediationCoefficient(null);
        rule.setRemediationOffset(null);
        dbClient.ruleDao().update(session, rule);
    }

    private void convertEnabledRequirement(RuleDto ruleRow, RequirementMigrationDto enabledRequirement,
            DbSession session) {
        ruleRow.setSubCharacteristicId(
                enabledRequirement.getParentId() != null ? enabledRequirement.getParentId() : null);
        ruleRow.setRemediationFunction(enabledRequirement.getFunction().toUpperCase());
        ruleRow.setRemediationCoefficient(
                convertDuration(enabledRequirement.getCoefficientValue(), enabledRequirement.getCoefficientUnit()));
        ruleRow.setRemediationOffset(
                convertDuration(enabledRequirement.getOffsetValue(), enabledRequirement.getOffsetUnit()));

        // Constant/issue with coefficient is replaced by Constant/issue with offset (with no coefficient)
        if (DebtRemediationFunction.Type.CONSTANT_ISSUE.name().equals(ruleRow.getRemediationFunction())
                && ruleRow.getRemediationCoefficient() != null) {
            ruleRow.setRemediationOffset(ruleRow.getRemediationCoefficient());
            ruleRow.setRemediationCoefficient(null);
        }

        // If the coefficient of a linear or linear with offset function is null, it should be replaced by 0
        if ((DebtRemediationFunction.Type.LINEAR.name().equals(ruleRow.getRemediationFunction())
                || DebtRemediationFunction.Type.LINEAR_OFFSET.name().equals(ruleRow.getRemediationFunction()))
                && ruleRow.getRemediationCoefficient() == null) {
            ruleRow.setRemediationCoefficient("0" + convertUnit(enabledRequirement.getCoefficientUnit()));
            // If the offset of a constant per issue or linear with offset function is null, it should be replaced by 0
        } else if ((DebtRemediationFunction.Type.CONSTANT_ISSUE.name().equals(ruleRow.getRemediationFunction())
                || DebtRemediationFunction.Type.LINEAR_OFFSET.name().equals(ruleRow.getRemediationFunction()))
                && ruleRow.getRemediationOffset() == null) {
            ruleRow.setRemediationOffset("0" + convertUnit(enabledRequirement.getOffsetUnit()));
        }

        if (!isDebtDefaultValuesSameAsOverriddenValues(ruleRow)) {
            // Default values on debt are not the same that ones set by SQALE, update the rule
            dbClient.ruleDao().update(session, ruleRow);
        }
    }

    @CheckForNull
    @VisibleForTesting
    static String convertDuration(@Nullable Double oldValue, @Nullable String oldUnit) {
        if (oldValue != null && oldValue > 0) {
            // 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)) + convertUnit(oldUnit);
        }
        return null;
    }

    @VisibleForTesting
    private static String convertUnit(@Nullable String oldUnit) {
        String unit = oldUnit != null ? oldUnit : Duration.DAY;
        return "mn".equals(unit) ? Duration.MINUTE : unit;
    }

    @VisibleForTesting
    static boolean isDebtDefaultValuesSameAsOverriddenValues(RuleDto rule) {
        return new EqualsBuilder().append(rule.getDefaultSubCharacteristicId(), rule.getSubCharacteristicId())
                .append(rule.getDefaultRemediationFunction(), rule.getRemediationFunction())
                .append(rule.getDefaultRemediationCoefficient(), rule.getRemediationCoefficient())
                .append(rule.getDefaultRemediationOffset(), rule.getRemediationOffset()).isEquals();
    }

    private void removeRequirementsDataFromCharacteristics() {
        DbSession dbSession = dbClient.openSession(false);
        try {
            dbSession.getMapper(CharacteristicMapper.class).deleteRequirementsFromCharacteristicsTable();
            dbSession.commit();
        } finally {
            MyBatis.closeQuietly(dbSession);
        }
    }

}