]> source.dussan.org Git - sonarqube.git/commitdiff
RegisterRule & Test usign Dao2
authorStephane Gamard <stephane.gamard@searchbox.com>
Thu, 8 May 2014 22:11:24 +0000 (00:11 +0200)
committerStephane Gamard <stephane.gamard@searchbox.com>
Thu, 8 May 2014 22:11:24 +0000 (00:11 +0200)
sonar-server/src/main/java/org/sonar/server/rule2/RegisterRules.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/rule2/RegisterRulesTest.java [new file with mode: 0644]

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 (file)
index 0000000..d66622a
--- /dev/null
@@ -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 (file)
index 0000000..624fe9f
--- /dev/null
@@ -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();
+    }
+  }
+}
+