]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10515 ALM App Install table + DAO
authorJulien HENRY <julien.henry@sonarsource.com>
Thu, 26 Apr 2018 14:59:26 +0000 (16:59 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 26 Apr 2018 18:20:53 +0000 (20:20 +0200)
* Add migration to create table alm_app_installs
* Add dao for alm_app_installs

17 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/alm/AlmAppInstallDao.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/alm/AlmAppInstallMapper.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/alm/package-info.java [new file with mode: 0644]
server/sonar-db-dao/src/main/resources/org/sonar/db/alm/AlmAppInstallMapper.xml [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/alm/AlmAppInstallDaoTest.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/CreateAlmAppInstallsTable.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/CreateAlmAppInstallsTableTest.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/CreateAlmAppInstallsTableTest/empty.sql [new file with mode: 0644]

index 7afde4f1d069ef1a1554b05a7a43bfd4e68b18f1..d2ff2acdd1e5de994c840a49f75fd26c1c1a0541 100644 (file)
@@ -119,7 +119,7 @@ public class ComputeEngineContainerImplTest {
       assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize(
         COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
           + 26 // level 1
-          + 53 // content of DaoModule
+          + 54 // content of DaoModule
           + 3 // content of EsModule
           + 59 // content of CorePropertyDefinitions
           + 1 // StopFlagContainer
index 1843f327d7623ee3eb6cbb8731c1994e4fb9be44..cf7e558676925bfab02767e936ead9c8dbaf80f8 100644 (file)
@@ -52,6 +52,7 @@ public final class SqTables {
   public static final Set<String> TABLES = unmodifiableSet(new HashSet<>(asList(
     "active_rules",
     "active_rule_parameters",
+    "alm_app_installs",
     "analysis_properties",
     "ce_activity",
     "ce_queue",
index 35a9732f71aa22d6dd3123159f97082afe63db29..fc72c134c975ee4416e8cb014eabfdddb5c010ec 100644 (file)
@@ -790,3 +790,15 @@ CREATE UNIQUE INDEX "PK_WEBHOOK_DELIVERIES" ON "WEBHOOK_DELIVERIES" ("UUID");
 CREATE INDEX "COMPONENT_UUID" ON "WEBHOOK_DELIVERIES" ("COMPONENT_UUID");
 CREATE INDEX "CE_TASK_UUID" ON "WEBHOOK_DELIVERIES" ("CE_TASK_UUID");
 CREATE INDEX "ANALYSIS_UUID" ON "WEBHOOK_DELIVERIES" ("ANALYSIS_UUID");
+
+CREATE TABLE "ALM_APP_INSTALLS" (
+  "UUID" VARCHAR(40) NOT NULL PRIMARY KEY,
+  "ALM_ID" VARCHAR(40) NOT NULL,
+  "OWNER_ID" VARCHAR(4000) NOT NULL,
+  "INSTALL_ID" VARCHAR(4000) NOT NULL,
+  "CREATED_AT" BIGINT NOT NULL,
+  "UPDATED_AT" BIGINT NOT NULL,
+  CONSTRAINT "PK_ALM_APP_INSTALLS" PRIMARY KEY ("UUID")
+);
+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");
index eeb23ef31adb51d289f197f7ab2bb287c72afd1c..a7aeef31af0606fd26c26a1383e2b6bfc4248521 100644 (file)
@@ -39,6 +39,7 @@ import org.sonar.db.es.EsQueueDao;
 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.measure.LiveMeasureDao;
 import org.sonar.db.measure.MeasureDao;
 import org.sonar.db.measure.custom.CustomMeasureDao;
@@ -102,6 +103,7 @@ public class DaoModule extends Module {
     GroupDao.class,
     GroupMembershipDao.class,
     GroupPermissionDao.class,
+    AlmAppInstallDao.class,
     InternalPropertiesDao.class,
     IssueChangeDao.class,
     IssueDao.class,
index e7b50e95bf8addd625d41565bfd88da1c92d19cb..99732f30f3609a52225dc91b8c7692cc6d8d22f3 100644 (file)
@@ -37,6 +37,7 @@ import org.sonar.db.es.EsQueueDao;
 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.measure.LiveMeasureDao;
 import org.sonar.db.measure.MeasureDao;
 import org.sonar.db.measure.custom.CustomMeasureDao;
@@ -87,6 +88,7 @@ public class DbClient {
   private final OrganizationMemberDao organizationMemberDao;
   private final QualityProfileDao qualityProfileDao;
   private final PropertiesDao propertiesDao;
+  private final AlmAppInstallDao almAppInstallDao;
   private final InternalPropertiesDao internalPropertiesDao;
   private final SnapshotDao snapshotDao;
   private final ComponentDao componentDao;
@@ -144,6 +146,7 @@ public class DbClient {
     for (Dao dao : daos) {
       map.put(dao.getClass(), dao);
     }
+    almAppInstallDao = getDao(map, AlmAppInstallDao.class);
     schemaMigrationDao = getDao(map, SchemaMigrationDao.class);
     authorizationDao = getDao(map, AuthorizationDao.class);
     organizationDao = getDao(map, OrganizationDao.class);
@@ -207,6 +210,10 @@ public class DbClient {
     return database;
   }
 
+  public AlmAppInstallDao almAppInstallDao() {
+    return almAppInstallDao;
+  }
+
   public SchemaMigrationDao schemaMigrationDao() {
     return schemaMigrationDao;
   }
@@ -422,7 +429,7 @@ public class DbClient {
   }
 
   public WebhookDao webhookDao() {
-    return webhookDao ;
+    return webhookDao;
   }
 
   public WebhookDeliveryDao webhookDeliveryDao() {
index 269d7aef70b0c0f2505443fd8ecaa55c73845171..bbd97e3fcfa80e18d223915c2555bca8d9ab6f84 100644 (file)
@@ -30,6 +30,7 @@ import org.apache.ibatis.session.SqlSessionFactory;
 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
 import org.apache.ibatis.session.TransactionIsolationLevel;
 import org.sonar.api.Startable;
+import org.sonar.db.alm.AlmAppInstallMapper;
 import org.sonar.db.ce.CeActivityMapper;
 import org.sonar.db.ce.CeQueueMapper;
 import org.sonar.db.ce.CeScannerContextMapper;
@@ -198,6 +199,7 @@ public class MyBatis implements Startable {
     Class<?>[] mappers = {
       ActiveRuleMapper.class,
       AnalysisPropertiesMapper.class,
+      AlmAppInstallMapper.class,
       AuthorizationMapper.class,
       BranchMapper.class,
       CeActivityMapper.class,
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/AlmAppInstallDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/AlmAppInstallDao.java
new file mode 100644 (file)
index 0000000..03bc10e
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * 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.alm;
+
+import java.util.Locale;
+import java.util.Objects;
+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;
+
+/**
+ * Store instances of installed app in external ALM like GitHub or Bitbucket Cloud.
+ */
+public class AlmAppInstallDao implements Dao {
+
+  public enum ALM {
+    BITBUCKETCLOUD,
+    GITHUB;
+
+    String getId() {
+      return this.name().toLowerCase(Locale.ENGLISH);
+    }
+  }
+
+  private final System2 system2;
+  private final UuidFactory uuidFactory;
+
+  public AlmAppInstallDao(System2 system2, UuidFactory uuidFactory) {
+    this.system2 = system2;
+    this.uuidFactory = uuidFactory;
+  }
+
+  /**
+   * @param alm Unique identifier of the ALM, like 'bitbucketcloud' or 'github', can't be null
+   * @param ownerId ALM specific identifier of the owner of the app, like team or user uuid for Bitbucket Cloud or organization id for Github, can't be null
+   * @param installId ALM specific identifier of the app installation, can't be null
+   */
+  public void insertOrUpdate(DbSession dbSession, ALM alm, String ownerId, String installId) {
+    checkAlm(alm);
+    checkOwnerId(ownerId);
+    checkArgument(isNotEmpty(installId), "installId can't be null nor empty");
+
+    AlmAppInstallMapper mapper = getMapper(dbSession);
+    long now = system2.now();
+
+    if (mapper.update(alm.getId(), ownerId, installId, now) == 0) {
+      mapper.insert(uuidFactory.create(), alm.getId(), ownerId, installId, now);
+    }
+  }
+
+  public Optional<String> getInstallId(DbSession dbSession, ALM alm, String ownerId) {
+    checkAlm(alm);
+    checkOwnerId(ownerId);
+
+    AlmAppInstallMapper mapper = getMapper(dbSession);
+    return Optional.ofNullable(mapper.selectInstallId(alm.getId(), ownerId));
+  }
+
+  private static void checkAlm(@Nullable ALM alm) {
+    Objects.requireNonNull(alm, "alm can't be null");
+  }
+
+  private static void checkOwnerId(@Nullable String ownerId) {
+    checkArgument(isNotEmpty(ownerId), "ownerId can't be null nor empty");
+  }
+
+  private static AlmAppInstallMapper getMapper(DbSession dbSession) {
+    return dbSession.getMapper(AlmAppInstallMapper.class);
+  }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/AlmAppInstallMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/AlmAppInstallMapper.java
new file mode 100644 (file)
index 0000000..300890d
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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.alm;
+
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.annotations.Param;
+
+public interface AlmAppInstallMapper {
+
+  @CheckForNull
+  String selectInstallId(@Param("almId") String almId, @Param("ownerId") String ownerId);
+
+  void insert(@Param("uuid") String uuid, @Param("almId") String almId, @Param("ownerId") String ownerId, @Param("installId") String installId, @Param("now") long now);
+
+  int update(@Param("almId") String almId, @Param("ownerId") String ownerId, @Param("installId") String installId, @Param("now") long now);
+
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/package-info.java b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/package-info.java
new file mode 100644 (file)
index 0000000..52a5bf5
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.db.alm;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/alm/AlmAppInstallMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/alm/AlmAppInstallMapper.xml
new file mode 100644 (file)
index 0000000..d09f04d
--- /dev/null
@@ -0,0 +1,53 @@
+<?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.alm.AlmAppInstallMapper">
+
+  <select id="selectInstallId" parameterType="Map" resultType="String">
+    select
+      install_id as installId
+    from
+      alm_app_installs
+    where
+      alm_id = #{almId, jdbcType=VARCHAR}
+      and owner_id = #{ownerId, jdbcType=VARCHAR}
+  </select>
+
+  <insert id="insert" parameterType="Map" useGeneratedKeys="false">
+    INSERT INTO alm_app_installs
+    (
+      uuid,
+      alm_id,
+      owner_id,
+      install_id,
+      created_at,
+      updated_at
+    )
+    VALUES (
+      #{uuid, jdbcType=VARCHAR},
+      #{almId, jdbcType=VARCHAR},
+      #{ownerId, jdbcType=VARCHAR},
+      #{installId, jdbcType=VARCHAR},
+      #{now, jdbcType=BIGINT},
+      #{now, jdbcType=BIGINT}
+    )
+  </insert>
+
+  <update id="update" parameterType="map">
+    update alm_app_installs set
+      install_id = #{installId, jdbcType=VARCHAR},
+      updated_at = #{now, jdbcType=BIGINT}
+    where
+      alm_id = #{almId, jdbcType=VARCHAR}
+      and owner_id = #{ownerId, jdbcType=VARCHAR}
+  </update>
+
+  <delete id="deleteByOwnerId" parameterType="Map">
+    delete from alm_app_installs
+    where
+      alm_id = #{almId, jdbcType=VARCHAR}
+      and owner_id = #{ownerId, jdbcType=VARCHAR}
+  </delete>
+
+
+</mapper>
index 89f2e067b9af4f7ae7e1d5fc969fb69d42a5f814..213eeb67ccbbe79014a3df6f693d509e9e25e91c 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 + 53);
+    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 54);
   }
 }
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/alm/AlmAppInstallDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/alm/AlmAppInstallDaoTest.java
new file mode 100644 (file)
index 0000000..c9df59c
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+ * 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.alm;
+
+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.UuidFactory;
+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;
+import static org.sonar.db.alm.AlmAppInstallDao.ALM.GITHUB;
+
+public class AlmAppInstallDaoTest {
+
+  private static final String A_UUID = "abcde1234";
+  private static final String A_UUID_2 = "xyz789";
+  private static final String EMPTY_STRING = "";
+  private static final String A_OWNER = "my_org_id";
+  private static final String ANOTHER_OWNER = "another_org";
+  private static final long DATE = 1_600_000_000_000L;
+  private static final long DATE_LATER = 1_700_000_000_000L;
+  private static final String AN_INSTALL = "some install id";
+  private static final String OTHER_INSTALL = "other install id";
+
+  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 UuidFactory uuidFactory = mock(UuidFactory.class);
+  private AlmAppInstallDao underTest = new AlmAppInstallDao(system2, uuidFactory);
+
+  @Test
+  public void insert_throws_NPE_if_alm_is_null() {
+    expectAlmNPE();
+
+    underTest.insertOrUpdate(dbSession, null, A_OWNER, AN_INSTALL);
+  }
+
+  @Test
+  public void insert_throws_IAE_if_owner_id_is_null() {
+    expectOwnerIdNullOrEmptyIAE();
+
+    underTest.insertOrUpdate(dbSession, GITHUB, null, AN_INSTALL);
+  }
+
+  @Test
+  public void insert_throws_IAE_if_owner_id_is_empty() {
+    expectOwnerIdNullOrEmptyIAE();
+
+    underTest.insertOrUpdate(dbSession, GITHUB, EMPTY_STRING, AN_INSTALL);
+  }
+
+  @Test
+  public void insert_throws_IAE_if_install_id_is_null() {
+    expectInstallIdNullOrEmptyIAE();
+
+    underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER, null);
+  }
+
+  @Test
+  public void insert_throws_IAE_if_install_id_is_empty() {
+    expectInstallIdNullOrEmptyIAE();
+
+    underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER, EMPTY_STRING);
+  }
+
+  @Test
+  public void insert() {
+    when(uuidFactory.create()).thenReturn(A_UUID);
+    when(system2.now()).thenReturn(DATE);
+    underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER, AN_INSTALL);
+
+    assertThatAlmAppInstall(GITHUB, A_OWNER)
+      .hasInstallId(AN_INSTALL)
+      .hasCreatedAt(DATE)
+      .hasUpdatedAt(DATE);
+  }
+
+  @Test
+  public void update() {
+    when(uuidFactory.create()).thenReturn(A_UUID);
+    when(system2.now()).thenReturn(DATE);
+    underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER, AN_INSTALL);
+
+    when(system2.now()).thenReturn(DATE_LATER);
+    underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER, OTHER_INSTALL);
+
+    assertThatAlmAppInstall(GITHUB, A_OWNER)
+      .hasInstallId(OTHER_INSTALL)
+      .hasCreatedAt(DATE)
+      .hasUpdatedAt(DATE_LATER);
+  }
+
+  @Test
+  public void putMultiple() {
+    when(system2.now()).thenReturn(DATE);
+    when(uuidFactory.create())
+      .thenReturn(A_UUID)
+      .thenReturn(A_UUID_2);
+    underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER, AN_INSTALL);
+    underTest.insertOrUpdate(dbSession, GITHUB, ANOTHER_OWNER, OTHER_INSTALL);
+
+    assertThatAlmAppInstall(GITHUB, A_OWNER)
+      .hasInstallId(AN_INSTALL)
+      .hasCreatedAt(DATE)
+      .hasUpdatedAt(DATE);
+
+    assertThatAlmAppInstall(GITHUB, ANOTHER_OWNER)
+      .hasInstallId(OTHER_INSTALL)
+      .hasCreatedAt(DATE)
+      .hasUpdatedAt(DATE);
+  }
+
+  @Test
+  public void getInstallId_throws_NPE_when_alm_is_null() {
+    expectAlmNPE();
+
+    underTest.getInstallId(dbSession, null, A_OWNER);
+  }
+
+  @Test
+  public void getInstallId_throws_IAE_when_owner_id_is_null() {
+    expectOwnerIdNullOrEmptyIAE();
+
+    underTest.getInstallId(dbSession, GITHUB, null);
+  }
+
+  @Test
+  public void getInstallId_throws_IAE_when_owner_id_is_empty() {
+    expectOwnerIdNullOrEmptyIAE();
+
+    underTest.getInstallId(dbSession, GITHUB, EMPTY_STRING);
+  }
+
+  @Test
+  public void getInstallId_returns_empty_optional_when_entry_does_not_exist_in_DB() {
+    assertThat(underTest.getInstallId(dbSession, GITHUB, A_OWNER)).isEmpty();
+  }
+
+  @Test
+  public void getInstallId_returns_install_id_when_entry_exists() {
+    when(uuidFactory.create()).thenReturn(A_UUID);
+    underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER, AN_INSTALL);
+
+    assertThat(underTest.getInstallId(dbSession, GITHUB, A_OWNER)).contains(AN_INSTALL);
+  }
+
+  private void expectAlmNPE() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("alm can't be null");
+  }
+
+  private void expectOwnerIdNullOrEmptyIAE() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("ownerId can't be null nor empty");
+  }
+
+  private void expectInstallIdNullOrEmptyIAE() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("installId can't be null nor empty");
+  }
+
+  private AlmAppInstallAssert assertThatAlmAppInstall(AlmAppInstallDao.ALM alm, String ownerId) {
+    return new AlmAppInstallAssert(dbTester, dbSession, alm, ownerId);
+  }
+
+  private static class AlmAppInstallAssert extends AbstractAssert<AlmAppInstallAssert, AlmAppInstall> {
+
+    private AlmAppInstallAssert(DbTester dbTester, DbSession dbSession, AlmAppInstallDao.ALM alm, String ownerId) {
+      super(asAlmAppInstall(dbTester, dbSession, alm, ownerId), AlmAppInstallAssert.class);
+    }
+
+    private static AlmAppInstall asAlmAppInstall(DbTester dbTester, DbSession dbSession, AlmAppInstallDao.ALM alm, String ownerId) {
+      Map<String, Object> row = dbTester.selectFirst(
+        dbSession,
+        "select" +
+          " install_id as \"installId\", created_at as \"createdAt\", updated_at as \"updatedAt\"" +
+          " from alm_app_installs" +
+          " where alm_id='" + alm.getId() + "' and owner_id='" + ownerId + "'");
+      return new AlmAppInstall(
+        (String) row.get("installId"),
+        (Long) row.get("createdAt"),
+        (Long) row.get("updatedAt"));
+    }
+
+    public void doesNotExist() {
+      isNull();
+    }
+
+    public AlmAppInstallAssert hasInstallId(String expected) {
+      isNotNull();
+
+      if (!Objects.equals(actual.getInstallId(), expected)) {
+        failWithMessage("Expected ALM App Install to have column INSTALL_ID to be <%s> but was <%s>", true, actual.getInstallId());
+      }
+      return this;
+    }
+
+    public AlmAppInstallAssert hasCreatedAt(long expected) {
+      isNotNull();
+
+      if (!Objects.equals(actual.getCreatedAt(), expected)) {
+        failWithMessage("Expected ALM App Install to have column CREATED_AT to be <%s> but was <%s>", expected, actual.getCreatedAt());
+      }
+
+      return this;
+    }
+
+    public AlmAppInstallAssert hasUpdatedAt(long expected) {
+      isNotNull();
+
+      if (!Objects.equals(actual.getUpdatedAt(), expected)) {
+        failWithMessage("Expected ALM App Install to have column UPDATED_AT to be <%s> but was <%s>", expected, actual.getUpdatedAt());
+      }
+
+      return this;
+    }
+
+  }
+
+  private static final class AlmAppInstall {
+    private final String installId;
+    private final Long createdAt;
+    private final Long updatedAt;
+
+    public AlmAppInstall(@Nullable String installId, @Nullable Long createdAt, @Nullable Long updatedAt) {
+      this.installId = installId;
+      this.createdAt = createdAt;
+      this.updatedAt = updatedAt;
+    }
+
+    @CheckForNull
+    public String getInstallId() {
+      return installId;
+    }
+
+    @CheckForNull
+    public Long getCreatedAt() {
+      return createdAt;
+    }
+
+    @CheckForNull
+    public Long getUpdatedAt() {
+      return updatedAt;
+    }
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/CreateAlmAppInstallsTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/CreateAlmAppInstallsTable.java
new file mode 100644 (file)
index 0000000..3a3378c
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * 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.UUID_SIZE;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class CreateAlmAppInstallsTable extends DdlChange {
+
+  private static final String TABLE_NAME = "alm_app_installs";
+
+  private static final VarcharColumnDef UUID = newVarcharColumnDefBuilder()
+    .setColumnName("uuid")
+    .setLimit(UUID_SIZE)
+    .setIsNullable(false)
+    .build();
+  private static final VarcharColumnDef ALM_ID_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("alm_id")
+    .setIsNullable(false)
+    .setLimit(40)
+    .build();
+  private static final VarcharColumnDef OWNER_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("owner_id")
+    .setIsNullable(false)
+    .setLimit(MAX_SIZE)
+    .build();
+  private static final VarcharColumnDef INSTALL_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("install_id")
+    .setIsNullable(false)
+    .setLimit(MAX_SIZE)
+    .build();
+  private static final BigIntegerColumnDef CREATED_AT_COLUMN = newBigIntegerColumnDefBuilder()
+    .setColumnName("created_at")
+    .setIsNullable(false)
+    .build();
+  private static final BigIntegerColumnDef UPDATED_AT_COLUMN = newBigIntegerColumnDefBuilder()
+    .setColumnName("updated_at")
+    .setIsNullable(false)
+    .build();
+
+  public CreateAlmAppInstallsTable(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+
+    if (!tableExists()) {
+      context.execute(new CreateTableBuilder(getDialect(), TABLE_NAME)
+        .addPkColumn(UUID)
+        .addColumn(ALM_ID_COLUMN)
+        .addColumn(OWNER_COLUMN)
+        .addColumn(INSTALL_COLUMN)
+        .addColumn(CREATED_AT_COLUMN)
+        .addColumn(UPDATED_AT_COLUMN)
+        .build());
+
+      context.execute(new CreateIndexBuilder(getDialect())
+        .addColumn(ALM_ID_COLUMN)
+        .addColumn(OWNER_COLUMN)
+        .setUnique(true)
+        .setTable(TABLE_NAME)
+        .setName("alm_app_installs_owner")
+        .build());
+      context.execute(new CreateIndexBuilder(getDialect())
+        .addColumn(ALM_ID_COLUMN)
+        .addColumn(INSTALL_COLUMN)
+        .setUnique(true)
+        .setTable(TABLE_NAME)
+        .setName("alm_app_installs_install")
+        .build());
+    }
+  }
+
+  private boolean tableExists() throws SQLException {
+    try (Connection connection = getDatabase().getDataSource().getConnection()) {
+      return DatabaseUtils.tableExists(TABLE_NAME, connection);
+    }
+  }
+}
index cb3eb5ae31eff5e25fea149e04a290a269cc8963..3613d9144b39c4872de159bce7ad8c0994c48ab2 100644 (file)
@@ -31,6 +31,7 @@ public class DbVersion72 implements DbVersion {
       .add(2101, "Add HASH_METHOD to table users", AddHashMethodToUsersTable.class)
       .add(2102, "Populate HASH_METHOD on table users", PopulateHashMethodOnUsers.class)
       .add(2103, "Add isExternal boolean to rules", AddRuleExternal.class)
-      ;
+      .add(2104, "Create ALM_APP_INSTALLS table", CreateAlmAppInstallsTable.class)
+    ;
   }
 }
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/CreateAlmAppInstallsTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/CreateAlmAppInstallsTableTest.java
new file mode 100644 (file)
index 0000000..71d8e13
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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 org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+import static java.sql.Types.BIGINT;
+import static java.sql.Types.VARCHAR;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CreateAlmAppInstallsTableTest {
+
+  private static final String TABLE = "alm_app_installs";
+
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(CreateAlmAppInstallsTableTest.class, "empty.sql");
+
+  private CreateAlmAppInstallsTable underTest = new CreateAlmAppInstallsTable(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() {
+    assertThat(db.countRowsOfTable(TABLE)).isEqualTo(0);
+
+    db.assertColumnDefinition(TABLE, "uuid", VARCHAR, 40, false);
+    db.assertPrimaryKey(TABLE, "pk_" + TABLE, "uuid");
+    db.assertColumnDefinition(TABLE, "alm_id", VARCHAR, 40, false);
+    db.assertColumnDefinition(TABLE, "owner_id", VARCHAR, 4000, false);
+    db.assertColumnDefinition(TABLE, "install_id", VARCHAR, 4000, false);
+    db.assertColumnDefinition(TABLE, "created_at", BIGINT, null, false);
+    db.assertColumnDefinition(TABLE, "updated_at", BIGINT, null, false);
+
+    db.assertUniqueIndex(TABLE, "alm_app_installs_owner", "alm_id", "owner_id");
+    db.assertUniqueIndex(TABLE, "alm_app_installs_install", "alm_id", "install_id");
+  }
+}
index 7676fe52168b24f46f991901f45c8358a5470dd0..8e86219a76f1a9d249c50ad3c8e2a06031ceb74c 100644 (file)
@@ -34,7 +34,7 @@ public class DbVersion72Test {
 
   @Test
   public void verify_migration_count() {
-    verifyMigrationCount(underTest, 4);
+    verifyMigrationCount(underTest, 5);
   }
 
 }
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/CreateAlmAppInstallsTableTest/empty.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/CreateAlmAppInstallsTableTest/empty.sql
new file mode 100644 (file)
index 0000000..e69de29