]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10311 Store deprecated keys of rule at startup
authorEric Hartmann <hartmann.eric@gmail.com>
Thu, 1 Feb 2018 09:07:09 +0000 (10:07 +0100)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 8 Feb 2018 12:41:00 +0000 (13:41 +0100)
12 files changed:
server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java
server/sonar-db-dao/src/main/java/org/sonar/db/rule/DeprecatedRuleKeyDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/rule/RuleMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDbTester.java
server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleTesting.java
server/sonar-server/src/main/java/org/sonar/server/rule/RegisterRules.java
server/sonar-server/src/main/java/org/sonar/server/rule/SingleDeprecatedRuleKey.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/rule/RegisterRulesTest.java
server/sonar-server/src/test/java/org/sonar/server/rule/SingleDeprecatedRuleKeyTest.java [new file with mode: 0644]

index 37755361fa7cc0d3f439fd6d512c0b06926663c0..1843f327d7623ee3eb6cbb8731c1994e4fb9be44 100644 (file)
@@ -59,6 +59,7 @@ public final class SqTables {
     "ce_task_input",
     "ce_scanner_context",
     "default_qprofiles",
+    "deprecated_rule_keys",
     "duplications_index",
     "es_queue",
     "events",
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/DeprecatedRuleKeyDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/DeprecatedRuleKeyDto.java
new file mode 100644 (file)
index 0000000..e18c6fd
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.db.rule;
+
+import java.util.Objects;
+import javax.annotation.CheckForNull;
+
+/**
+ * Map the table "deprecated_rule_keys"
+ */
+public class DeprecatedRuleKeyDto {
+  /**
+   *  Uuid of the deprecated key
+   */
+  private String uuid;
+  /**
+   * the id of the current rule for this deprecated key
+   */
+  private Integer ruleId;
+  /**
+   * repository key that was deprecated
+   */
+  private String oldRepositoryKey;
+  /**
+   * rule key that was deprecated, not nullable
+   */
+  private String oldRuleKey;
+  /**
+   * creation date of the row
+   */
+  private Long createdAt;
+
+  /**
+   * current repository key retrieved from an external join on rule_id
+   */
+  private String newRepositoryKey;
+  /**
+   * current rule key retrieved from an external join on rule_id
+   */
+  private String newRuleKey;
+
+  public String getUuid() {
+    return uuid;
+  }
+
+  public DeprecatedRuleKeyDto setUuid(String uuid) {
+    this.uuid = uuid;
+    return this;
+  }
+
+  public Integer getRuleId() {
+    return ruleId;
+  }
+
+  public DeprecatedRuleKeyDto setRuleId(Integer ruleId) {
+    this.ruleId = ruleId;
+    return this;
+  }
+
+  public String getOldRepositoryKey() {
+    return oldRepositoryKey;
+  }
+
+  public DeprecatedRuleKeyDto setOldRepositoryKey(String oldRepositoryKey) {
+    this.oldRepositoryKey = oldRepositoryKey;
+    return this;
+  }
+
+  public String getOldRuleKey() {
+    return oldRuleKey;
+  }
+
+  public DeprecatedRuleKeyDto setOldRuleKey(String oldRuleKey) {
+    this.oldRuleKey = oldRuleKey;
+    return this;
+  }
+
+  /**
+   * This value may be null if the rule has been deleted
+   *
+   * @return the current repository key
+   */
+  @CheckForNull
+  public String getNewRepositoryKey() {
+    return newRepositoryKey;
+  }
+
+  /**
+   * This value may be null if the rule has been deleted
+   *
+   * @return the current rule key
+   */
+  @CheckForNull
+  public String getNewRuleKey() {
+    return newRuleKey;
+  }
+
+  public long getCreatedAt() {
+    return createdAt;
+  }
+
+  public DeprecatedRuleKeyDto setCreatedAt(long createdAt) {
+    this.createdAt = createdAt;
+    return this;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(uuid);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null || getClass() != obj.getClass()) {
+      return false;
+    }
+    final DeprecatedRuleKeyDto other = (DeprecatedRuleKeyDto) obj;
+    return Objects.equals(this.uuid, other.uuid);
+  }
+}
index 96d540e8579ad53afdaceea7e67dfb213237e90c..3ee2b77d40a95ec10962e14f437f304a4777bc26 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.db.rule;
 import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.Consumer;
 import javax.annotation.Nullable;
 import org.apache.ibatis.session.ResultHandler;
@@ -38,6 +39,7 @@ import static java.util.Collections.emptyList;
 import static java.util.Optional.ofNullable;
 import static org.sonar.db.DatabaseUtils.executeLargeInputs;
 import static org.sonar.db.DatabaseUtils.executeLargeInputsWithoutOutput;
+import static org.sonar.db.DatabaseUtils.executeLargeUpdates;
 
 public class RuleDao implements Dao {
 
@@ -238,4 +240,18 @@ public class RuleDao implements Dao {
     mapper(session).deleteParameter(ruleParameterId);
   }
 
+  public Set<DeprecatedRuleKeyDto> selectAllDeprecatedRuleKeys(DbSession session) {
+    return mapper(session).selectAllDeprecatedRuleKeys();
+  }
+
+  public void deleteDeprecatedRuleKeys(DbSession dbSession, Collection<String> uuids) {
+    if (uuids.isEmpty()) {
+      return;
+    }
+    executeLargeUpdates(uuids, mapper(dbSession)::deleteDeprecatedRuleKeys);
+  }
+
+  public void insert(DbSession dbSession, DeprecatedRuleKeyDto deprecatedRuleKey) {
+    mapper(dbSession).insertDeprecatedRuleKey(deprecatedRuleKey);
+  }
 }
index 09a7c49816943ad14664ab2a855869897ea909ab..f3c689e33a419c09231b84c584f271f4f0548407 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.db.rule;
 
 import java.util.List;
+import java.util.Set;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.session.ResultHandler;
 import org.sonar.db.es.RuleExtensionId;
@@ -83,4 +84,10 @@ public interface RuleMapper {
   void updateParameter(RuleParamDto param);
 
   void deleteParameter(Integer paramId);
+
+  Set<DeprecatedRuleKeyDto> selectAllDeprecatedRuleKeys();
+
+  void deleteDeprecatedRuleKeys(@Param("uuids") List<String> uuids);
+
+  void insertDeprecatedRuleKey(DeprecatedRuleKeyDto deprecatedRuleKeyDto);
 }
index 6cfde04506cf1c955abb09efa2028400558a2c76..37ed0bf0628cfdda10f1fce14599d80f90fb0165 100644 (file)
     where
       id=#{id,jdbcType=INTEGER}
   </update>
