]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10517 Create table project_mappings
authorJulien HENRY <julien.henry@sonarsource.com>
Fri, 30 Mar 2018 08:13:33 +0000 (10:13 +0200)
committerSonarTech <sonartech@sonarsource.com>
Wed, 16 May 2018 18:20:45 +0000 (20:20 +0200)
  * Migration
  * Add DAO
  * Purge on project deletion

22 files changed:
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingsDao.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingsMapper.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeCommands.java
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/purge/PurgeMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/mapping/ProjectMappingsMapper.xml [new file with mode: 0644]
server/sonar-db-dao/src/main/resources/org/sonar/db/purge/PurgeMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/mapping/ProjectMappingsDaoTest.java [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTableTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72Test.java
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTableTest/empty.sql [new file with mode: 0644]

index 819fb4bd4728c546c2c337345e8ee7f397b35101..52c53b1dbb3a7ad9b064309320a7dff795ad126f 100644 (file)
@@ -120,6 +120,7 @@ public class ComputeEngineContainerImplTest {
         COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
           + 27 // level 1
           + 54 // content of DaoModule
+          + 55 // content of DaoModule
           + 3 // content of EsModule
           + 59 // content of CorePropertyDefinitions
           + 1 // StopFlagContainer
index cf7e558676925bfab02767e936ead9c8dbaf80f8..f33d7f5f575881f458843b42854f62be9c9731df 100644 (file)
@@ -87,6 +87,7 @@ public final class SqTables {
     "projects",
     "project_branches",
     "project_links",
+    "project_mappings",
     "project_measures",
     "project_qprofiles",
     "properties",
index 2ebc5a39f71ed2238b59f35ecf00378edbf2efe4..dd2e3a3317e9dab4eeee4ecc4e35bd785f0fdf91 100644 (file)
@@ -797,3 +797,15 @@ CREATE TABLE "ALM_APP_INSTALLS" (
 );
 CREATE UNIQUE INDEX "ALM_APP_INSTALLS_OWNER" ON "ALM_APP_INSTALLS" ("ALM_ID", "OWNER_ID");
 CREATE UNIQUE INDEX "ALM_APP_INSTALLS_INSTALL" ON "ALM_APP_INSTALLS" ("ALM_ID", "INSTALL_ID");
+
+CREATE TABLE "PROJECT_MAPPINGS" (
+  "UUID" VARCHAR(40) NOT NULL PRIMARY KEY,
+  "KEY_TYPE" VARCHAR(200) NOT NULL,
+  "KEE" VARCHAR(4000) NOT NULL,
+  "PROJECT_UUID" VARCHAR(40) NOT NULL,
+  "CREATED_AT" BIGINT NOT NULL,
+  CONSTRAINT "PK_PROJECT_MAPPINGS" PRIMARY KEY ("UUID")
+);
+CREATE UNIQUE INDEX "KEY_TYPE_KEE" ON "PROJECT_MAPPINGS" ("KEY_TYPE", "KEE");
+CREATE INDEX "PROJECT_UUID" ON "PROJECT_MAPPINGS" ("PROJECT_UUID");
+
index a7aeef31af0606fd26c26a1383e2b6bfc4248521..ff37086215acf3439eb7504897bffbc59eb9159b 100644 (file)
@@ -40,6 +40,7 @@ import org.sonar.db.event.EventDao;
 import org.sonar.db.issue.IssueChangeDao;
 import org.sonar.db.issue.IssueDao;
 import org.sonar.db.alm.AlmAppInstallDao;
+import org.sonar.db.mapping.ProjectMappingsDao;
 import org.sonar.db.measure.LiveMeasureDao;
 import org.sonar.db.measure.MeasureDao;
 import org.sonar.db.measure.custom.CustomMeasureDao;
@@ -117,6 +118,7 @@ public class DaoModule extends Module {
     PermissionTemplateDao.class,
     PluginDao.class,
     ProjectLinkDao.class,
+    ProjectMappingsDao.class,
     ProjectQgateAssociationDao.class,
     PropertiesDao.class,
     PurgeDao.class,
index 99732f30f3609a52225dc91b8c7692cc6d8d22f3..31d0465dcd48f8c66a7247dfedcba665091c1946 100644 (file)
@@ -38,6 +38,7 @@ import org.sonar.db.event.EventDao;
 import org.sonar.db.issue.IssueChangeDao;
 import org.sonar.db.issue.IssueDao;
 import org.sonar.db.alm.AlmAppInstallDao;
+import org.sonar.db.mapping.ProjectMappingsDao;
 import org.sonar.db.measure.LiveMeasureDao;
 import org.sonar.db.measure.MeasureDao;
 import org.sonar.db.measure.custom.CustomMeasureDao;
@@ -136,6 +137,7 @@ public class DbClient {
   private final LiveMeasureDao liveMeasureDao;
   private final WebhookDao webhookDao;
   private final WebhookDeliveryDao webhookDeliveryDao;
+  private final ProjectMappingsDao projectMappingsDao;
 
   public DbClient(Database database, MyBatis myBatis, DBSessions dbSessions, Dao... daos) {
     this.database = database;
@@ -200,6 +202,7 @@ public class DbClient {
     liveMeasureDao = getDao(map, LiveMeasureDao.class);
     webhookDao = getDao(map, WebhookDao.class);
     webhookDeliveryDao = getDao(map, WebhookDeliveryDao.class);
+    projectMappingsDao = getDao(map, ProjectMappingsDao.class);
   }
 
   public DbSession openSession(boolean batch) {
@@ -435,4 +438,8 @@ public class DbClient {
   public WebhookDeliveryDao webhookDeliveryDao() {
     return webhookDeliveryDao;
   }
+
+  public ProjectMappingsDao projectMappingsDao() {
+    return projectMappingsDao;
+  }
 }
index bbd97e3fcfa80e18d223915c2555bca8d9ab6f84..82967d9b2598332acc8c6c453596391c2b93d6eb 100644 (file)
@@ -63,6 +63,8 @@ import org.sonar.db.issue.IssueChangeMapper;
 import org.sonar.db.issue.IssueDto;
 import org.sonar.db.issue.IssueMapper;
 import org.sonar.db.issue.ShortBranchIssueDto;
+import org.sonar.db.mapping.ProjectMappingDto;
+import org.sonar.db.mapping.ProjectMappingsMapper;
 import org.sonar.db.measure.LiveMeasureMapper;
 import org.sonar.db.measure.MeasureDto;
 import org.sonar.db.measure.MeasureMapper;
@@ -176,6 +178,7 @@ public class MyBatis implements Startable {
     confBuilder.loadAlias("PermissionTemplateUser", PermissionTemplateUserDto.class);
     confBuilder.loadAlias("Plugin", PluginDto.class);
     confBuilder.loadAlias("ProjectQgateAssociation", ProjectQgateAssociationDto.class);
+    confBuilder.loadAlias("ProjectMapping", ProjectMappingDto.class);
     confBuilder.loadAlias("PurgeableAnalysis", PurgeableAnalysisDto.class);
     confBuilder.loadAlias("QualityGateCondition", QualityGateConditionDto.class);
     confBuilder.loadAlias("QualityGate", QualityGateDto.class);
@@ -232,6 +235,7 @@ public class MyBatis implements Startable {
       PermissionTemplateCharacteristicMapper.class,
       PermissionTemplateMapper.class,
       PluginMapper.class,
+      ProjectMappingsMapper.class,
       ProjectQgateAssociationMapper.class,
       PropertiesMapper.class,
       PurgeMapper.class,
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingDto.java
new file mode 100644 (file)
index 0000000..7c1bd12
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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.mapping;
+
+/**
+ * Generic purpose way to attach data to a project. For example to store the identifier
+ * of the project in a remote system (ALM).
+ * The pair (keyType, key) is unique.
+ * Data is automatically purged when project is deleted.
+ */
+public final class ProjectMappingDto {
+  private String uuid;
+  private String keyType;
+  private String key;
+  private String projectUuid;
+
+  public String getUuid() {
+    return uuid;
+  }
+
+  public void setUuid(String uuid) {
+    this.uuid = uuid;
+  }
+
+  public String getKeyType() {
+    return keyType;
+  }
+
+  public void setKeyType(String keyType) {
+    this.keyType = keyType;
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  public void setKey(String key) {
+    this.key = key;
+  }
+
+  public String getProjectUuid() {
+    return projectUuid;
+  }
+
+  public void setProjectUuid(String projectUuid) {
+    this.projectUuid = projectUuid;
+  }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingsDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingsDao.java
new file mode 100644 (file)
index 0000000..bf9a728
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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.mapping;
+
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.apache.commons.lang.StringUtils.isNotEmpty;
+
+public class ProjectMappingsDao implements Dao {
+
+  private final System2 system2;
+  private final UuidFactory uuidFactory;
+
+  public ProjectMappingsDao(System2 system2, UuidFactory uuidFactory) {
+    this.system2 = system2;
+    this.uuidFactory = uuidFactory;
+  }
+
+  public void put(DbSession dbSession, String keyType, String key, String projectUuid) {
+    checkKeyType(keyType);
+    checkKey(key);
+    checkArgument(isNotEmpty(projectUuid), "projectUuid can't be null nor empty");
+
+    ProjectMappingsMapper mapper = getMapper(dbSession);
+    mapper.deleteByKey(keyType, key);
+    long now = system2.now();
+    mapper.put(uuidFactory.create(), keyType, key, projectUuid, now);
+  }
+
+  public Optional<ProjectMappingDto> get(DbSession dbSession, String keyType, String key) {
+    checkKeyType(keyType);
+    checkKey(key);
+
+    ProjectMappingsMapper mapper = getMapper(dbSession);
+    return Optional.ofNullable(mapper.selectByKey(keyType, key));
+  }
+
+  public void clear(DbSession dbSession, String keyType, String key) {
+    checkKeyType(keyType);
+    checkKey(key);
+    ProjectMappingsMapper mapper = getMapper(dbSession);
+    mapper.deleteByKey(keyType, key);
+  }
+
+  private static void checkKeyType(@Nullable String keyType) {
+    checkArgument(keyType != null && !keyType.isEmpty(), "key type can't be null nor empty");
+  }
+
+  private static void checkKey(@Nullable String key) {
+    checkArgument(key != null && !key.isEmpty(), "key can't be null nor empty");
+  }
+
+  private static ProjectMappingsMapper getMapper(DbSession dbSession) {
+    return dbSession.getMapper(ProjectMappingsMapper.class);
+  }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingsMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/mapping/ProjectMappingsMapper.java
new file mode 100644 (file)
index 0000000..c59db39
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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.mapping;
+
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.annotations.Param;
+
+public interface ProjectMappingsMapper {
+
+  @CheckForNull
+  ProjectMappingDto selectByKey(@Param("keyType") String keyType, @Param("key") String key);
+
+  void put(@Param("uuid") String uuid, @Param("keyType") String keyType, @Param("key") String key, @Param("projectUuid") String projectUuid, @Param("createdAt") long createdAt);
+
+  void deleteByKey(@Param("keyType") String keyType, @Param("key") String key);
+}
index 4e346364ee94e6aaf5f2e5c6660e4df5bc6dbbfd..bab365e9f53bd3730e26e44e8289be8b57650971 100644 (file)
@@ -291,6 +291,13 @@ class PurgeCommands {
     profiler.stop();
   }
 
+  void deleteProjectMappings(String rootUuid) {
+    profiler.start("deleteProjectMappings (project_mappings)");
+    purgeMapper.deleteProjectMappingsByProjectUuid(rootUuid);
+    session.commit();
+    profiler.stop();
+  }
+
   void deleteBranch(String rootUuid) {
     profiler.start("deleteBranch (project_branches)");
     purgeMapper.deleteBranchByUuid(rootUuid);
index 0d6a2d09d91b86fbf419ec38378e4a8d1762d549..1270cdde24127e0c5c239e933d1bb503272ca798 100644 (file)
@@ -202,6 +202,7 @@ public class PurgeDao implements Dao {
     commands.deleteCeActivity(rootUuid);
     commands.deleteCeQueue(rootUuid);
     commands.deleteWebhookDeliveries(rootUuid);
+    commands.deleteProjectMappings(rootUuid);
     commands.deleteBranch(rootUuid);
     commands.deleteLiveMeasures(rootUuid);
   }
index a9dec7bb5b213f4df6cb57a586df0b3cfa675017..bbf0be4b2d25c2dba2c057b420712386983c1eea 100644 (file)
@@ -108,6 +108,8 @@ public interface PurgeMapper {
 
   void deleteWebhookDeliveriesByProjectUuid(@Param("projectUuid") String projectUuid);
 
+  void deleteProjectMappingsByProjectUuid(@Param("projectUuid") String projectUuid);
+
   void deleteBranchByUuid(@Param("uuid") String uuid);
 
   void deleteLiveMeasuresByProjectUuid(@Param("projectUuid") String projectUuid);
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/mapping/ProjectMappingsMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/mapping/ProjectMappingsMapper.xml
new file mode 100644 (file)
index 0000000..551eda2
--- /dev/null
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.mapping.ProjectMappingsMapper">
+
+  <select id="selectByKey" parameterType="Map" resultType="ProjectMapping">
+    select
+      uuid as "uuid",
+      key_type as "key_type",
+      kee as "key",
+      project_uuid as projectUuid
+    from
+      project_mappings
+    where
+      key_type = #{keyType, jdbcType=VARCHAR}
+      and kee = #{key, jdbcType=VARCHAR}
+  </select>
+
+  <insert id="put" parameterType="Map" useGeneratedKeys="false">
+    INSERT INTO project_mappings
+    (
+      uuid,
+      key_type,
+      kee,
+      project_uuid,
+      created_at
+    )
+    VALUES (
+      #{uuid,jdbcType=VARCHAR},
+      #{keyType,jdbcType=VARCHAR},
+      #{key,jdbcType=VARCHAR},
+      #{projectUuid,jdbcType=VARCHAR},
+      #{createdAt,jdbcType=BIGINT}
+    )
+  </insert>
+
+  <delete id="deleteByKey" parameterType="Map">
+    delete from project_mappings
+    where
+      key_type=#{keyType,jdbcType=VARCHAR}
+      and kee=#{key,jdbcType=VARCHAR}
+  </delete>
+
+
+</mapper>
index b8af25a75987f681a6ae6096615c0f9df5e22552..b1657baf79b0a21d7ab22de2be7b8c479dbe5fae 100644 (file)
     delete from webhook_deliveries where component_uuid=#{projectUuid,jdbcType=VARCHAR}
   </delete>
 
+  <delete id="deleteProjectMappingsByProjectUuid">
+    delete from project_mappings where project_uuid=#{projectUuid,jdbcType=VARCHAR}
+  </delete>
+
   <delete id="deleteBranchByUuid">
     delete from project_branches where uuid=#{uuid,jdbcType=VARCHAR}
   </delete>
index 213eeb67ccbbe79014a3df6f693d509e9e25e91c..60d417966bd0bb6cf713800dcd084fb89482607e 100644 (file)
@@ -30,6 +30,6 @@ public class DaoModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new DaoModule().configure(container);
-    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 54);
+    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 55);
   }
 }
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/mapping/ProjectMappingsDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/mapping/ProjectMappingsDaoTest.java
new file mode 100644 (file)
index 0000000..eeca63b
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * 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.mapping;
+
+import java.util.Map;
+import java.util.Objects;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.assertj.core.api.AbstractAssert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.SequenceUuidFactory;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ProjectMappingsDaoTest {
+
+  private static final String EMPTY_STRING = "";
+  private static final String A_KEY_TYPE = "a_key_type";
+  private static final String A_KEY = "a_key";
+  private static final String ANOTHER_KEY = "another_key";
+  private static final long DATE = 1_600_000_000_000L;
+  private static final String PROJECT_UUID = "123456789";
+  private static final String OTHER_PROJECT_UUID = "987654321";
+
+  private System2 system2 = mock(System2.class);
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public DbTester dbTester = DbTester.create(system2);
+
+  private DbSession dbSession = dbTester.getSession();
+
+  private ProjectMappingsDao underTest = new ProjectMappingsDao(system2, new SequenceUuidFactory());
+
+  @Test
+  public void put_throws_IAE_if_key_type_is_null() {
+    expectKeyTypeNullOrEmptyIAE();
+
+    underTest.put(dbSession, null, A_KEY, PROJECT_UUID);
+  }
+
+  @Test
+  public void put_throws_IAE_if_key_is_null() {
+    expectKeyNullOrEmptyIAE();
+
+    underTest.put(dbSession, A_KEY_TYPE, null, PROJECT_UUID);
+  }
+
+  @Test
+  public void put_throws_IAE_if_key_is_empty() {
+    expectKeyNullOrEmptyIAE();
+
+    underTest.put(dbSession, A_KEY_TYPE, EMPTY_STRING, PROJECT_UUID);
+  }
+
+  @Test
+  public void save_throws_IAE_if_project_uuid_is_null() {
+    expectValueNullOrEmptyIAE();
+
+    underTest.put(dbSession, A_KEY_TYPE, A_KEY, null);
+  }
+
+  @Test
+  public void put_throws_IAE_if_project_uuid_is_empty() {
+    expectValueNullOrEmptyIAE();
+
+    underTest.put(dbSession, A_KEY_TYPE, A_KEY, EMPTY_STRING);
+  }
+
+  @Test
+  public void put() {
+    when(system2.now()).thenReturn(DATE);
+    underTest.put(dbSession, A_KEY_TYPE, A_KEY, PROJECT_UUID);
+
+    assertThatProjectMapping(A_KEY_TYPE, A_KEY)
+      .hasProjectUuid(PROJECT_UUID)
+      .hasCreatedAt(DATE);
+  }
+
+  @Test
+  public void clear() {
+    when(system2.now()).thenReturn(DATE);
+    underTest.put(dbSession, A_KEY_TYPE, A_KEY, PROJECT_UUID);
+
+    assertThatProjectMapping(A_KEY_TYPE, A_KEY)
+        .hasProjectUuid(PROJECT_UUID)
+        .hasCreatedAt(DATE);
+
+    underTest.clear(dbSession, A_KEY_TYPE, A_KEY);
+
+    assertThat(underTest.get(dbSession, A_KEY_TYPE, A_KEY)).isEmpty();
+  }
+
+  @Test
+  public void putMultiple() {
+    when(system2.now()).thenReturn(DATE);
+    underTest.put(dbSession, A_KEY_TYPE, A_KEY, PROJECT_UUID);
+    underTest.put(dbSession, A_KEY_TYPE, ANOTHER_KEY, OTHER_PROJECT_UUID);
+
+    assertThatProjectMapping(A_KEY_TYPE, A_KEY)
+        .hasProjectUuid(PROJECT_UUID)
+        .hasCreatedAt(DATE);
+
+    assertThatProjectMapping(A_KEY_TYPE, ANOTHER_KEY)
+        .hasProjectUuid(OTHER_PROJECT_UUID)
+        .hasCreatedAt(DATE);
+  }
+
+  @Test
+  public void get_throws_IAE_when_key_type_is_null() {
+    expectKeyTypeNullOrEmptyIAE();
+
+    underTest.get(dbSession, null, A_KEY);
+  }
+
+  @Test
+  public void get_throws_IAE_when_key_is_null() {
+    expectKeyNullOrEmptyIAE();
+
+    underTest.get(dbSession, A_KEY_TYPE, null);
+  }
+
+  @Test
+  public void get_throws_IAE_when_key_is_empty() {
+    expectKeyNullOrEmptyIAE();
+
+    underTest.get(dbSession, A_KEY_TYPE, EMPTY_STRING);
+  }
+
+  @Test
+  public void get_returns_empty_optional_when_mapping_does_not_exist_in_DB() {
+    assertThat(underTest.get(dbSession, A_KEY_TYPE, A_KEY)).isEmpty();
+  }
+
+  @Test
+  public void get_returns_project_uuid_when_mapping_has_project_uuid_stored() {
+    underTest.put(dbSession, A_KEY_TYPE, A_KEY, PROJECT_UUID);
+
+    assertThat(underTest.get(dbSession, A_KEY_TYPE, A_KEY).get().getProjectUuid()).isEqualTo(PROJECT_UUID);
+  }
+
+  private void expectKeyTypeNullOrEmptyIAE() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("key type can't be null nor empty");
+  }
+
+  private void expectKeyNullOrEmptyIAE() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("key can't be null nor empty");
+  }
+
+  private void expectValueNullOrEmptyIAE() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("projectUuid can't be null nor empty");
+  }
+
+  private ProjectMappingAssert assertThatProjectMapping(String keyType, String key) {
+    return new ProjectMappingAssert(dbTester, dbSession, keyType, key);
+  }
+
+  private static class ProjectMappingAssert extends AbstractAssert<ProjectMappingAssert, ProjectMapping> {
+
+    private ProjectMappingAssert(DbTester dbTester, DbSession dbSession, String internalMappingKeyType, String internalMappingKey) {
+      super(asProjectMapping(dbTester, dbSession, internalMappingKeyType, internalMappingKey), ProjectMappingAssert.class);
+    }
+
+    private static ProjectMapping asProjectMapping(DbTester dbTester, DbSession dbSession, String projectMappingKeyType, String projectMappingKey) {
+      Map<String, Object> row = dbTester.selectFirst(
+        dbSession,
+        "select" +
+          " project_uuid as \"projectUuid\", created_at as \"createdAt\"" +
+          " from project_mappings" +
+          " where key_type='"+projectMappingKeyType+"' and kee='" + projectMappingKey + "'");
+      return new ProjectMapping(
+        (String) row.get("projectUuid"),
+        (Long) row.get("createdAt"));
+    }
+
+    public void doesNotExist() {
+      isNull();
+    }
+
+
+    public ProjectMappingAssert hasProjectUuid(String expected) {
+      isNotNull();
+
+      if (!Objects.equals(actual.getProjectUuid(), expected)) {
+        failWithMessage("Expected Internal mapping to have column VALUE to be <%s> but was <%s>", true, actual.getProjectUuid());
+      }
+      return this;
+    }
+
+    public ProjectMappingAssert hasCreatedAt(long expected) {
+      isNotNull();
+
+      if (!Objects.equals(actual.getCreatedAt(), expected)) {
+        failWithMessage("Expected Internal mapping to have column CREATED_AT to be <%s> but was <%s>", expected, actual.getCreatedAt());
+      }
+
+      return this;
+    }
+
+  }
+
+  private static final class ProjectMapping {
+    private final String projectUuid;
+    private final Long createdAt;
+
+    public ProjectMapping(@Nullable String projectUuid, @Nullable Long createdAt) {
+      this.projectUuid = projectUuid;
+      this.createdAt = createdAt;
+    }
+
+    @CheckForNull
+    public String getProjectUuid() {
+      return projectUuid;
+    }
+
+    @CheckForNull
+    public Long getCreatedAt() {
+      return createdAt;
+    }
+  }
+}
index 6ef07206f31d6c791725b19eaa1058f19f6af568..c2ba89ca811eed59521900eaccfa9432842d1adb 100644 (file)
@@ -532,6 +532,18 @@ public class PurgeDaoTest {
     assertThat(selectAllDeliveryUuids(dbTester, dbSession)).containsOnly("D2");
   }
 
+  @Test
+  public void deleteProject_deletes_project_mappings() {
+    ComponentDto project = dbTester.components().insertPublicProject();
+    dbClient.projectMappingsDao().put(dbSession, "a.key.type", "a.key", project.uuid());
+    dbClient.projectMappingsDao().put(dbSession, "a.key.type", "another.key", "D2");
+
+    underTest.deleteProject(dbSession, project.uuid());
+
+    assertThat(dbClient.projectMappingsDao().get(dbSession, "a.key.type", "a.key")).isEmpty();
+    assertThat(dbClient.projectMappingsDao().get(dbSession, "a.key.type", "another.key")).isNotEmpty();
+  }
+
   @Test
   public void deleteNonRootComponents_has_no_effect_when_parameter_is_empty() {
     DbSession dbSession = mock(DbSession.class);
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTable.java
new file mode 100644 (file)
index 0000000..0417cb1
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * 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.platform.db.migration.version.v72;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.server.platform.db.migration.def.BigIntegerColumnDef;
+import org.sonar.server.platform.db.migration.def.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder;
+import org.sonar.server.platform.db.migration.sql.CreateTableBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.BigIntegerColumnDef.newBigIntegerColumnDefBuilder;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.MAX_SIZE;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class CreateProjectMappingsTable extends DdlChange {
+
+  private static final String TABLE_NAME = "project_mappings";
+
+  private static final VarcharColumnDef UUID_COLUMN = VarcharColumnDef.newVarcharColumnDefBuilder()
+    .setColumnName("uuid")
+    .setIsNullable(false)
+    .setLimit(VarcharColumnDef.UUID_SIZE)
+    .build();
+  private static final VarcharColumnDef KEY_TYPE_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("key_type")
+    .setIsNullable(false)
+    .setLimit(200)
+    .build();
+  private static final VarcharColumnDef KEE_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("kee")
+    .setIsNullable(false)
+    .setLimit(MAX_SIZE)
+    .build();
+  private static final VarcharColumnDef PROJECT_UUID = VarcharColumnDef.newVarcharColumnDefBuilder()
+    .setColumnName("project_uuid")
+    .setIsNullable(false)
+    .setLimit(VarcharColumnDef.UUID_SIZE)
+    .build();
+  private static final BigIntegerColumnDef CREATED_AT_COLUMN = newBigIntegerColumnDefBuilder()
+    .setColumnName("created_at")
+    .setIsNullable(false)
+    .build();
+
+  public CreateProjectMappingsTable(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+
+    if (!tableExists()) {
+      context.execute(new CreateTableBuilder(getDialect(), TABLE_NAME)
+        .addPkColumn(UUID_COLUMN)
+        .addColumn(KEY_TYPE_COLUMN)
+        .addColumn(KEE_COLUMN)
+        .addColumn(PROJECT_UUID)
+        .addColumn(CREATED_AT_COLUMN)
+        .build());
+
+      context.execute(new CreateIndexBuilder(getDialect())
+        .addColumn(KEY_TYPE_COLUMN)
+        .addColumn(KEE_COLUMN)
+        .setUnique(true)
+        .setTable(TABLE_NAME)
+        .setName("key_type_kee")
+        .build());
+
+      context.execute(new CreateIndexBuilder(getDialect())
+        .addColumn(PROJECT_UUID)
+        .setUnique(false)
+        .setTable(TABLE_NAME)
+        .setName("project_uuid")
+        .build());
+    }
+  }
+
+  private boolean tableExists() throws SQLException {
+    try (Connection connection = getDatabase().getDataSource().getConnection()) {
+      return DatabaseUtils.tableExists(TABLE_NAME, connection);
+    }
+  }
+}
index 803381a42343289553b5e4ee71c967742adaaa54..8400eb1a14e4b9fda69c7c54782d9d10154615e5 100644 (file)
@@ -33,6 +33,7 @@ public class DbVersion72 implements DbVersion {
       .add(2103, "Add isExternal boolean to rules", AddRuleExternal.class)
       .add(2104, "Create ALM_APP_INSTALLS table", CreateAlmAppInstallsTable.class)
       .add(2105, "Add LINE_HASHES_VERSION to table FILE_SOURCES", AddLineHashesVersionToFileSources.class)
+      .add(2106, "Create PROJECT_MAPPINGS table", CreateProjectMappingsTable.class)
     ;
   }
 }
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTableTest.java
new file mode 100644 (file)
index 0000000..19f3d6b
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * 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.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+import static java.sql.Types.VARCHAR;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CreateProjectMappingsTableTest {
+
+  private static final String TABLE = "project_mappings";
+
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(CreateProjectMappingsTableTest.class, "empty.sql");
+
+  private CreateProjectMappingsTable underTest = new CreateProjectMappingsTable(db.database());
+
+  @Test
+  public void creates_table_on_empty_db() throws SQLException {
+    underTest.execute();
+
+    checkTable();
+  }
+
+  @Test
+  public void migration_is_reentrant() throws SQLException {
+    underTest.execute();
+    underTest.execute();
+
+    checkTable();
+  }
+
+  private void checkTable() {
+    db.assertColumnDefinition(TABLE, "uuid", Types.VARCHAR, 40, false);
+    db.assertColumnDefinition(TABLE, "key_type", VARCHAR, 200, false);
+    db.assertColumnDefinition(TABLE, "kee", VARCHAR, 4000, false);
+    db.assertColumnDefinition(TABLE, "project_uuid", Types.VARCHAR, 40, false);
+    db.assertColumnDefinition(TABLE, "created_at", Types.BIGINT, 20, false);
+
+    db.assertUniqueIndex(TABLE, "key_type_kee", "key_type", "kee");
+    db.assertIndex(TABLE, "project_uuid", "project_uuid");
+    assertThat(db.countRowsOfTable(TABLE)).isEqualTo(0);
+  }
+}
index eb251a545de958361cf91980224e3e47931bf338..1cb135035acc51bc971b1299221581731d52f69a 100644 (file)
@@ -34,7 +34,7 @@ public class DbVersion72Test {
 
   @Test
   public void verify_migration_count() {
-    verifyMigrationCount(underTest, 6);
+    verifyMigrationCount(underTest, 7);
   }
 
 }
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTableTest/empty.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/CreateProjectMappingsTableTest/empty.sql
new file mode 100644 (file)
index 0000000..e69de29