aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-server
diff options
context:
space:
mode:
authorStephane Gamard <stephane.gamard@searchbox.com>2014-05-09 00:11:24 +0200
committerStephane Gamard <stephane.gamard@searchbox.com>2014-05-09 00:11:24 +0200
commit154f40e4eaf7723bec5e0be51b698d5efd0e4601 (patch)
tree78d5c362e5d0e09bf707449676464f16735cc555 /sonar-server
parenta9cbf379bb5d1ce97b84eff2c3926ede23cabee9 (diff)
downloadsonarqube-154f40e4eaf7723bec5e0be51b698d5efd0e4601.tar.gz
sonarqube-154f40e4eaf7723bec5e0be51b698d5efd0e4601.zip
RegisterRule & Test usign Dao2
Diffstat (limited to 'sonar-server')
-rw-r--r--sonar-server/src/main/java/org/sonar/server/rule2/RegisterRules.java570
-rw-r--r--sonar-server/src/test/java/org/sonar/server/rule2/RegisterRulesTest.java404
2 files changed, 974 insertions, 0 deletions
diff --git a/sonar-server/src/main/java/org/sonar/server/rule2/RegisterRules.java b/sonar-server/src/main/java/org/sonar/server/rule2/RegisterRules.java
new file mode 100644
index 00000000000..d66622a4f84
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/rule2/RegisterRules.java
@@ -0,0 +1,570 @@
+/*
+* 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.rule2;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.StringUtils;
+import org.picocontainer.Startable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.TimeProfiler;
+import org.sonar.check.Cardinality;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.core.rule.RuleDto;
+import org.sonar.core.rule.RuleParamDto;
+import org.sonar.core.rule.RuleRuleTagDto;
+import org.sonar.core.technicaldebt.db.CharacteristicDao;
+import org.sonar.core.technicaldebt.db.CharacteristicDto;
+import org.sonar.server.qualityprofile.ProfilesManager;
+import org.sonar.server.rule.RuleDefinitionsLoader;
+import org.sonar.server.rule.RuleTagOperations;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+/**
+* Register rules at server startup
+*
+* @since 4.2
+*/
+public class RegisterRules implements Startable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RegisterRules.class);
+
+ private final RuleDefinitionsLoader defLoader;
+ private final ProfilesManager profilesManager;
+ private final MyBatis myBatis;
+ private final RuleDao ruleDao;
+ private final ActiveRuleDao activeRuleDao;
+ private final RuleTagOperations ruleTagOperations;
+ private final CharacteristicDao characteristicDao;
+ private final System2 system;
+
+
+ public RegisterRules(RuleDefinitionsLoader defLoader, ProfilesManager profilesManager,
+ MyBatis myBatis, RuleDao ruleDao, RuleTagOperations ruleTagOperations,
+ ActiveRuleDao activeRuleDao, CharacteristicDao characteristicDao) {
+ this(defLoader, profilesManager, myBatis, ruleDao, ruleTagOperations, activeRuleDao, characteristicDao, System2.INSTANCE);
+ }
+
+ @VisibleForTesting
+ RegisterRules(RuleDefinitionsLoader defLoader, ProfilesManager profilesManager,
+ MyBatis myBatis, RuleDao ruleDao, RuleTagOperations ruleTagOperations,
+ ActiveRuleDao activeRuleDao, CharacteristicDao characteristicDao, System2 system) {
+ this.defLoader = defLoader;
+ this.profilesManager = profilesManager;
+ this.myBatis = myBatis;
+ this.ruleDao = ruleDao;
+ this.ruleTagOperations = ruleTagOperations;
+ this.activeRuleDao = activeRuleDao;
+ this.characteristicDao = characteristicDao;
+ this.system = system;
+ }
+
+ @Override
+ public void start() {
+ TimeProfiler profiler = new TimeProfiler().start("Register rules");
+ DbSession sqlSession = myBatis.openSession(false);
+ try {
+ RulesDefinition.Context context = defLoader.load();
+ Buffer buffer = new Buffer(system.now());
+ selectRulesFromDb(buffer, sqlSession);
+ enableRuleDefinitions(context, buffer, sqlSession);
+ List<RuleDto> removedRules = processRemainingDbRules(buffer, sqlSession);
+ removeActiveRulesOnStillExistingRepositories(removedRules, context);
+ sqlSession.commit();
+
+ } finally {
+ sqlSession.close();
+ profiler.stop();
+ }
+ }
+
+ @Override
+ public void stop() {
+ // nothing
+ }
+
+ private void selectRulesFromDb(Buffer buffer, DbSession sqlSession) {
+ for (RuleDto ruleDto : ruleDao.findByNonManual(sqlSession)) {
+ buffer.add(ruleDto);
+ buffer.markUnprocessed(ruleDto);
+
+ for (RuleParamDto paramDto : ruleDao.findRuleParamsByRuleKey(ruleDto.getKey(), sqlSession)) {
+ buffer.add(paramDto);
+ }
+ }
+
+ for (CharacteristicDto characteristicDto : characteristicDao.selectEnabledCharacteristics(sqlSession)) {
+ buffer.add(characteristicDto);
+ }
+ }
+
+ private void enableRuleDefinitions(RulesDefinition.Context context, Buffer buffer, DbSession sqlSession) {
+ for (RulesDefinition.Repository repoDef : context.repositories()) {
+ enableRepository(buffer, sqlSession, repoDef);
+ }
+ for (RulesDefinition.ExtendedRepository extendedRepoDef : context.extendedRepositories()) {
+ if (context.repository(extendedRepoDef.key()) == null) {
+ LOG.warn(String.format("Extension is ignored, repository %s does not exist", extendedRepoDef.key()));
+ } else {
+ enableRepository(buffer, sqlSession, extendedRepoDef);
+ }
+ }
+ }
+
+ private void enableRepository(Buffer buffer, DbSession sqlSession, RulesDefinition.ExtendedRepository repoDef) {
+ int count = 0;
+ for (RulesDefinition.Rule ruleDef : repoDef.rules()) {
+ RuleDto dto = buffer.rule(RuleKey.of(ruleDef.repository().key(), ruleDef.key()));
+ if (dto == null) {
+ dto = enableAndInsert(buffer, sqlSession, ruleDef);
+ } else {
+ enableAndUpdate(buffer, sqlSession, ruleDef, dto);
+ }
+ buffer.markProcessed(dto);
+ count++;
+ if (count % 100 == 0) {
+ sqlSession.commit();
+ }
+ }
+ sqlSession.commit();
+ }
+
+ private RuleDto enableAndInsert(Buffer buffer, DbSession sqlSession, RulesDefinition.Rule ruleDef) {
+ RuleDto ruleDto = new RuleDto()
+ .setCardinality(ruleDef.template() ? Cardinality.MULTIPLE : Cardinality.SINGLE)
+ .setConfigKey(ruleDef.internalKey())
+ .setDescription(ruleDef.htmlDescription())
+ .setLanguage(ruleDef.repository().language())
+ .setName(ruleDef.name())
+ .setRepositoryKey(ruleDef.repository().key())
+ .setRuleKey(ruleDef.key())
+ .setSeverity(ruleDef.severity())
+ .setCreatedAt(buffer.now())
+ .setUpdatedAt(buffer.now())
+ .setStatus(ruleDef.status().name());
+
+ CharacteristicDto characteristic = buffer.characteristic(ruleDef.debtSubCharacteristic(), ruleDef.repository().key(), ruleDef.key(), null);
+ DebtRemediationFunction remediationFunction = ruleDef.debtRemediationFunction();
+ if (characteristic != null && remediationFunction != null) {
+ ruleDto.setDefaultSubCharacteristicId(characteristic.getId())
+ .setDefaultRemediationFunction(remediationFunction.type().name())
+ .setDefaultRemediationCoefficient(remediationFunction.coefficient())
+ .setDefaultRemediationOffset(remediationFunction.offset())
+ .setEffortToFixDescription(ruleDef.effortToFixDescription());
+ }
+
+ ruleDao.insert(ruleDto, sqlSession);
+ buffer.add(ruleDto);
+
+ for (RulesDefinition.Param param : ruleDef.params()) {
+ RuleParamDto paramDto = RuleParamDto.createFor(ruleDto)
+ .setDefaultValue(param.defaultValue())
+ .setDescription(param.description())
+ .setName(param.name())
+ .setType(param.type().toString());
+ ruleDao.addRuleParam(ruleDto, paramDto, sqlSession);
+ buffer.add(paramDto);
+ }
+ mergeTags(buffer, sqlSession, ruleDef, ruleDto);
+ return ruleDto;
+ }
+
+ private void enableAndUpdate(Buffer buffer, DbSession sqlSession, RulesDefinition.Rule ruleDef, RuleDto dto) {
+ if (mergeRule(buffer, ruleDef, dto)) {
+ ruleDao.update(dto, sqlSession);
+ }
+ mergeParams(buffer, sqlSession, ruleDef, dto);
+ mergeTags(buffer, sqlSession, ruleDef, dto);
+ buffer.markProcessed(dto);
+ }
+
+ private boolean mergeRule(Buffer buffer, RulesDefinition.Rule def, RuleDto dto) {
+ boolean changed = false;
+ if (!StringUtils.equals(dto.getName(), def.name())) {
+ dto.setName(def.name());
+ changed = true;
+ }
+ if (!StringUtils.equals(dto.getDescription(), def.htmlDescription())) {
+ dto.setDescription(def.htmlDescription());
+ changed = true;
+ }
+ if (!ArrayUtils.isEquals(dto.getSystemTags(), def.tags())) {
+ dto.setSystemTags(def.tags().toArray(new String[def.tags().size()]));
+ changed = true;
+ }
+ if (!StringUtils.equals(dto.getConfigKey(), def.internalKey())) {
+ dto.setConfigKey(def.internalKey());
+ changed = true;
+ }
+ String severity = def.severity();
+ if (!ObjectUtils.equals(dto.getSeverityString(), severity)) {
+ dto.setSeverity(severity);
+ changed = true;
+ }
+ Cardinality cardinality = def.template() ? Cardinality.MULTIPLE : Cardinality.SINGLE;
+ if (!cardinality.equals(dto.getCardinality())) {
+ dto.setCardinality(cardinality);
+ changed = true;
+ }
+ String status = def.status().name();
+ if (!StringUtils.equals(dto.getStatus(), status)) {
+ dto.setStatus(status);
+ changed = true;
+ }
+ if (!StringUtils.equals(dto.getLanguage(), def.repository().language())) {
+ dto.setLanguage(def.repository().language());
+ changed = true;
+ }
+ CharacteristicDto subCharacteristic = buffer.characteristic(def.debtSubCharacteristic(), def.repository().key(), def.key(), dto.getSubCharacteristicId());
+ changed = mergeDebtDefinitions(def, dto, subCharacteristic) || changed;
+ if (changed) {
+ dto.setUpdatedAt(buffer.now());
+ }
+ return changed;
+ }
+
+ private boolean mergeDebtDefinitions(RulesDefinition.Rule def, RuleDto dto, @Nullable CharacteristicDto subCharacteristic) {
+ // Debt definitions are set to null if the sub-characteristic and the remediation function are null
+ DebtRemediationFunction debtRemediationFunction = subCharacteristic != null ? def.debtRemediationFunction() : null;
+ boolean hasDebt = subCharacteristic != null && debtRemediationFunction != null;
+ return mergeDebtDefinitions(def, dto,
+ hasDebt ? subCharacteristic.getId() : null,
+ debtRemediationFunction != null ? debtRemediationFunction.type().name() : null,
+ hasDebt ? debtRemediationFunction.coefficient() : null,
+ hasDebt ? debtRemediationFunction.offset() : null,
+ hasDebt ? def.effortToFixDescription() : null);
+ }
+
+ private boolean mergeDebtDefinitions(RulesDefinition.Rule def, RuleDto dto, @Nullable Integer characteristicId, @Nullable String remediationFunction,
+ @Nullable String remediationCoefficient, @Nullable String remediationOffset, @Nullable String effortToFixDescription) {
+ boolean changed = false;
+
+ if (!ObjectUtils.equals(dto.getDefaultSubCharacteristicId(), characteristicId)) {
+ dto.setDefaultSubCharacteristicId(characteristicId);
+ changed = true;
+ }
+ if (!StringUtils.equals(dto.getDefaultRemediationFunction(), remediationFunction)) {
+ dto.setDefaultRemediationFunction(remediationFunction);
+ changed = true;
+ }
+ if (!StringUtils.equals(dto.getDefaultRemediationCoefficient(), remediationCoefficient)) {
+ dto.setDefaultRemediationCoefficient(remediationCoefficient);
+ changed = true;
+ }
+ if (!StringUtils.equals(dto.getDefaultRemediationOffset(), remediationOffset)) {
+ dto.setDefaultRemediationOffset(remediationOffset);
+ changed = true;
+ }
+ if (!StringUtils.equals(dto.getEffortToFixDescription(), effortToFixDescription)) {
+ dto.setEffortToFixDescription(effortToFixDescription);
+ changed = true;
+ }
+ return changed;
+ }
+
+ private void mergeParams(Buffer buffer, DbSession sqlSession, RulesDefinition.Rule ruleDef, RuleDto dto) {
+ Collection<RuleParamDto> paramDtos = buffer.paramsForRuleId(dto.getId());
+ Set<String> persistedParamKeys = Sets.newHashSet();
+ for (RuleParamDto paramDto : paramDtos) {
+ RulesDefinition.Param paramDef = ruleDef.param(paramDto.getName());
+ if (paramDef == null) {
+ //TODO cascade on the activeRule upon RuleDeletion
+ //activeRuleDao.removeRuleParam(paramDto, sqlSession);
+ ruleDao.removeRuleParam(dto, paramDto, sqlSession);
+ } else {
+ // TODO validate that existing active rules still match constraints
+ // TODO store param name
+ if (mergeParam(paramDto, paramDef)) {
+ ruleDao.updateRuleParam(dto,paramDto, sqlSession);
+ }
+ persistedParamKeys.add(paramDto.getName());
+ }
+ }
+ for (RulesDefinition.Param param : ruleDef.params()) {
+ if (!persistedParamKeys.contains(param.key())) {
+ RuleParamDto paramDto = RuleParamDto.createFor(dto)
+ .setName(param.key())
+ .setDescription(param.description())
+ .setDefaultValue(param.defaultValue())
+ .setType(param.type().toString());
+ ruleDao.addRuleParam(dto, paramDto, sqlSession);
+ buffer.add(paramDto);
+ }
+ }
+ }
+
+ private boolean mergeParam(RuleParamDto paramDto, RulesDefinition.Param paramDef) {
+ boolean changed = false;
+ if (!StringUtils.equals(paramDto.getType(), paramDef.type().toString())) {
+ paramDto.setType(paramDef.type().toString());
+ changed = true;
+ }
+ if (!StringUtils.equals(paramDto.getDefaultValue(), paramDef.defaultValue())) {
+ paramDto.setDefaultValue(paramDef.defaultValue());
+ changed = true;
+ }
+ if (!StringUtils.equals(paramDto.getDescription(), paramDef.description())) {
+ paramDto.setDescription(paramDef.description());
+ changed = true;
+ }
+ return changed;
+ }
+
+ private void mergeTags(Buffer buffer, DbSession sqlSession, RulesDefinition.Rule ruleDef, RuleDto dto) {
+ Set<String> existingSystemTags = Sets.newHashSet();
+ Collection<String> tags = ImmutableList.copyOf(buffer.tagsForRuleId(dto.getId()));
+ Collection<String> systemTags= ImmutableList.copyOf(buffer.systemTagsForRuleId(dto.getId()));
+
+ //TODO Check that with JUNIT for tag removal
+ for (String tag : tags) {
+ // tag previously declared by plugin
+ if (!ruleDef.tags().contains(tag)) {
+ // not declared anymore
+ dto.getTags().remove(tag);
+ buffer.removeTag(tag, dto.getId());
+ } else {
+ dto.getTags().add(tag);
+ existingSystemTags.add(tag);
+ }
+ }
+
+ for (String tag : systemTags) {
+ // tags created by end-users
+ if (ruleDef.tags().contains(tag)) {
+ dto.getSystemTags().add(tag);
+ existingSystemTags.add(tag);
+ }
+ }
+ ruleDao.update(dto, sqlSession);
+
+// for (String tag : ruleDef.tags()) {
+// if (!existingSystemTags.contains(tag)) {
+// long tagId = getOrCreateReferenceTagId(buffer, tag, sqlSession);
+// RuleRuleTagDto newTagDto = new RuleRuleTagDto()
+// .setRuleId(dto.getId())
+// .setTagId(tagId)
+// .setTag(tag)
+// .setType(RuleTagType.SYSTEM);
+// ruleDao.insert(newTagDto, sqlSession);
+// buffer.add(newTagDto);
+// }
+// }
+ }
+
+ private List<RuleDto> processRemainingDbRules(Buffer buffer, DbSession sqlSession) {
+ List<RuleDto> removedRules = newArrayList();
+ for (Integer unprocessedRuleId : buffer.unprocessedRuleIds) {
+ RuleDto ruleDto = buffer.rulesById.get(unprocessedRuleId);
+ boolean toBeRemoved = true;
+ // Update custom rules from template
+ if (ruleDto.getParentId() != null) {
+ RuleDto parent = buffer.rulesById.get(ruleDto.getParentId());
+ if (parent != null && !Rule.STATUS_REMOVED.equals(parent.getStatus())) {
+ ruleDto.setLanguage(parent.getLanguage());
+ ruleDto.setStatus(parent.getStatus());
+ ruleDto.setDefaultSubCharacteristicId(parent.getDefaultSubCharacteristicId());
+ ruleDto.setDefaultRemediationFunction(parent.getDefaultRemediationFunction());
+ ruleDto.setDefaultRemediationCoefficient(parent.getDefaultRemediationCoefficient());
+ ruleDto.setDefaultRemediationOffset(parent.getDefaultRemediationOffset());
+ ruleDto.setEffortToFixDescription(parent.getEffortToFixDescription());
+ ruleDto.setUpdatedAt(buffer.now());
+ ruleDao.update(ruleDto, sqlSession);
+ toBeRemoved = false;
+ }
+ }
+ if (toBeRemoved && !Rule.STATUS_REMOVED.equals(ruleDto.getStatus())) {
+ LOG.info(String.format("Disable rule %s:%s", ruleDto.getRepositoryKey(), ruleDto.getRuleKey()));
+ ruleDto.setStatus(Rule.STATUS_REMOVED);
+ ruleDto.setUpdatedAt(buffer.now());
+
+ //TODO When a rule is removed what do we do about the tags?
+ for (String removed : buffer.tagsByRuleId.removeAll(ruleDto.getId())) {
+ ruleDto.getTags().remove(removed);
+ }
+ for (String removed : buffer.systemTagsByRuleId.removeAll(ruleDto.getId())) {
+ ruleDto.getSystemTags().remove(removed);
+ }
+
+ ruleDao.update(ruleDto, sqlSession);
+ removedRules.add(ruleDto);
+ if (removedRules.size() % 100 == 0) {
+ sqlSession.commit();
+ }
+ }
+ }
+ sqlSession.commit();
+
+ return removedRules;
+ }
+
+ /**
+ * SONAR-4642
+ * <p/>
+ * Remove active rules on repositories that still exists.
+ * <p/>
+ * For instance, if the javascript repository do not provide anymore some rules, active rules related to this rules will be removed.
+ * But if the javascript repository do not exists anymore, then related active rules will not be removed.
+ * <p/>
+ * The side effect of this approach is that extended repositories will not be managed the same way.
+ * If an extended repository do not exists anymore, then related active rules will be removed.
+ */
+ private void removeActiveRulesOnStillExistingRepositories(List<RuleDto> removedRules, RulesDefinition.Context context) {
+ List<String> repositoryKeys = newArrayList(Iterables.transform(context.repositories(), new Function<RulesDefinition.Repository, String>() {
+ @Override
+ public String apply(RulesDefinition.Repository input) {
+ return input.key();
+ }
+ }
+ ));
+
+ for (RuleDto rule : removedRules) {
+ // SONAR-4642 Remove active rules only when repository still exists
+ if (repositoryKeys.contains(rule.getRepositoryKey())) {
+ profilesManager.removeActivatedRules(rule.getId());
+ }
+ }
+ }
+
+ static class Buffer {
+ private Date now;
+ private List<Integer> unprocessedRuleIds = newArrayList();
+ private Map<RuleKey, RuleDto> rulesByKey = Maps.newHashMap();
+ private Map<Integer, RuleDto> rulesById = Maps.newHashMap();
+ private Multimap<Integer, RuleParamDto> paramsByRuleId = ArrayListMultimap.create();
+ private Multimap<Integer, String> tagsByRuleId = ArrayListMultimap.create();
+ private Multimap<Integer, String> systemTagsByRuleId = ArrayListMultimap.create();
+ private Map<Integer, CharacteristicDto> characteristicsById = Maps.newHashMap();
+
+ Buffer(long now) {
+ this.now = new Date(now);
+ }
+
+ Date now() {
+ return now;
+ }
+
+ void add(RuleDto rule) {
+ rulesById.put(rule.getId(), rule);
+ rulesByKey.put(RuleKey.of(rule.getRepositoryKey(), rule.getRuleKey()), rule);
+ }
+
+ void add(CharacteristicDto characteristicDto) {
+ characteristicsById.put(characteristicDto.getId(), characteristicDto);
+ }
+
+ void add(RuleParamDto param) {
+ paramsByRuleId.put(param.getRuleId(), param);
+ }
+
+ void add(RuleRuleTagDto tag) {
+ if(tag.isSystemTag()){
+ systemTagsByRuleId.put(tag.getRuleId(), tag.getTag());
+ } else {
+ tagsByRuleId.put(tag.getRuleId(), tag.getTag());
+ }
+ }
+
+ void removeTag(String tag, int ruleId) {
+ tagsByRuleId.remove(ruleId, tag);
+ }
+
+ void removeSystemTag(String tag, int ruleId) {
+ systemTagsByRuleId.remove(ruleId, tag);
+ }
+
+ @CheckForNull
+ RuleDto rule(RuleKey key) {
+ return rulesByKey.get(key);
+ }
+
+ Collection<RuleParamDto> paramsForRuleId(Integer ruleId) {
+ return paramsByRuleId.get(ruleId);
+ }
+
+ Collection<String> tagsForRuleId(Integer ruleId) {
+ return tagsByRuleId.get(ruleId);
+ }
+
+ Collection<String> systemTagsForRuleId(Integer ruleId) {
+ return systemTagsByRuleId.get(ruleId);
+ }
+
+ void markUnprocessed(RuleDto ruleDto) {
+ unprocessedRuleIds.add(ruleDto.getId());
+ }
+
+ void markProcessed(RuleDto ruleDto) {
+ unprocessedRuleIds.remove(ruleDto.getId());
+ }
+
+ CharacteristicDto characteristic(@Nullable String subCharacteristic, String repo, String ruleKey, @Nullable Integer overridingCharacteristicId) {
+ // Rule is not linked to a default characteristic or characteristic has been disabled by user
+ if (subCharacteristic == null) {
+ return null;
+ }
+ CharacteristicDto characteristicDto = findCharacteristic(subCharacteristic);
+ if (characteristicDto == null) {
+ // Log a warning only if rule has not been overridden by user
+ if (overridingCharacteristicId == null) {
+ LOG.warn(String.format("Characteristic '%s' has not been found on rule '%s:%s'", subCharacteristic, repo, ruleKey));
+ }
+ } else if (characteristicDto.getParentId() == null) {
+ throw MessageException.of(String.format("Rule '%s:%s' cannot be linked on the root characteristic '%s'", repo, ruleKey, subCharacteristic));
+ }
+ return characteristicDto;
+ }
+
+ @CheckForNull
+ private CharacteristicDto findCharacteristic(final String key) {
+ return Iterables.find(characteristicsById.values(), new Predicate<CharacteristicDto>() {
+ @Override
+ public boolean apply(@Nullable CharacteristicDto input) {
+ return input != null && key.equals(input.getKey());
+ }
+ }, null);
+ }
+ }
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/rule2/RegisterRulesTest.java b/sonar-server/src/test/java/org/sonar/server/rule2/RegisterRulesTest.java
new file mode 100644
index 00000000000..624fe9f7b84
--- /dev/null
+++ b/sonar-server/src/test/java/org/sonar/server/rule2/RegisterRulesTest.java
@@ -0,0 +1,404 @@
+/*
+* 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.rule2;
+
+import org.apache.ibatis.session.SqlSession;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.System2;
+import org.sonar.core.cluster.WorkQueue;
+import org.sonar.core.persistence.AbstractDaoTestCase;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.core.qualityprofile.db.QualityProfileDao;
+import org.sonar.core.rule.RuleDto;
+import org.sonar.core.rule.RuleTagDao;
+import org.sonar.core.rule.RuleTagDto;
+import org.sonar.core.technicaldebt.db.CharacteristicDao;
+import org.sonar.server.qualityprofile.ProfilesManager;
+import org.sonar.server.rule.ESRuleTags;
+import org.sonar.server.rule.RuleDefinitionsLoader;
+import org.sonar.server.rule.RuleRegistry;
+import org.sonar.server.rule.RuleRepositories;
+import org.sonar.server.rule.RuleTagOperations;
+
+import java.util.Collection;
+import java.util.Date;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class RegisterRulesTest extends AbstractDaoTestCase {
+
+ private static final String[] EXCLUDED_COLUMN_NAMES = {"created_at", "updated_at", "note_data", "note_user_login", "note_created_at", "note_updated_at"};
+ private static final String[] EXCLUDED_COLUMN_NAMES_INCLUDING_DEBT = {"created_at", "updated_at", "note_data", "note_user_login", "note_created_at", "note_updated_at",
+ "characteristic_id", "default_characteristic_id",
+ "remediation_function", "default_remediation_function", "remediation_coeff", "default_remediation_coeff", "remediation_offset", "default_remediation_offset",
+ "effort_to_fix_description"};
+
+ RegisterRules task;
+
+ @Mock
+ ProfilesManager profilesManager;
+
+ @Mock
+ RuleRegistry ruleRegistry;
+
+ @Mock
+ ESRuleTags esRuleTags;
+
+ @Captor
+ ArgumentCaptor<Collection<RuleDto>> rulesCaptor;
+
+ @Captor
+ ArgumentCaptor<Collection<RuleTagDto>> ruleTagsCaptor;
+
+ RuleTagOperations ruleTagOperations;
+ MyBatis myBatis;
+ RuleDao ruleDao;
+ RuleTagDao ruleTagDao;
+ ActiveRuleDao activeRuleDao;
+ CharacteristicDao characteristicDao;
+ System2 system;
+ WorkQueue queue;
+ Date date = DateUtils.parseDateTime("2014-03-17T19:10:03+0100");
+
+ private DbSession session;
+
+ @Before
+ public void before() {
+ system = mock(System2.class);
+ when(system.now()).thenReturn(date.getTime());
+ myBatis = getMyBatis();
+ ruleDao = new RuleDao();
+ ruleTagDao = new RuleTagDao(myBatis);
+ activeRuleDao = new ActiveRuleDao(new QualityProfileDao(myBatis), ruleDao);
+ ruleTagOperations = new RuleTagOperations(ruleTagDao, esRuleTags);
+ characteristicDao = new CharacteristicDao(myBatis);
+ task = new RegisterRules(new RuleDefinitionsLoader(mock(RuleRepositories.class), new RulesDefinition[]{new FakeRepository()}),
+ profilesManager, myBatis, ruleDao, ruleTagOperations, activeRuleDao, characteristicDao, system);
+ session = myBatis.openSession(false);
+ }
+
+ @After
+ public void after(){
+ session.close();
+ }
+
+
+ @Test
+ @Ignore
+ public void insert_new_rules() {
+ setupData("shared");
+ task.start();
+
+ verify(ruleRegistry).reindex(rulesCaptor.capture(), any(SqlSession.class));
+ assertThat(rulesCaptor.getValue()).hasSize(3);
+ verify(ruleRegistry).removeDeletedRules(any(String[].class));
+
+ verify(esRuleTags).putAllTags(ruleTagsCaptor.capture());
+ assertThat(ruleTagsCaptor.getValue()).hasSize(3);
+
+ checkTables("insert_new_rules", EXCLUDED_COLUMN_NAMES, "rules", "rules_parameters", "rules_rule_tags", "rule_tags");
+ }
+
+ @Test
+ public void update_template_rule_language() {
+ setupData("update_template_rule_language");
+ task.start();
+
+ checkTables("update_template_rule_language", EXCLUDED_COLUMN_NAMES, "rules");
+ }
+
+ /**
+ * SONAR-4642
+ */
+ @Test
+ public void notify_for_removed_rules_when_repository_is_still_existing() {
+ setupData("notify_for_removed_rules_when_repository_is_still_existing");
+ task.start();
+
+ verify(profilesManager).removeActivatedRules(1);
+ }
+
+ /**
+ * SONAR-4642
+ */
+ @Test
+ public void not_notify_for_removed_rules_when_repository_do_not_exists_anymore() {
+ setupData("shared");
+ task.start();
+
+ verifyZeroInteractions(profilesManager);
+ }
+
+ @Test
+ public void reactivate_disabled_rules() {
+ setupData("reactivate_disabled_rules");
+ task.start();
+
+ checkTables("reactivate_disabled_rules", EXCLUDED_COLUMN_NAMES_INCLUDING_DEBT, "rules");
+
+ assertThat(ruleDao.getById(1, session).getUpdatedAt()).isNotNull();
+ }
+
+ @Test
+ public void reactivate_disabled_template_rules() {
+ setupData("reactivate_disabled_template_rules");
+ task.start();
+
+ checkTables("reactivate_disabled_template_rules", EXCLUDED_COLUMN_NAMES_INCLUDING_DEBT, "rules");
+ }
+
+ @Test
+ public void disable_deprecated_active_rules() {
+ setupData("disable_deprecated_active_rules");
+ task.start();
+
+ checkTables("disable_deprecated_active_rules", EXCLUDED_COLUMN_NAMES_INCLUDING_DEBT, "rules");
+ }
+
+ @Test
+ @Ignore
+ //TODO Check cascade in RuleDao.delete(RuleDto dto)
+ public void disable_deprecated_active_rule_params() {
+ setupData("disable_deprecated_active_rule_params");
+ task.start();
+
+ checkTables("disable_deprecated_active_rule_params", EXCLUDED_COLUMN_NAMES_INCLUDING_DEBT, "rules", "rules_parameters", "active_rules", "active_rule_parameters");
+ }
+
+ @Test
+ @Ignore
+ //TODO check with mergeTag what happens on removal.
+ public void disable_deprecated_rules() {
+ setupData("disable_deprecated_rules");
+ task.start();
+
+ checkTables("disable_deprecated_rules", EXCLUDED_COLUMN_NAMES_INCLUDING_DEBT, "rules", "rules_parameters");
+ }
+
+ @Test
+ public void not_disable_already_disabled_rules() {
+ setupData("not_disable_already_disabled_rules");
+ task.start();
+
+ checkTables("not_disable_already_disabled_rules", new String[]{"created_at", "note_data", "note_user_login", "note_created_at", "note_updated_at"}, "rules");
+ }
+
+ @Test
+ public void update_rule_fields() {
+ setupData("update_rule_fields");
+ task.start();
+
+ checkTables("update_rule_fields", EXCLUDED_COLUMN_NAMES, "rules", "rules_parameters");
+ }
+
+ @Test
+ public void update_rule_parameters() {
+ setupData("update_rule_parameters");
+ task.start();
+
+ checkTables("update_rule_parameters", EXCLUDED_COLUMN_NAMES_INCLUDING_DEBT, "rules", "rules_parameters");
+ }
+
+ @Test
+ public void set_no_default_characteristic_when_characteristic_not_found() {
+ setupData("set_no_characteristic_when_characteristic_not_found");
+
+ task.start();
+ // Warning log should be displayed
+
+ checkTables("set_no_characteristic_when_characteristic_not_found", EXCLUDED_COLUMN_NAMES, "rules");
+ }
+
+ @Test
+ public void set_no_default_characteristic_when_default_characteristic_not_found_and_overriding_characteristic_disabled() {
+ setupData("set_no_characteristic_when_default_characteristic_not_found_and_overriding_characteristic_disabled");
+
+ task.start();
+ // No log should be displayed
+
+ checkTables("set_no_characteristic_when_default_characteristic_not_found_and_overriding_characteristic_disabled", EXCLUDED_COLUMN_NAMES, "rules");
+ }
+
+ @Test
+ public void set_no_default_characteristic_when_default_characteristic_not_found_but_characteristic_has_been_overridden() {
+ setupData("set_no_default_characteristic_when_default_characteristic_not_found_but_characteristic_has_been_overridden");
+
+ task.start();
+ // No log should be displayed
+
+ checkTables("set_no_default_characteristic_when_default_characteristic_not_found_but_characteristic_has_been_overridden", EXCLUDED_COLUMN_NAMES, "rules");
+ }
+
+ @Test
+ public void fail_when_rule_is_linked_on_root_characteristic() {
+ setupData("ignore_rule_debt_definitions_if_rule_is_linked_on_root_characteristic");
+
+ try {
+ task.start();
+ fail();
+ } catch (Exception e) {
+ assertThat(e).isInstanceOf(MessageException.class).hasMessage("Rule 'fake:rule1' cannot be linked on the root characteristic 'MEMORY_EFFICIENCY'");
+ }
+ }
+
+ @Test
+ public void not_disable_template_rules_if_parent_is_enabled() {
+ setupData("not_disable_template_rules_if_parent_is_enabled");
+ task.start();
+
+ checkTables("not_disable_template_rules_if_parent_is_enabled", EXCLUDED_COLUMN_NAMES_INCLUDING_DEBT, "rules");
+ }
+
+ @Test
+ public void disable_template_rules_if_parent_is_disabled() {
+ setupData("disable_template_rules_if_parent_is_disabled");
+ task.start();
+
+ checkTables("disable_template_rules_if_parent_is_disabled", EXCLUDED_COLUMN_NAMES_INCLUDING_DEBT, "rules");
+ }
+
+ @Test
+ public void not_disable_manual_rules() {
+ // the hardcoded repository "manual" is used for manual violations
+ setupData("not_disable_manual_rules");
+ task.start();
+
+ checkTables("not_disable_manual_rules", EXCLUDED_COLUMN_NAMES_INCLUDING_DEBT, "rules");
+ }
+
+// @Test
+// public void test_high_number_of_rules() {
+// task = new RegisterRules(new RuleDefinitionsLoader(mock(RuleRepositories.class), new RulesDefinition[]{new BigRepository()}),
+// profilesManager, ruleTagOperations, myBatis, ruleDao, activeRuleDao, characteristicDao, mock(RegisterDebtModel.class));
+//
+// setupData("shared");
+// task.start();
+//
+// // There is already one rule in DB
+// assertThat(ruleDao.selectAll()).hasSize(BigRepository.SIZE + 1);
+// assertThat(ruleDao.selectParameters()).hasSize(BigRepository.SIZE * 20);
+// assertThat(ruleDao.selectTags(getMyBatis().openSession(false))).hasSize(BigRepository.SIZE * 3);
+// }
+
+// @Test
+// public void insert_extended_repositories() {
+// task = new RegisterRules(new RuleDefinitionsLoader(mock(RuleRepositories.class), new RulesDefinition[]{
+// new FindbugsRepository(), new FbContribRepository()}),
+// profilesManager, ruleRegistry, esRuleTags, ruleTagOperations, myBatis, ruleDao, ruleTagDao, activeRuleDao, characteristicDao, mock(RegisterDebtModel.class)
+// );
+//
+// setupData("empty");
+// task.start();
+//
+// checkTables("insert_extended_repositories", EXCLUDED_COLUMN_NAMES_INCLUDING_DEBT, "rules");
+// }
+
+ static class FakeRepository implements RulesDefinition {
+ @Override
+ public void define(Context context) {
+ NewRepository repo = context.createRepository("fake", "java");
+
+ NewRule rule1 = repo.createRule("rule1")
+ .setName("One")
+ .setHtmlDescription("Description of One")
+ .setSeverity(Severity.BLOCKER)
+ .setInternalKey("config1")
+ .setTags("tag1", "tag3", "tag5");
+
+ rule1.setDebtSubCharacteristic("MEMORY_EFFICIENCY")
+ .setDebtRemediationFunction(rule1.debtRemediationFunctions().linearWithOffset("5d", "10h"))
+ .setEffortToFixDescription("squid.S115.effortToFix");
+
+ rule1.createParam("param1").setDescription("parameter one").setDefaultValue("default value one");
+ rule1.createParam("param2").setDescription("parameter two").setDefaultValue("default value two");
+
+ repo.createRule("rule2")
+ .setName("Two")
+ .setHtmlDescription("Description of Two")
+ .setSeverity(Severity.INFO)
+ .setStatus(RuleStatus.DEPRECATED);
+ repo.done();
+ }
+ }
+
+ static class BigRepository implements RulesDefinition {
+ static final int SIZE = 500;
+
+ @Override
+ public void define(Context context) {
+ NewRepository repo = context.createRepository("big", "java");
+ for (int i = 0; i < SIZE; i++) {
+ NewRule rule = repo.createRule("rule" + i)
+ .setName("name of " + i)
+ .setHtmlDescription("description of " + i)
+ .setSeverity(Severity.BLOCKER)
+ .setInternalKey("config1")
+ .setTags("tag1", "tag3", "tag5");
+ for (int j = 0; j < 20; j++) {
+ rule.createParam("param" + j);
+ }
+
+ }
+ repo.done();
+ }
+ }
+
+ static class FindbugsRepository implements RulesDefinition {
+ @Override
+ public void define(Context context) {
+ NewRepository repo = context.createRepository("findbugs", "java");
+ repo.createRule("rule1")
+ .setName("Rule One")
+ .setHtmlDescription("Description of Rule One");
+ repo.done();
+ }
+ }
+
+ static class FbContribRepository implements RulesDefinition {
+ @Override
+ public void define(Context context) {
+ NewExtendedRepository repo = context.extendRepository("findbugs", "java");
+ repo.createRule("rule2")
+ .setName("Rule Two")
+ .setHtmlDescription("Description of Rule Two");
+ repo.done();
+ }
+ }
+}
+