+
+  <select id="selectAllDeprecatedRuleKeys" resultType="org.sonar.db.rule.DeprecatedRuleKeyDto">
+    SELECT
+      drk.uuid,
+      drk.rule_id as "ruleId",
+      drk.old_repository_key as "oldRepositoryKey",
+      drk.old_rule_key as "oldRuleKey",
+      r.plugin_rule_key as "newRuleKey",
+      r.plugin_name as "newRepositoryKey",
+      drk.created_at as "createdAt"
+    FROM
+      deprecated_rule_keys drk
+    LEFT OUTER JOIN rules r on r.id = drk.rule_id
+  </select>
+
+  <delete id="deleteDeprecatedRuleKeys">
+    DELETE FROM
+      deprecated_rule_keys
+    WHERE
+    <foreach collection="uuids" index="index" item="uuid" open="" separator=" or " close="">
+      uuid=#{uuid,jdbcType=INTEGER}
+    </foreach>
+  </delete>
+
+  <insert id="insertDeprecatedRuleKey" parameterType="org.sonar.db.rule.DeprecatedRuleKeyDto" keyColumn="uuid" useGeneratedKeys="false" keyProperty="uuid">
+    INSERT INTO deprecated_rule_keys (
+      uuid,
+      rule_id,
+      old_repository_key,
+      old_rule_key,
+      created_at
+    )
+    values (
+      #{uuid,jdbcType=VARCHAR},
+      #{ruleId,jdbcType=INTEGER},
+      #{oldRepositoryKey,jdbcType=VARCHAR},
+      #{oldRuleKey,jdbcType=VARCHAR},
+      #{createdAt,jdbcType=BIGINT}
+    )
+  </insert>
 </mapper>
 
index 0cd2d997ee93252f48bb9c0d3147d6a24bd883d1..dd698f8eca31fa5175165c0c3ed78a38c75df0c6 100644 (file)
@@ -25,7 +25,9 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.Consumer;
+import org.apache.ibatis.exceptions.PersistenceException;
 import org.apache.ibatis.session.ResultHandler;
 import org.junit.Before;
 import org.junit.Rule;
@@ -48,7 +50,9 @@ import org.sonar.db.rule.RuleDto.Scope;
 
 import static com.google.common.collect.Sets.newHashSet;
 import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
 
