/*
* 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.debt;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import org.apache.ibatis.session.SqlSession;
import org.sonar.api.ServerComponent;
import org.sonar.api.server.debt.DebtCharacteristic;
import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic;
import org.sonar.api.utils.System2;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.core.persistence.DbSession;
import org.sonar.core.persistence.MyBatis;
import org.sonar.core.rule.RuleDto;
import org.sonar.core.technicaldebt.db.CharacteristicDto;
import org.sonar.server.db.DbClient;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.user.UserSession;
import org.sonar.server.util.Validation;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import java.util.Date;
import java.util.List;
public class DebtModelOperations implements ServerComponent {
private final DbClient dbClient;
private final System2 system2;
public DebtModelOperations(DbClient dbClient) {
this(dbClient, System2.INSTANCE);
}
@VisibleForTesting
DebtModelOperations(DbClient dbClient, System2 system2) {
this.dbClient = dbClient;
this.system2 = system2;
}
public DebtCharacteristic create(String name, @Nullable Integer parentId) {
checkPermission();
SqlSession session = dbClient.openSession(false);
try {
checkNotAlreadyExists(name, session);
CharacteristicDto newCharacteristic = new CharacteristicDto()
.setKey(name.toUpperCase().replace(" ", "_"))
.setName(name)
.setEnabled(true)
.setCreatedAt(new Date(system2.now()));
// New sub characteristic
if (parentId != null) {
CharacteristicDto parent = findCharacteristic(parentId, session);
if (parent.getParentId() != null) {
throw new BadRequestException("A sub characteristic can not have a sub characteristic as parent.");
}
newCharacteristic.setParentId(parent.getId());
} else {
// New root characteristic
newCharacteristic.setOrder(dbClient.debtCharacteristicDao().selectMaxCharacteristicOrder(session) + 1);
}
dbClient.debtCharacteristicDao().insert(newCharacteristic, session);
session.commit();
return toCharacteristic(newCharacteristic);
} finally {
MyBatis.closeQuietly(session);
}
}
public DebtCharacteristic rename(int characteristicId, String newName) {
checkPermission();
SqlSession session = dbClient.openSession(false);
try {
checkNotAlreadyExists(newName, session);
CharacteristicDto dto = findCharacteristic(characteristicId, session);
if (!dto.getName().equals(newName)) {
dto.setName(newName);
dto.setUpdatedAt(new Date(system2.now()));
dbClient.debtCharacteristicDao().update(dto, session);
session.commit();
}
return toCharacteristic(dto);
} finally {
MyBatis.closeQuietly(session);
}
}
public DebtCharacteristic moveUp(int characteristicId) {
return move(characteristicId, true);
}
public DebtCharacteristic moveDown(int characteristicId) {
return move(characteristicId, false);
}
private DebtCharacteristic move(int characteristicId, boolean moveUpOrDown) {
checkPermission();
SqlSession session = dbClient.openSession(false);
try {
final CharacteristicDto dto = findCharacteristic(characteristicId, session);
if (dto.getParentId() != null) {
throw new BadRequestException("Sub characteristics can not be moved.");
}
int currentOrder = getOrder(dto);
CharacteristicDto dtoToSwitchOrderWith = findCharacteristicToSwitchWith(dto, moveUpOrDown, session);
// Do nothing when characteristic is already to the good location
if (dtoToSwitchOrderWith == null) {
return toCharacteristic(dto);
}
int nextOrder = getOrder(dtoToSwitchOrderWith);
dtoToSwitchOrderWith.setOrder(currentOrder);
dtoToSwitchOrderWith.setUpdatedAt(new Date(system2.now()));
dbClient.debtCharacteristicDao().update(dtoToSwitchOrderWith, session);
dto.setOrder(nextOrder);
dto.setUpdatedAt(new Date(system2.now()));
dbClient.debtCharacteristicDao().update(dto, session);
session.commit();
return toCharacteristic(dto);
} finally {
MyBatis.closeQuietly(session);
}
}
private int getOrder(CharacteristicDto characteristicDto) {
Integer order = characteristicDto.getOrder();
if (order == null) {
throw new IllegalArgumentException(String.format("The order of the characteristic '%s' should not be null", characteristicDto.getKey()));
}
return order;
}
@CheckForNull
private CharacteristicDto findCharacteristicToSwitchWith(final CharacteristicDto dto, final boolean moveUpOrDown, SqlSession session) {
// characteristics should be sort by 'order'
List<CharacteristicDto> rootCharacteristics = dbClient.debtCharacteristicDao().selectEnabledRootCharacteristics(session);
int currentPosition = Iterables.indexOf(rootCharacteristics, new Predicate<CharacteristicDto>() {
@Override
public boolean apply(@Nullable CharacteristicDto input) {
return input != null && input.getKey().equals(dto.getKey());
}
});
Integer nextPosition = moveUpOrDown ? (currentPosition > 0 ? currentPosition - 1 : null) : (currentPosition < rootCharacteristics.size() - 1 ? currentPosition + 1 : null);
return nextPosition != null ? Iterables.get(rootCharacteristics, nextPosition) : null;
}
/**
* Disable characteristic and its sub characteristics or only sub characteristic.
* Will also update every rules linked to sub characteristics by setting characteristic id to -1 and remove function, coefficient and offset.
*/
public void delete(int characteristicId) {
checkPermission();
Date updateDate = new Date(system2.now());
DbSession session = dbClient.openSession(true);
try {
delete(findCharacteristic(characteristicId, session), updateDate, session);
session.commit();
} finally {
MyBatis.closeQuietly(session);
}
}
/**
* Disabled a characteristic or a sub characteristic.
* If it has already been disabled, do nothing (for instance when call on a list of characteristics and sub-characteristics in random order)
*/
public void delete(CharacteristicDto characteristicOrSubCharacteristic, Date updateDate, DbSession session) {
// Do nothing is the characteristic is already disabled
if (characteristicOrSubCharacteristic.isEnabled()) {
// When root characteristic, browse sub characteristics and disable rule debt on each sub characteristic then disable it
if (characteristicOrSubCharacteristic.getParentId() == null) {
List<CharacteristicDto> subCharacteristics = dbClient.debtCharacteristicDao().selectCharacteristicsByParentId(characteristicOrSubCharacteristic.getId(), session);
for (CharacteristicDto subCharacteristic : subCharacteristics) {
disableSubCharacteristic(subCharacteristic, updateDate, session);
}
disableCharacteristic(characteristicOrSubCharacteristic, updateDate, session);
} else {
// When sub characteristic, disable rule debt on the sub characteristic then disable it
disableSubCharacteristic(characteristicOrSubCharacteristic, updateDate, session);
}
}
}
private void disableSubCharacteristic(CharacteristicDto subCharacteristic, Date updateDate, DbSession session) {
// Disable debt on all rules (even REMOVED ones, in order to have no issue if they are reactivated) linked to the sub characteristic
disableRulesDebt(dbClient.ruleDao().findRulesByDebtSubCharacteristicId(session, subCharacteristic.getId()), subCharacteristic.getId(), updateDate, session);
disableCharacteristic(subCharacteristic, updateDate, session);
}
private void disableCharacteristic(CharacteristicDto characteristic, Date updateDate, SqlSession session) {
characteristic.setEnabled(false);
characteristic.setUpdatedAt(updateDate);
dbClient.debtCharacteristicDao().update(characteristic, session);
}
private void disableRulesDebt(List<RuleDto> ruleDtos, Integer subCharacteristicId, Date updateDate, DbSession session) {
for (RuleDto ruleDto : ruleDtos) {
if (subCharacteristicId.equals(ruleDto.getSubCharacteristicId())) {
ruleDto.setSubCharacteristicId(RuleDto.DISABLED_CHARACTERISTIC_ID);
ruleDto.setRemediationFunction(null);
ruleDto.setRemediationCoefficient(null);
ruleDto.setRemediationOffset(null);
ruleDto.setUpdatedAt(updateDate);
}
if (subCharacteristicId.equals(ruleDto.getDefaultSubCharacteristicId())) {
ruleDto.setDefaultSubCharacteristicId(null);
ruleDto.setDefaultRemediationFunction(null);
ruleDto.setDefaultRemediationCoefficient(null);
ruleDto.setDefaultRemediationOffset(null);
}
dbClient.ruleDao().update(session, ruleDto);
}
}
private CharacteristicDto findCharacteristic(Integer id, SqlSession session) {
CharacteristicDto dto = dbClient.debtCharacteristicDao().selectById(id, session);
if (dto == null) {
throw new NotFoundException(String.format("Characteristic with id %s does not exists.", id));
}
return dto;
}
private void checkNotAlreadyExists(String name, SqlSession session) {
if (dbClient.debtCharacteristicDao().selectByName(name, session) != null) {
throw new BadRequestException(Validation.IS_ALREADY_USED_MESSAGE, name);
}
}
private void checkPermission() {
UserSession.get().checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN);
}
private static DebtCharacteristic toCharacteristic(CharacteristicDto dto) {
return new DefaultDebtCharacteristic()
.setId(dto.getId())
.setKey(dto.getKey())
.setName(dto.getName())
.setOrder(dto.getOrder())
.setParentId(dto.getParentId())
.setCreatedAt(dto.getCreatedAt())
.setUpdatedAt(dto.getUpdatedAt());
}
}