@@ -747,7 +751,7 @@ public class RuleDaoTest {
   @Test
   public void scrollIndexingRulesByKeys_scrolls_nothing_if_key_does_not_exist() {
     Accumulator<RuleForIndexingDto> accumulator = new Accumulator<>();
-    RuleDefinitionDto r1 = db.rules().insert();
+    db.rules().insert();
 
     underTest.scrollIndexingRulesByKeys(db.getSession(), singletonList(RuleKey.of("does", "not exist")), accumulator);
 
@@ -778,7 +782,7 @@ public class RuleDaoTest {
     RuleMetadataDto r1Extension = db.rules().insertOrUpdateMetadata(r1, organization, r -> r.setTagsField("t1,t2"));
     RuleExtensionId r1ExtensionId = new RuleExtensionId(organization.getUuid(), r1.getRepositoryKey(), r1.getRuleKey());
     RuleDefinitionDto r2 = db.rules().insert();
-    RuleMetadataDto r2Extension = db.rules().insertOrUpdateMetadata(r2, organization, r -> r.setTagsField("t1,t3"));
+    db.rules().insertOrUpdateMetadata(r2, organization, r -> r.setTagsField("t1,t3"));
 
     underTest.scrollIndexingRuleExtensionsByIds(db.getSession(), singletonList(r1ExtensionId), accumulator);
 
@@ -788,7 +792,104 @@ public class RuleDaoTest {
         tuple(r1.getKey(), organization.getUuid(), r1Extension.getTagsAsString()));
   }
 
-  private static class Accumulator<T> implements Consumer<T> {
+  @Test
+  public void selectAllDeprecatedRuleKeys() {
+    RuleDefinitionDto r1 = db.rules().insert();
+    RuleDefinitionDto r2 = db.rules().insert();
+
+    db.rules().insertDeprecatedKey(r -> r.setRuleId(r1.getId()));
+    db.rules().insertDeprecatedKey(r -> r.setRuleId(r2.getId()));
+
+    db.getSession().commit();
+
+    Set<DeprecatedRuleKeyDto> deprecatedRuleKeyDtos = underTest.selectAllDeprecatedRuleKeys(db.getSession());
+    assertThat(deprecatedRuleKeyDtos).hasSize(2);
+  }
+
+  @Test
+  public void selectAllDeprecatedRuleKeys_return_values_even_if_there_is_no_rule() {
+    db.rules().insertDeprecatedKey();
+    db.rules().insertDeprecatedKey();
+
+    Set<DeprecatedRuleKeyDto> deprecatedRuleKeyDtos = underTest.selectAllDeprecatedRuleKeys(db.getSession());
+    assertThat(deprecatedRuleKeyDtos).hasSize(2);
+    assertThat(deprecatedRuleKeyDtos)
+      .extracting(DeprecatedRuleKeyDto::getNewRepositoryKey, DeprecatedRuleKeyDto::getNewRuleKey)
+      .containsExactly(
+        tuple(null, null),
+        tuple(null, null)
+      );
+  }
+
+  @Test
+  public void deleteDeprecatedRuleKeys_with_empty_list_has_no_effect() {
+    db.rules().insertDeprecatedKey();
+    db.rules().insertDeprecatedKey();
+
+    assertThat(underTest.selectAllDeprecatedRuleKeys(db.getSession())).hasSize(2);
+
+    underTest.deleteDeprecatedRuleKeys(db.getSession(), emptyList());
+
+    assertThat(underTest.selectAllDeprecatedRuleKeys(db.getSession())).hasSize(2);
+  }
+
+  @Test
+  public void deleteDeprecatedRuleKeys_with_non_existing_uuid_has_no_effect() {
+    db.rules().insertDeprecatedKey(d -> d.setUuid("A1"));
+    db.rules().insertDeprecatedKey(d -> d.setUuid("A2"));
+
+    assertThat(underTest.selectAllDeprecatedRuleKeys(db.getSession())).hasSize(2);
+
+    underTest.deleteDeprecatedRuleKeys(db.getSession(), asList("B1", "B2"));
+
+    assertThat(underTest.selectAllDeprecatedRuleKeys(db.getSession())).hasSize(2);
+  }
+
+  @Test
+  public void deleteDeprecatedRuleKeys() {
+    DeprecatedRuleKeyDto deprecatedRuleKeyDto1 = db.rules().insertDeprecatedKey();
+    db.rules().insertDeprecatedKey();;
+
+    assertThat(underTest.selectAllDeprecatedRuleKeys(db.getSession())).hasSize(2);
+
+    underTest.deleteDeprecatedRuleKeys(db.getSession(), singletonList(deprecatedRuleKeyDto1.getUuid()));
+    assertThat(underTest.selectAllDeprecatedRuleKeys(db.getSession())).hasSize(1);
+  }
+
+  @Test
+  public void insertDeprecatedRuleKey() {
+    RuleDefinitionDto r1 = db.rules().insert();
+    DeprecatedRuleKeyDto deprecatedRuleKeyDto = db.rules().insertDeprecatedKey(d -> d.setRuleId(r1.getId()));
+
+    db.getSession().commit();
+
+    Set<DeprecatedRuleKeyDto> deprecatedRuleKeyDtos = underTest.selectAllDeprecatedRuleKeys(db.getSession());
+    assertThat(deprecatedRuleKeyDtos).hasSize(1);
+
+    DeprecatedRuleKeyDto deprecatedRuleKeyDto1 = deprecatedRuleKeyDtos.iterator().next();
+    assertThat(deprecatedRuleKeyDto1.getOldRepositoryKey()).isEqualTo(deprecatedRuleKeyDto.getOldRepositoryKey());
+    assertThat(deprecatedRuleKeyDto1.getOldRuleKey()).isEqualTo(deprecatedRuleKeyDto.getOldRuleKey());
+    assertThat(deprecatedRuleKeyDto1.getNewRepositoryKey()).isEqualTo(r1.getRepositoryKey());
+    assertThat(deprecatedRuleKeyDto1.getNewRuleKey()).isEqualTo(r1.getRuleKey());
+    assertThat(deprecatedRuleKeyDto1.getUuid()).isEqualTo(deprecatedRuleKeyDto.getUuid());
+    assertThat(deprecatedRuleKeyDto1.getCreatedAt()).isEqualTo(deprecatedRuleKeyDto.getCreatedAt());
+    assertThat(deprecatedRuleKeyDto1.getRuleId()).isEqualTo(r1.getId());
+  }
+
+  @Test
+  public void insertDeprecatedRuleKey_with_same_RuleKey_should_fail() {
+    String repositoryKey = randomAlphanumeric(50);
+    String ruleKey = randomAlphanumeric(50);
+    db.rules().insertDeprecatedKey(d -> d.setOldRepositoryKey(repositoryKey)
+      .setOldRuleKey(ruleKey));
+
+    thrown.expect(PersistenceException.class);
+
+    db.rules().insertDeprecatedKey(d -> d.setOldRepositoryKey(repositoryKey)
+      .setOldRuleKey(ruleKey));
+  }
+
+  private static class Accumulator<T>  implements Consumer<T> {
     private final List<T> list = new ArrayList<>();
 
     @Override
index d816f4fe0fba335c212b1ff9ddac6472d0452d27..6bfaf0d28314fcf66474406b2fcca7a3730360c5 100644 (file)
  */
 package org.sonar.db.rule;
 
-import java.util.Arrays;
 import java.util.function.Consumer;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.server.rule.RuleParamType;
 import org.sonar.db.DbTester;
 import org.sonar.db.organization.OrganizationDto;
 
+import static java.util.Arrays.asList;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.sonar.db.rule.RuleTesting.newDeprecatedRuleKey;
 import static org.sonar.db.rule.RuleTesting.newRule;
 import static org.sonar.db.rule.RuleTesting.newRuleDto;
 
@@ -49,7 +50,7 @@ public class RuleDbTester {
   @SafeVarargs
   public final RuleDefinitionDto insert(Consumer<RuleDefinitionDto>... populaters) {
     RuleDefinitionDto rule = newRule();
-    Arrays.asList(populaters).forEach(populater -> populater.accept(rule));
+    asList(populaters).forEach(populater -> populater.accept(rule));
     return insert(rule);
   }
 
@@ -74,7 +75,7 @@ public class RuleDbTester {
   @SafeVarargs
   public final RuleMetadataDto insertOrUpdateMetadata(RuleDefinitionDto rule, OrganizationDto organization, Consumer<RuleMetadataDto>... populaters) {
     RuleMetadataDto dto = RuleTesting.newRuleMetadata(rule, organization);
-    Arrays.asList(populaters).forEach(populater -> populater.accept(dto));
+    asList(populaters).forEach(populater -> populater.accept(dto));
     return insertOrUpdateMetadata(dto);
   }
 
@@ -91,7 +92,7 @@ public class RuleDbTester {
   @SafeVarargs
   public final RuleParamDto insertRuleParam(RuleDefinitionDto rule, Consumer<RuleParamDto>... populaters) {
     RuleParamDto param = RuleTesting.newRuleParam(rule);
-    Arrays.asList(populaters).forEach(populater -> populater.accept(param));
+    asList(populaters).forEach(populater -> populater.accept(param));
     db.getDbClient().ruleDao().insertRuleParam(db.getSession(), rule, param);
     db.commit();
     return param;
@@ -127,7 +128,7 @@ public class RuleDbTester {
   @SafeVarargs
   public final RuleDto insertRule(OrganizationDto organization, Consumer<RuleDto>... populaters) {
     RuleDto ruleDto = newRuleDto(organization);
-    Arrays.asList(populaters).forEach(populater -> populater.accept(ruleDto));
+    asList(populaters).forEach(populater -> populater.accept(ruleDto));
     return insertRule(ruleDto);
   }
 
@@ -137,6 +138,15 @@ public class RuleDbTester {
     return insertRule(ruleDto);
   }
 
+  @SafeVarargs
+  public final DeprecatedRuleKeyDto insertDeprecatedKey(Consumer<DeprecatedRuleKeyDto>... deprecatedRuleKeyDtoConsumers) {
+    DeprecatedRuleKeyDto deprecatedRuleKeyDto = newDeprecatedRuleKey();
+    asList(deprecatedRuleKeyDtoConsumers).forEach(c -> c.accept(deprecatedRuleKeyDto));
+    db.getDbClient().ruleDao().insert(db.getSession(), deprecatedRuleKeyDto);
+    return deprecatedRuleKeyDto;
+  }
+
+
   public RuleParamDto insertRuleParam(RuleDto rule) {
     RuleParamDto param = new RuleParamDto();
     param.setRuleId(rule.getId());
index 7256c275bd8793533c27b946e6a75180e49c54e0..3339cc08120ee674ae1a1032b8240ebdaa7e60e4 100644 (file)
@@ -28,6 +28,8 @@ import org.sonar.api.rule.RuleStatus;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.rules.RuleType;
 import org.sonar.api.server.rule.RuleParamType;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.UuidFactoryFast;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.rule.RuleDto.Format;
 import org.sonar.db.rule.RuleDto.Scope;
@@ -49,6 +51,8 @@ public class RuleTesting {
   public static final RuleKey XOO_X2 = RuleKey.of("xoo", "x2");
   public static final RuleKey XOO_X3 = RuleKey.of("xoo", "x3");
 
+  private static final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
+
   private RuleTesting() {
     // only static helpers
   }
@@ -112,6 +116,15 @@ public class RuleTesting {
       .setType(RuleParamType.STRING.type());
   }
 
+  public static DeprecatedRuleKeyDto newDeprecatedRuleKey() {
+    return new DeprecatedRuleKeyDto()
+      .setUuid(uuidFactory.create())
+      .setOldRepositoryKey(randomAlphanumeric(50))
+      .setOldRuleKey(randomAlphanumeric(50))
+      .setRuleId(nextInt(100_000))
+      .setCreatedAt(System.currentTimeMillis());
+  }
+
   /**
    * @deprecated use newRule(...)
    */
index 3920e843356134be997c3a55a32e897d3190f226..8f82e6ac4c3614b1a7259f1dbb89088bfbbf4d72 100644 (file)
@@ -21,13 +21,14 @@ package org.sonar.server.rule;
 
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 import org.apache.commons.lang.ObjectUtils;
@@ -44,11 +45,13 @@ import org.sonar.api.utils.System2;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.api.utils.log.Profiler;
+import org.sonar.core.util.UuidFactory;
 import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.qualityprofile.ActiveRuleDto;
 import org.sonar.db.qualityprofile.ActiveRuleParamDto;
+import org.sonar.db.rule.DeprecatedRuleKeyDto;
 import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.db.rule.RuleDto.Format;
 import org.sonar.db.rule.RuleDto.Scope;
@@ -62,7 +65,10 @@ import org.sonar.server.rule.index.RuleIndexer;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Sets.difference;
 import static java.lang.String.format;
+import static java.util.Collections.emptySet;
+import static java.util.stream.Collectors.toSet;
 
 /**
  * Register rules at server startup
@@ -80,10 +86,11 @@ public class RegisterRules implements Startable {
   private final System2 system2;
   private final OrganizationFlags organizationFlags;
   private final WebServerRuleFinder webServerRuleFinder;
+  private final UuidFactory uuidFactory;
 
   public RegisterRules(RuleDefinitionsLoader defLoader, QProfileRules qProfileRules, DbClient dbClient, RuleIndexer ruleIndexer,
     ActiveRuleIndexer activeRuleIndexer, Languages languages, System2 system2, OrganizationFlags organizationFlags,
-    WebServerRuleFinder webServerRuleFinder) {
+    WebServerRuleFinder webServerRuleFinder, UuidFactory uuidFactory) {
     this.defLoader = defLoader;
     this.qProfileRules = qProfileRules;
     this.dbClient = dbClient;
@@ -93,6 +100,7 @@ public class RegisterRules implements Startable {
     this.system2 = system2;
     this.organizationFlags = organizationFlags;
     this.webServerRuleFinder = webServerRuleFinder;
+    this.uuidFactory = uuidFactory;
   }
 
   @Override
@@ -100,14 +108,20 @@ public class RegisterRules implements Startable {
     Profiler profiler = Profiler.create(LOG).startInfo("Register rules");
     try (DbSession dbSession = dbClient.openSession(false)) {
       Map<RuleKey, RuleDefinitionDto> allRules = loadRules(dbSession);
-      List<RuleKey> keysToIndex = new ArrayList<>();
+      Map<Integer, Set<SingleDeprecatedRuleKey>> existingDeprecatedRuleKeys = loadDeprecatedRuleKeys(dbSession);
 
       RulesDefinition.Context context = defLoader.load();
+
+      List<RulesDefinition.ExtendedRepository> repositories = getRepositories(context);
+
+      List<RuleKey> keysToIndex = new ArrayList<>();
       boolean orgsEnabled = organizationFlags.isEnabled(dbSession);
-      for (RulesDefinition.ExtendedRepository repoDef : getRepositories(context)) {
+
+      for (RulesDefinition.ExtendedRepository repoDef : repositories) {
         if (languages.get(repoDef.language()) != null) {
           for (RulesDefinition.Rule ruleDef : repoDef.rules()) {
             RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key());
+
             if (ruleDef.template() && orgsEnabled) {
               RuleDefinitionDto ruleDefinition = allRules.get(ruleKey);
               if (ruleDefinition != null && ruleDefinition.getStatus() == RuleStatus.REMOVED) {
@@ -118,7 +132,7 @@ public class RegisterRules implements Startable {
               }
               continue;
             }
-            boolean relevantForIndex = registerRule(ruleDef, allRules, dbSession);
+            boolean relevantForIndex = registerRule(ruleDef, allRules, existingDeprecatedRuleKeys, dbSession);
             if (relevantForIndex) {
               keysToIndex.add(ruleKey);
             }
@@ -126,6 +140,7 @@ public class RegisterRules implements Startable {
           dbSession.commit();
         }
       }
+
       List<RuleDefinitionDto> removedRules = processRemainingDbRules(allRules.values(), dbSession);
       List<ActiveRuleChange> changes = removeActiveRulesOnStillExistingRepositories(dbSession, removedRules, context);
       dbSession.commit();
@@ -142,6 +157,11 @@ public class RegisterRules implements Startable {
     }
   }
 
+  @Override
+  public void stop() {
+    // nothing
+  }
+
   private void persistRepositories(DbSession dbSession, List<RulesDefinition.Repository> repositories) {
     dbClient.ruleRepositoryDao().truncate(dbSession);
     List<RuleRepositoryDto> dtos = repositories
@@ -152,12 +172,8 @@ public class RegisterRules implements Startable {
     dbSession.commit();
   }
 
-  @Override
-  public void stop() {
-    // nothing
-  }
-
-  private boolean registerRule(RulesDefinition.Rule ruleDef, Map<RuleKey, RuleDefinitionDto> allRules, DbSession session) {
+  private boolean registerRule(RulesDefinition.Rule ruleDef, Map<RuleKey, RuleDefinitionDto> allRules, Map<Integer,
+    Set<SingleDeprecatedRuleKey>> existingDeprecatedRuleKeys, DbSession session) {
     RuleKey ruleKey = RuleKey.of(ruleDef.repository().key(), ruleDef.key());
 
     RuleDefinitionDto existingRule = allRules.remove(ruleKey);
@@ -171,24 +187,16 @@ public class RegisterRules implements Startable {
       newRule = false;
     }
 
-    boolean executeUpdate = false;
-    if (mergeRule(ruleDef, rule)) {
-      executeUpdate = true;
-    }
-
-    if (mergeDebtDefinitions(ruleDef, rule)) {
-      executeUpdate = true;
-    }
-
-    if (mergeTags(ruleDef, rule)) {
-      executeUpdate = true;
-    }
+    boolean executeUpdate = mergeRule(ruleDef, rule);
+    executeUpdate |= mergeDebtDefinitions(ruleDef, rule);
+    executeUpdate |= mergeTags(ruleDef, rule);
 
     if (executeUpdate) {
       update(session, rule);
     }
 
     mergeParams(ruleDef, rule, session);
+    updateDeprecatedKeys(ruleDef, rule, existingDeprecatedRuleKeys, session);
     return newRule || executeUpdate;
   }
 
@@ -200,6 +208,14 @@ public class RegisterRules implements Startable {
     return rules;
   }
 
+  private Map<Integer, Set<SingleDeprecatedRuleKey>> loadDeprecatedRuleKeys(DbSession dbSession) {
+    return dbClient.ruleDao().selectAllDeprecatedRuleKeys(dbSession)
+      .stream()
+      .map(SingleDeprecatedRuleKey::from)
+      .collect(Collectors.groupingBy(SingleDeprecatedRuleKey::getRuleId, toSet()));
+  }
+
+
   private List<RulesDefinition.ExtendedRepository> getRepositories(RulesDefinition.Context context) {
     List<RulesDefinition.ExtendedRepository> repositories = new ArrayList<>();
     for (RulesDefinition.Repository repoDef : context.repositories()) {
@@ -256,7 +272,7 @@ public class RegisterRules implements Startable {
     }
   }
 
-  private boolean mergeRule(RulesDefinition.Rule def, RuleDefinitionDto dto) {
+  private static boolean mergeRule(RulesDefinition.Rule def, RuleDefinitionDto dto) {
     boolean changed = false;
     if (!StringUtils.equals(dto.getName(), def.name())) {
       dto.setName(def.name());
@@ -303,7 +319,7 @@ public class RegisterRules implements Startable {
     return changed;
   }
 
-  private boolean mergeDescription(RulesDefinition.Rule def, RuleDefinitionDto dto) {
+  private static boolean mergeDescription(RulesDefinition.Rule def, RuleDefinitionDto dto) {
     boolean changed = false;
     if (def.htmlDescription() != null && !StringUtils.equals(dto.getDescription(), def.htmlDescription())) {
       dto.setDescription(def.htmlDescription());
@@ -317,7 +333,7 @@ public class RegisterRules implements Startable {
     return changed;
   }
 
-  private boolean mergeDebtDefinitions(RulesDefinition.Rule def, RuleDefinitionDto dto) {
+  private static boolean mergeDebtDefinitions(RulesDefinition.Rule def, RuleDefinitionDto dto) {
     // Debt definitions are set to null if the sub-characteristic and the remediation function are null
     DebtRemediationFunction debtRemediationFunction = def.debtRemediationFunction();
     boolean hasDebt = debtRemediationFunction != null;
@@ -331,7 +347,7 @@ public class RegisterRules implements Startable {
     return mergeDebtDefinitions(dto, null, null, null, null);
   }
 
-  private boolean mergeDebtDefinitions(RuleDefinitionDto dto, @Nullable String remediationFunction,
+  private static boolean mergeDebtDefinitions(RuleDefinitionDto dto, @Nullable String remediationFunction,
     @Nullable String remediationCoefficient, @Nullable String remediationOffset, @Nullable String effortToFixDescription) {
     boolean changed = false;
 
@@ -399,7 +415,7 @@ public class RegisterRules implements Startable {
     }
   }
 
-  private boolean mergeParam(RuleParamDto paramDto, RulesDefinition.Param paramDef) {
+  private static boolean mergeParam(RuleParamDto paramDto, RulesDefinition.Param paramDef) {
     boolean changed = false;
     if (!StringUtils.equals(paramDto.getType(), paramDef.type().toString())) {
       paramDto.setType(paramDef.type().toString());
@@ -420,7 +436,7 @@ public class RegisterRules implements Startable {
     boolean changed = false;
 
     if (RuleStatus.REMOVED == ruleDef.status()) {
-      dto.setSystemTags(Collections.emptySet());
+      dto.setSystemTags(emptySet());
       changed = true;
     } else if (dto.getSystemTags().size() != ruleDef.tags().size() ||
       !dto.getSystemTags().containsAll(ruleDef.tags())) {
@@ -432,6 +448,33 @@ public class RegisterRules implements Startable {
     return changed;
   }
 
+  private void updateDeprecatedKeys(RulesDefinition.Rule ruleDef, RuleDefinitionDto rule,
+    Map<Integer, Set<SingleDeprecatedRuleKey>> existingDeprecatedKeysById, DbSession dbSession) {
+
+    Set<SingleDeprecatedRuleKey> deprecatedRuleKeysFromDefinition = SingleDeprecatedRuleKey.from(ruleDef);
+    Set<SingleDeprecatedRuleKey> deprecatedRuleKeysFromDB = existingDeprecatedKeysById.getOrDefault(rule.getId(), emptySet());
+
+    // DeprecatedKeys that must be deleted
+    List<String> uuidsToBeDeleted = difference(deprecatedRuleKeysFromDB, deprecatedRuleKeysFromDefinition).stream()
+      .map(SingleDeprecatedRuleKey::getUuid)
+      .collect(MoreCollectors.toList());
+
+    dbClient.ruleDao().deleteDeprecatedRuleKeys(dbSession, uuidsToBeDeleted);
+
+    // DeprecatedKeys that must be created
+    Sets.SetView<SingleDeprecatedRuleKey> deprecatedRuleKeysToBeCreated = difference(deprecatedRuleKeysFromDefinition, deprecatedRuleKeysFromDB);
+
+    deprecatedRuleKeysToBeCreated
+      .forEach(r -> dbClient.ruleDao().insert(dbSession, new DeprecatedRuleKeyDto()
+          .setUuid(uuidFactory.create())
+          .setRuleId(rule.getId())
+          .setOldRepositoryKey(r.getOldRepositoryKey())
+          .setOldRuleKey(r.getOldRuleKey())
+          .setCreatedAt(system2.now())
+        )
+      );
+  }
+
   private List<RuleDefinitionDto> processRemainingDbRules(Collection<RuleDefinitionDto> existingRules, DbSession session) {
     // custom rules check status of template, so they must be processed at the end
     List<RuleDefinitionDto> customRules = newArrayList();
@@ -465,7 +508,7 @@ public class RegisterRules implements Startable {
   private void removeRule(DbSession session, List<RuleDefinitionDto> removedRules, RuleDefinitionDto rule) {
     LOG.info(format("Disable rule %s", rule.getKey()));
     rule.setStatus(RuleStatus.REMOVED);
-    rule.setSystemTags(Collections.emptySet());
+    rule.setSystemTags(emptySet());
     update(session, rule);
     // FIXME resetting the tags for all organizations must be handled a different way
     // rule.setTags(Collections.emptySet());
diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/SingleDeprecatedRuleKey.java b/server/sonar-server/src/main/java/org/sonar/server/rule/SingleDeprecatedRuleKey.java
new file mode 100644 (file)
index 0000000..fdfc5b9
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.rule;
+
+import java.util.Objects;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.rule.DeprecatedRuleKeyDto;
+
+@Immutable
+class SingleDeprecatedRuleKey {
+  private String oldRuleKey;
+  private String oldRepositoryKey;
+  private String newRuleKey;
+  private String newRepositoryKey;
+  private String uuid;
+  private Integer ruleId;
+
+  /**
+   * static methods {@link #from(RulesDefinition.Rule)} and {@link #from(DeprecatedRuleKeyDto)} must be used
+   */
+  private SingleDeprecatedRuleKey() {
+    // empty
+  }
+
+  public static Set<SingleDeprecatedRuleKey> from(RulesDefinition.Rule rule) {
+    return rule.deprecatedRuleKeys().stream()
+      .map(r -> new SingleDeprecatedRuleKey()
+        .setNewRepositoryKey(rule.repository().key())
+        .setNewRuleKey(rule.key())
+        .setOldRepositoryKey(r.repository())
+        .setOldRuleKey(r.rule()))
+      .collect(MoreCollectors.toSet(rule.deprecatedRuleKeys().size()));
+  }
+
+  public static SingleDeprecatedRuleKey from(DeprecatedRuleKeyDto rule) {
+    return new SingleDeprecatedRuleKey()
+      .setUuid(rule.getUuid())
+      .setRuleId(rule.getRuleId())
+      .setNewRepositoryKey(rule.getNewRepositoryKey())
+      .setNewRuleKey(rule.getNewRuleKey())
+      .setOldRepositoryKey(rule.getOldRepositoryKey())
+      .setOldRuleKey(rule.getOldRuleKey());
+  }
+
+  public String getOldRuleKey() {
+    return oldRuleKey;
+  }
+
+  public String getOldRepositoryKey() {
+    return oldRepositoryKey;
+  }
+
+  public RuleKey getOldRuleKeyAsRuleKey() {
+    return RuleKey.of(oldRepositoryKey, oldRuleKey);
+  }
+
+  @CheckForNull
+  public String getNewRuleKey() {
+    return newRuleKey;
+  }
+
+  @CheckForNull
+  public String getNewRepositoryKey() {
+    return newRepositoryKey;
+  }
+
+  @CheckForNull
+  public String getUuid() {
+    return uuid;
+  }
+
+  @CheckForNull
+  public Integer getRuleId() {
+    return ruleId;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof SingleDeprecatedRuleKey)) {
+      return false;
+    }
+    SingleDeprecatedRuleKey that = (SingleDeprecatedRuleKey) o;
+    return Objects.equals(oldRuleKey, that.oldRuleKey) &&
+      Objects.equals(oldRepositoryKey, that.oldRepositoryKey) &&
+      Objects.equals(newRuleKey, that.newRuleKey) &&
+      Objects.equals(newRepositoryKey, that.newRepositoryKey);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(oldRuleKey, oldRepositoryKey, newRuleKey, newRepositoryKey);
+  }
+
+  private SingleDeprecatedRuleKey setRuleId(Integer ruleId) {
+    this.ruleId = ruleId;
+    return this;
+  }
+
+  private SingleDeprecatedRuleKey setUuid(String uuid) {
+    this.uuid = uuid;
+    return this;
+  }
+
+  private SingleDeprecatedRuleKey setOldRuleKey(String oldRuleKey) {
+    this.oldRuleKey = oldRuleKey;
+    return this;
+  }
+
+  private SingleDeprecatedRuleKey setOldRepositoryKey(String oldRepositoryKey) {
+    this.oldRepositoryKey = oldRepositoryKey;
+    return this;
+  }
+
+  private SingleDeprecatedRuleKey setNewRuleKey(@Nullable String newRuleKey) {
+    this.newRuleKey = newRuleKey;
+    return this;
+  }
+
+  private SingleDeprecatedRuleKey setNewRepositoryKey(@Nullable String newRepositoryKey) {
+    this.newRepositoryKey = newRepositoryKey;
+    return this;
+  }
+}
index 4bf6459bf56be049dff5c05452c35aee81b0ab45..5be05c9d9dc9a692cfeebe37582108453fdb3347 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.rule;
 
 import java.util.Date;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.IntStream;
 import org.junit.Before;
 import org.junit.Test;
@@ -37,10 +38,13 @@ import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.UuidFactoryFast;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.rule.DeprecatedRuleKeyDto;
 import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleDto.Scope;
@@ -69,6 +73,7 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.sonar.api.rule.Severity.BLOCKER;
 import static org.sonar.api.rule.Severity.INFO;
+import static org.sonar.api.server.rule.RulesDefinition.*;
 
 public class RegisterRulesTest {
 
@@ -98,6 +103,7 @@ public class RegisterRulesTest {
   private RuleIndex ruleIndex;
   private OrganizationDto defaultOrganization;
   private OrganizationFlags organizationFlags = TestOrganizationFlags.standalone();
+  private UuidFactory uuidFactory = UuidFactoryFast.getInstance();
 
   @Before
   public void before() {
@@ -150,7 +156,7 @@ public class RegisterRulesTest {
 
     // register one rule
     execute(context -> {
-      RulesDefinition.NewRepository repo = context.createRepository("fake", "java");
+      NewRepository repo = context.createRepository("fake", "java");
       repo.createRule(ruleKey)
         .setName(randomAlphanumeric(5))
         .setHtmlDescription(randomAlphanumeric(20));
@@ -192,7 +198,7 @@ public class RegisterRulesTest {
 
     // register many rules
     execute(context -> {
-      RulesDefinition.NewRepository repo = context.createRepository("fake", "java");
+      NewRepository repo = context.createRepository("fake", "java");
       IntStream.range(0, numberOfRules)
         .mapToObj(i -> "rule-" + i)
         .forEach(ruleKey -> repo.createRule(ruleKey)
@@ -298,32 +304,26 @@ public class RegisterRulesTest {
 
   @Test
   public void add_new_tag() {
-    execute(new RulesDefinition() {
-      @Override
-      public void define(RulesDefinition.Context context) {
-        RulesDefinition.NewRepository repo = context.createRepository("fake", "java");
-        repo.createRule("rule1")
-          .setName("Rule One")
-          .setHtmlDescription("Description of Rule One")
-          .setTags("tag1");
-        repo.done();
-      }
+    execute((RulesDefinition) context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("rule1")
+        .setName("Rule One")
+        .setHtmlDescription("Description of Rule One")
+        .setTags("tag1");
+      repo.done();
     });
 
     OrganizationDto defaultOrganization = dbTester.getDefaultOrganization();
     RuleDto rule = dbClient.ruleDao().selectOrFailByKey(dbTester.getSession(), defaultOrganization, RULE_KEY1);
     assertThat(rule.getSystemTags()).containsOnly("tag1");
 
-    execute(new RulesDefinition() {
-      @Override
-      public void define(RulesDefinition.Context context) {
-        RulesDefinition.NewRepository repo = context.createRepository("fake", "java");
-        repo.createRule("rule1")
-          .setName("Rule One")
-          .setHtmlDescription("Description of Rule One")
-          .setTags("tag1", "tag2");
-        repo.done();
-      }
+    execute((RulesDefinition) context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("rule1")
+        .setName("Rule One")
+        .setHtmlDescription("Description of Rule One")
+        .setTags("tag1", "tag2");
+      repo.done();
     });
 
     rule = dbClient.ruleDao().selectOrFailByKey(dbTester.getSession(), defaultOrganization, RULE_KEY1);
@@ -333,27 +333,21 @@ public class RegisterRulesTest {
   @Test
   public void update_only_rule_name() {
     when(system.now()).thenReturn(DATE1.getTime());
-    execute(new RulesDefinition() {
-      @Override
-      public void define(Context context) {
-        NewRepository repo = context.createRepository("fake", "java");
-        repo.createRule("rule")
-          .setName("Name1")
-          .setHtmlDescription("Description");
-        repo.done();
-      }
+    execute((RulesDefinition) context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("rule")
+        .setName("Name1")
+        .setHtmlDescription("Description");
+      repo.done();
     });
 
     when(system.now()).thenReturn(DATE2.getTime());
-    execute(new RulesDefinition() {
-      @Override
-      public void define(Context context) {
-        NewRepository repo = context.createRepository("fake", "java");
-        repo.createRule("rule")
-          .setName("Name2")
-          .setHtmlDescription("Description");
-        repo.done();
-      }
+    execute((RulesDefinition) context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("rule")
+        .setName("Name2")
+        .setHtmlDescription("Description");
+      repo.done();
     });
 
     // rule1 has been updated
@@ -368,27 +362,21 @@ public class RegisterRulesTest {
   @Test
   public void update_only_rule_description() {
     when(system.now()).thenReturn(DATE1.getTime());
-    execute(new RulesDefinition() {
-      @Override
-      public void define(Context context) {
-        NewRepository repo = context.createRepository("fake", "java");
-        repo.createRule("rule")
-          .setName("Name")
-          .setHtmlDescription("Desc1");
-        repo.done();
-      }
+    execute((RulesDefinition) context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("rule")
+        .setName("Name")
+        .setHtmlDescription("Desc1");
+      repo.done();
     });
 
     when(system.now()).thenReturn(DATE2.getTime());
-    execute(new RulesDefinition() {
-      @Override
-      public void define(Context context) {
-        NewRepository repo = context.createRepository("fake", "java");
-        repo.createRule("rule")
-          .setName("Name")
-          .setHtmlDescription("Desc2");
-        repo.done();
-      }
+    execute((RulesDefinition) context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("rule")
+        .setName("Name")
+        .setHtmlDescription("Desc2");
+      repo.done();
     });
 
     // rule1 has been updated
@@ -431,7 +419,6 @@ public class RegisterRulesTest {
     when(system.now()).thenReturn(DATE2.getTime());
     execute(new FakeRepositoryV1());
 
-    String organizationUuid = dbTester.getDefaultOrganization().getUuid();
     RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(dbTester.getSession(), defaultOrganization, RULE_KEY1);
     assertThat(rule1.getCreatedAt()).isEqualTo(DATE1.getTime());
     assertThat(rule1.getUpdatedAt()).isEqualTo(DATE1.getTime());
@@ -443,7 +430,6 @@ public class RegisterRulesTest {
     assertThat(dbClient.ruleDao().selectAllDefinitions(dbTester.getSession())).hasSize(2);
     assertThat(esTester.getIds(RuleIndexDefinition.INDEX_TYPE_RULE)).containsOnly(RULE_KEY1.toString(), RULE_KEY2.toString());
 
-    String organizationUuid = dbTester.getDefaultOrganization().getUuid();
     RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(dbTester.getSession(), defaultOrganization, RULE_KEY2);
     assertThat(rule2.getStatus()).isEqualTo(RuleStatus.READY);
 
@@ -530,6 +516,94 @@ public class RegisterRulesTest {
     assertThat(logTester.logs(LoggerLevel.INFO)).contains("Template rule test:rule1 will not be imported, because organizations are enabled.");
   }
 
+  @Test
+  public void rules_that_deprecate_previous_rule_must_be_recorded() {
+    execute(context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("rule1")
+        .setName("One")
+        .setHtmlDescription("Description of One")
+        .setSeverity(BLOCKER)
+        .setInternalKey("config1")
+        .setTags("tag1", "tag2", "tag3")
+        .setType(RuleType.CODE_SMELL)
+        .setStatus(RuleStatus.BETA);
+      repo.done();
+    });
+
+    execute(context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("newKey")
+        .setName("One")
+        .setHtmlDescription("Description of One")
+        .setSeverity(BLOCKER)
+        .setInternalKey("config1")
+        .setTags("tag1", "tag2", "tag3")
+        .setType(RuleType.CODE_SMELL)
+        .setStatus(RuleStatus.BETA)
+        .addDeprecatedRuleKey("fake", "rule1")
+        .addDeprecatedRuleKey("fake", "rule2");
+      repo.done();
+    });
+
+    List<RuleDefinitionDto> rules = dbClient.ruleDao().selectAllDefinitions(dbTester.getSession());
+    Set<DeprecatedRuleKeyDto> deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(dbTester.getSession());
+    //assertThat(rules).hasSize(1); FIXME this must be true when renaming is done
+    assertThat(deprecatedRuleKeys).hasSize(2);
+  }
+
+  @Test
+  public void rules_that_remove_deprecated_key_must_remove_records() {
+    execute(context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("rule1")
+        .setName("One")
+        .setHtmlDescription("Description of One")
+        .setSeverity(BLOCKER)
+        .setInternalKey("config1")
+        .setTags("tag1", "tag2", "tag3")
+        .setType(RuleType.CODE_SMELL)
+        .setStatus(RuleStatus.BETA);
+      repo.done();
+    });
+
+    execute(context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("newKey")
+        .setName("One")
+        .setHtmlDescription("Description of One")
+        .setSeverity(BLOCKER)
+        .setInternalKey("config1")
+        .setTags("tag1", "tag2", "tag3")
+        .setType(RuleType.CODE_SMELL)
+        .setStatus(RuleStatus.BETA)
+        .addDeprecatedRuleKey("fake", "rule1")
+        .addDeprecatedRuleKey("fake", "rule2");
+      repo.done();
+    });
+
+    //assertThat(dbClient.ruleDao().selectAllDefinitions(dbTester.getSession())).hasSize(1); FIXME this must be true when renaming is done
+    Set<DeprecatedRuleKeyDto> deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(dbTester.getSession());
+    assertThat(deprecatedRuleKeys).hasSize(2);
+
+    execute(context -> {
+      NewRepository repo = context.createRepository("fake", "java");
+      repo.createRule("newKey")
+        .setName("One")
+        .setHtmlDescription("Description of One")
+        .setSeverity(BLOCKER)
+        .setInternalKey("config1")
+        .setTags("tag1", "tag2", "tag3")
+        .setType(RuleType.CODE_SMELL)
+        .setStatus(RuleStatus.BETA);
+      repo.done();
+    });
+
+    //assertThat(dbClient.ruleDao().selectAllDefinitions(dbTester.getSession())).hasSize(1); FIXME this must be true when renaming is done
+    deprecatedRuleKeys = dbClient.ruleDao().selectAllDeprecatedRuleKeys(dbTester.getSession());
+    assertThat(deprecatedRuleKeys).hasSize(0);
+  }
+
   private void execute(RulesDefinition... defs) {
     ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
     when(pluginRepository.getPluginKey(any(RulesDefinition.class))).thenReturn(FAKE_PLUGIN_KEY);
@@ -539,7 +613,8 @@ public class RegisterRulesTest {
     when(languages.get("java")).thenReturn(mock(Language.class));
     reset(webServerRuleFinder);
 
-    RegisterRules task = new RegisterRules(loader, qProfileRules, dbClient, ruleIndexer, activeRuleIndexer, languages, system, organizationFlags, webServerRuleFinder);
+    RegisterRules task = new RegisterRules(loader, qProfileRules, dbClient, ruleIndexer, activeRuleIndexer,
+      languages, system, organizationFlags, webServerRuleFinder, uuidFactory);
     task.start();
     // Execute a commit to refresh session state as the task is using its own session
     dbTester.getSession().commit();
@@ -658,7 +733,7 @@ public class RegisterRulesTest {
   static class RepositoryWithOneTemplateRule implements RulesDefinition {
     @Override
     public void define(Context context) {
-      RulesDefinition.NewRepository repo = context.createRepository("test", "java");
+      NewRepository repo = context.createRepository("test", "java");
       repo.createRule("rule1")
         .setName("Rule One")
         .setHtmlDescription("Description of Rule One")
diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/SingleDeprecatedRuleKeyTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/SingleDeprecatedRuleKeyTest.java
new file mode 100644 (file)
index 0000000..0311797
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.rule;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import org.assertj.core.groups.Tuple;
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.rule.DeprecatedRuleKeyDto;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.apache.commons.lang.math.RandomUtils.nextInt;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SingleDeprecatedRuleKeyTest {
+
+  @Test
+  public void test_creation_from_DeprecatedRuleKeyDto() {
+    // Creation from DeprecatedRuleKeyDto
+    DeprecatedRuleKeyDto deprecatedRuleKeyDto = new DeprecatedRuleKeyDto()
+      .setOldRuleKey(randomAlphanumeric(50))
+      .setOldRepositoryKey(randomAlphanumeric(50))
+      .setRuleId(nextInt(1000))
+      .setUuid(randomAlphanumeric(40));
+
+    SingleDeprecatedRuleKey singleDeprecatedRuleKey = SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto);
+
+    assertThat(singleDeprecatedRuleKey.getOldRepositoryKey()).isEqualTo(deprecatedRuleKeyDto.getOldRepositoryKey());
+    assertThat(singleDeprecatedRuleKey.getOldRuleKey()).isEqualTo(deprecatedRuleKeyDto.getOldRuleKey());
+    assertThat(singleDeprecatedRuleKey.getNewRepositoryKey()).isEqualTo(deprecatedRuleKeyDto.getNewRepositoryKey());
+    assertThat(singleDeprecatedRuleKey.getNewRuleKey()).isEqualTo(deprecatedRuleKeyDto.getNewRuleKey());
+    assertThat(singleDeprecatedRuleKey.getUuid()).isEqualTo(deprecatedRuleKeyDto.getUuid());
+    assertThat(singleDeprecatedRuleKey.getRuleId()).isEqualTo(deprecatedRuleKeyDto.getRuleId());
+    assertThat(singleDeprecatedRuleKey.getOldRuleKeyAsRuleKey())
+      .isEqualTo(RuleKey.of(deprecatedRuleKeyDto.getOldRepositoryKey(), deprecatedRuleKeyDto.getOldRuleKey()));
+  }
+
+  @Test
+  public void test_creation_from_RulesDefinitionRule() {
+    // Creation from RulesDefinition.Rule
+    ImmutableSet<RuleKey> deprecatedRuleKeys = ImmutableSet.of(
+      RuleKey.of(randomAlphanumeric(50), randomAlphanumeric(50)),
+      RuleKey.of(randomAlphanumeric(50), randomAlphanumeric(50)),
+      RuleKey.of(randomAlphanumeric(50), randomAlphanumeric(50))
+      );
+
+    RulesDefinition.Repository repository = mock(RulesDefinition.Repository.class);
+    when(repository.key()).thenReturn(randomAlphanumeric(50));
+
+    RulesDefinition.Rule rule = mock(RulesDefinition.Rule.class);
+    when(rule.key()).thenReturn(randomAlphanumeric(50));
+    when(rule.deprecatedRuleKeys()).thenReturn(deprecatedRuleKeys);
+    when(rule.repository()).thenReturn(repository);
+
+    Set<SingleDeprecatedRuleKey> singleDeprecatedRuleKeys = SingleDeprecatedRuleKey.from(rule);
+    assertThat(singleDeprecatedRuleKeys).hasSize(deprecatedRuleKeys.size());
+    assertThat(singleDeprecatedRuleKeys)
+      .extracting(SingleDeprecatedRuleKey::getUuid, SingleDeprecatedRuleKey::getOldRepositoryKey, SingleDeprecatedRuleKey::getOldRuleKey,
+        SingleDeprecatedRuleKey::getNewRepositoryKey, SingleDeprecatedRuleKey::getNewRuleKey, SingleDeprecatedRuleKey::getOldRuleKeyAsRuleKey)
+      .containsExactlyInAnyOrder(
+        deprecatedRuleKeys.stream().map(
+          r -> tuple(null, r.repository(), r.rule(), rule.repository().key(), rule.key(), RuleKey.of(r.repository(), r.rule()))
+        ).collect(MoreCollectors.toArrayList(deprecatedRuleKeys.size())).toArray(new Tuple[deprecatedRuleKeys.size()])
+    );
+  }
+
+  @Test
+  public void test_equality() {
+    DeprecatedRuleKeyDto deprecatedRuleKeyDto1 = new DeprecatedRuleKeyDto()
+      .setOldRuleKey(randomAlphanumeric(50))
+      .setOldRepositoryKey(randomAlphanumeric(50))
+      .setUuid(randomAlphanumeric(40))
+      .setRuleId(1);
+
+    DeprecatedRuleKeyDto deprecatedRuleKeyDto1WithoutUuid = new DeprecatedRuleKeyDto()
+      .setOldRuleKey(deprecatedRuleKeyDto1.getOldRuleKey())
+      .setOldRepositoryKey(deprecatedRuleKeyDto1.getOldRepositoryKey());
+
+    DeprecatedRuleKeyDto deprecatedRuleKeyDto2 = new DeprecatedRuleKeyDto()
+      .setOldRuleKey(randomAlphanumeric(50))
+      .setOldRepositoryKey(randomAlphanumeric(50))
+      .setUuid(randomAlphanumeric(40));
+
+    SingleDeprecatedRuleKey singleDeprecatedRuleKey1 = SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto1);
+    SingleDeprecatedRuleKey singleDeprecatedRuleKey2 = SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto2);
+
+    assertThat(singleDeprecatedRuleKey1).isEqualTo(singleDeprecatedRuleKey1);
+    assertThat(singleDeprecatedRuleKey1).isEqualTo(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto1));
+    assertThat(singleDeprecatedRuleKey1).isEqualTo(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto1WithoutUuid));
+    assertThat(singleDeprecatedRuleKey2).isEqualTo(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto2));
+
+    assertThat(singleDeprecatedRuleKey1.hashCode()).isEqualTo(singleDeprecatedRuleKey1.hashCode());
+    assertThat(singleDeprecatedRuleKey1.hashCode()).isEqualTo(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto1).hashCode());
+    assertThat(singleDeprecatedRuleKey1.hashCode()).isEqualTo(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto1WithoutUuid).hashCode());
+    assertThat(singleDeprecatedRuleKey2.hashCode()).isEqualTo(SingleDeprecatedRuleKey.from(deprecatedRuleKeyDto2).hashCode());
+
+    assertThat(singleDeprecatedRuleKey1).isNotEqualTo(null);
+    assertThat(singleDeprecatedRuleKey1).isNotEqualTo("");
+    assertThat(singleDeprecatedRuleKey1).isNotEqualTo(singleDeprecatedRuleKey2);
+    assertThat(singleDeprecatedRuleKey2).isNotEqualTo(singleDeprecatedRuleKey1);
+
+    assertThat(singleDeprecatedRuleKey1.hashCode()).isNotEqualTo(singleDeprecatedRuleKey2.hashCode());
+    assertThat(singleDeprecatedRuleKey2.hashCode()).isNotEqualTo(singleDeprecatedRuleKey1.hashCode());
+  }
+}