]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11321 Bind organization to alm installation when creating organization
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 19 Oct 2018 11:44:36 +0000 (13:44 +0200)
committerSonarTech <sonartech@sonarsource.com>
Fri, 16 Nov 2018 19:21:03 +0000 (20:21 +0100)
* Return AlmAppInstallDto in select methods of AlmAppInstallDao, this is required in order to be able to more easily link an OrganizationDto to a AlmAppInstallDto in next commit
* Create ORGANIZATION_ALM_BINDINGS table
* Bind organization with installation when creating organization
* Delete alm binding when removing organization
* Delete alm binding when uninstalling ALM application
* Return ALM info in api/organizations/search
* Ensure user is admin to return Bitbucket team/user details

48 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
server/sonar-db-dao/src/main/java/org/sonar/db/alm/AlmAppInstallMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/alm/AlmTesting.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/alm/OrganizationAlmBindingDao.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/alm/OrganizationAlmBindingDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/alm/OrganizationAlmBindingMapper.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingDao.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingMapper.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingsDao.java [deleted file]
server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingsMapper.java [deleted file]
server/sonar-db-dao/src/main/resources/org/sonar/db/alm/AlmAppInstallMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/alm/OrganizationAlmBindingMapper.xml [new file with mode: 0644]
server/sonar-db-dao/src/main/resources/org/sonar/db/alm/ProjectAlmBindingMapper.xml [new file with mode: 0644]
server/sonar-db-dao/src/main/resources/org/sonar/db/alm/ProjectAlmBindingsMapper.xml [deleted file]
server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/DbTester.java
server/sonar-db-dao/src/test/java/org/sonar/db/alm/AlmAppInstallDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/alm/AlmDbTester.java [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/alm/OrganizationAlmBindingDaoTest.java [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/alm/ProjectAlmBindingDaoTest.java [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/alm/ProjectAlmBindingsDaoTest.java [deleted file]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v75/CreateOrganizationsAlmBindingsTable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v75/DbVersion75.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v75/CreateOrganizationsAlmBindingsTableTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v75/DbVersion75Test.java
server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationAlmBinding.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationUpdater.java
server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationUpdaterImpl.java
server/sonar-server/src/main/java/org/sonar/server/organization/ws/CreateAction.java
server/sonar-server/src/main/java/org/sonar/server/organization/ws/DeleteAction.java
server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsSupport.java
server/sonar-server/src/main/java/org/sonar/server/organization/ws/SearchAction.java
server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentAction.java
server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationUpdaterImplTest.java
server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/organization/ws/DeleteActionTest.java
server/sonar-server/src/test/java/org/sonar/server/organization/ws/SearchActionTest.java
server/sonar-server/src/test/java/org/sonar/server/ui/ws/ComponentActionTest.java
server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentActionTest/return_alm_infos_on_branch.json [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentActionTest/return_alm_infos_on_module.json [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentActionTest/return_alm_infos_on_project.json [deleted file]
sonar-ws/src/main/protobuf/ws-organizations.proto

index abc064e3142dd164cbb23f616b655e1a241ed1c7..c4e1ff35582c796468d0cd7042e80fc9f9d7e15e 100644 (file)
@@ -108,7 +108,7 @@ public class ComputeEngineContainerImplTest {
             + 3 // CeCleaningModule + its content
             + 4 // WebhookModule
             + 1 // CeDistributedInformation
-      );
+        );
       assertThat(picoContainer.getParent().getComponentAdapters()).hasSize(
         CONTAINER_ITSELF
           + 8 // level 3
@@ -121,7 +121,7 @@ public class ComputeEngineContainerImplTest {
       assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize(
         COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
           + 26 // level 1
-          + 57 // content of DaoModule
+          + 58 // content of DaoModule
           + 3 // content of EsModule
           + 54 // content of CorePropertyDefinitions
           + 1 // StopFlagContainer
index 085d7de56258d25e8d909377b52291b5b559b69e..0e699cba125cc0d89532048e9181f21585937650 100644 (file)
@@ -77,6 +77,7 @@ public final class SqTables {
     "metrics",
     "notifications",
     "organizations",
+    "organization_alm_bindings",
     "organization_members",
     "org_qprofiles",
     "org_quality_gates",
index a1b83a1315ddfcc3f65b640a33fa174d2267b84c..cf18feb03167c2e319adc3a1edb2a46244f341cf 100644 (file)
@@ -902,3 +902,16 @@ CREATE TABLE "PROJECT_MAPPINGS" (
 );
 CREATE UNIQUE INDEX "KEY_TYPE_KEE" ON "PROJECT_MAPPINGS" ("KEY_TYPE", "KEE");
 CREATE INDEX "PROJECT_UUID" ON "PROJECT_MAPPINGS" ("PROJECT_UUID");
+
+CREATE TABLE "ORGANIZATION_ALM_BINDINGS" (
+  "UUID" VARCHAR(40) NOT NULL,
+  "ORGANIZATION_UUID" VARCHAR(40) NOT NULL,
+  "ALM_APP_INSTALL_UUID" VARCHAR(40) NOT NULL,
+  "ALM_ID" VARCHAR(40) NOT NULL,
+  "URL" VARCHAR(2000) NOT NULL,
+  "USER_UUID" VARCHAR(255) NOT NULL,
+  "CREATED_AT" BIGINT NOT NULL,
+  CONSTRAINT "PK_ORGANIZATION_ALM_BINDINGS" PRIMARY KEY ("UUID")
+);
+CREATE UNIQUE INDEX "ORG_ALM_BINDINGS_ORG" ON "ORGANIZATION_ALM_BINDINGS" ("ORGANIZATION_UUID");
+CREATE UNIQUE INDEX "ORG_ALM_BINDINGS_INSTALL" ON "ORGANIZATION_ALM_BINDINGS" ("ALM_APP_INSTALL_UUID");
index fbc228cdc35636e1926f3e761f2e478985a1d16d..bdc4097099a16caae3ddeba7b55c9ab23f253d43 100644 (file)
@@ -23,7 +23,9 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import org.sonar.core.platform.Module;
-import org.sonar.db.alm.ProjectAlmBindingsDao;
+import org.sonar.db.alm.AlmAppInstallDao;
+import org.sonar.db.alm.OrganizationAlmBindingDao;
+import org.sonar.db.alm.ProjectAlmBindingDao;
 import org.sonar.db.ce.CeActivityDao;
 import org.sonar.db.ce.CeQueueDao;
 import org.sonar.db.ce.CeScannerContextDao;
@@ -41,7 +43,6 @@ 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.mapping.ProjectMappingsDao;
 import org.sonar.db.measure.LiveMeasureDao;
 import org.sonar.db.measure.MeasureDao;
@@ -108,7 +109,7 @@ public class DaoModule extends Module {
     GroupMembershipDao.class,
     GroupPermissionDao.class,
     AlmAppInstallDao.class,
-    ProjectAlmBindingsDao.class,
+    ProjectAlmBindingDao.class,
     InternalPropertiesDao.class,
     IssueChangeDao.class,
     IssueDao.class,
@@ -116,6 +117,7 @@ public class DaoModule extends Module {
     MeasureDao.class,
     MetricDao.class,
     NotificationQueueDao.class,
+    OrganizationAlmBindingDao.class,
     OrganizationDao.class,
     OrganizationMemberDao.class,
     PermissionTemplateCharacteristicDao.class,
index edc6731c31b09d67611a92a746260075c941f209..8ba7cf8362f523c84ea5f1ada1d6ad33afe01da3 100644 (file)
@@ -21,7 +21,9 @@ package org.sonar.db;
 
 import java.util.IdentityHashMap;
 import java.util.Map;
-import org.sonar.db.alm.ProjectAlmBindingsDao;
+import org.sonar.db.alm.AlmAppInstallDao;
+import org.sonar.db.alm.OrganizationAlmBindingDao;
+import org.sonar.db.alm.ProjectAlmBindingDao;
 import org.sonar.db.ce.CeActivityDao;
 import org.sonar.db.ce.CeQueueDao;
 import org.sonar.db.ce.CeScannerContextDao;
@@ -39,7 +41,6 @@ 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.mapping.ProjectMappingsDao;
 import org.sonar.db.measure.LiveMeasureDao;
 import org.sonar.db.measure.MeasureDao;
@@ -92,7 +93,7 @@ public class DbClient {
   private final QualityProfileDao qualityProfileDao;
   private final PropertiesDao propertiesDao;
   private final AlmAppInstallDao almAppInstallDao;
-  private final ProjectAlmBindingsDao projectAlmBindingsDao;
+  private final ProjectAlmBindingDao projectAlmBindingDao;
   private final InternalPropertiesDao internalPropertiesDao;
   private final SnapshotDao snapshotDao;
   private final ComponentDao componentDao;
@@ -142,6 +143,7 @@ public class DbClient {
   private final WebhookDao webhookDao;
   private final WebhookDeliveryDao webhookDeliveryDao;
   private final ProjectMappingsDao projectMappingsDao;
+  private final OrganizationAlmBindingDao organizationAlmBindingDao;
 
   public DbClient(Database database, MyBatis myBatis, DBSessions dbSessions, Dao... daos) {
     this.database = database;
@@ -153,7 +155,7 @@ public class DbClient {
       map.put(dao.getClass(), dao);
     }
     almAppInstallDao = getDao(map, AlmAppInstallDao.class);
-    projectAlmBindingsDao = getDao(map, ProjectAlmBindingsDao.class);
+    projectAlmBindingDao = getDao(map, ProjectAlmBindingDao.class);
     schemaMigrationDao = getDao(map, SchemaMigrationDao.class);
     authorizationDao = getDao(map, AuthorizationDao.class);
     organizationDao = getDao(map, OrganizationDao.class);
@@ -209,6 +211,7 @@ public class DbClient {
     webhookDao = getDao(map, WebhookDao.class);
     webhookDeliveryDao = getDao(map, WebhookDeliveryDao.class);
     projectMappingsDao = getDao(map, ProjectMappingsDao.class);
+    organizationAlmBindingDao = getDao(map, OrganizationAlmBindingDao.class);
   }
 
   public DbSession openSession(boolean batch) {
@@ -223,8 +226,8 @@ public class DbClient {
     return almAppInstallDao;
   }
 
-  public ProjectAlmBindingsDao projectAlmBindingsDao() {
-    return projectAlmBindingsDao;
+  public ProjectAlmBindingDao projectAlmBindingsDao() {
+    return projectAlmBindingDao;
   }
 
   public SchemaMigrationDao schemaMigrationDao() {
@@ -456,4 +459,8 @@ public class DbClient {
   public ProjectMappingsDao projectMappingsDao() {
     return projectMappingsDao;
   }
+
+  public OrganizationAlmBindingDao organizationAlmBindingDao() {
+    return organizationAlmBindingDao;
+  }
 }
index 93af5cc3914613860990f75e9cab4fda704d1384..846ca3724e2a9d23a90f7f08aba10f79527cacb6 100644 (file)
@@ -31,8 +31,9 @@ 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.alm.OrganizationAlmBindingMapper;
 import org.sonar.db.alm.ProjectAlmBindingDto;
-import org.sonar.db.alm.ProjectAlmBindingsMapper;
+import org.sonar.db.alm.ProjectAlmBindingMapper;
 import org.sonar.db.ce.CeActivityMapper;
 import org.sonar.db.ce.CeQueueMapper;
 import org.sonar.db.ce.CeScannerContextMapper;
@@ -232,12 +233,13 @@ public class MyBatis implements Startable {
       MeasureMapper.class,
       MetricMapper.class,
       NotificationQueueMapper.class,
+      OrganizationAlmBindingMapper.class,
       OrganizationMapper.class,
       OrganizationMemberMapper.class,
       PermissionTemplateCharacteristicMapper.class,
       PermissionTemplateMapper.class,
       PluginMapper.class,
-      ProjectAlmBindingsMapper.class,
+      ProjectAlmBindingMapper.class,
       ProjectLinkMapper.class,
       ProjectMappingsMapper.class,
       ProjectQgateAssociationMapper.class,
index 04302fe6f06832448b61ff0590fdc8626258f519..005017eefd8d93aef84784218e1c37bda81c8e7d 100644 (file)
@@ -29,6 +29,7 @@ import org.sonar.db.Dao;
 import org.sonar.db.DbSession;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
 import static org.apache.commons.lang.StringUtils.isNotEmpty;
 
 /**
@@ -44,17 +45,17 @@ public class AlmAppInstallDao implements Dao {
     this.uuidFactory = uuidFactory;
   }
 
-  public Optional<AlmAppInstallDto> selectByOwner(DbSession dbSession, ALM alm, String ownerId) {
+  public Optional<AlmAppInstallDto> selectByOwnerId(DbSession dbSession, ALM alm, String ownerId) {
     checkAlm(alm);
     checkOwnerId(ownerId);
 
     AlmAppInstallMapper mapper = getMapper(dbSession);
-    return Optional.ofNullable(mapper.selectByOwner(alm.getId(), ownerId));
+    return Optional.ofNullable(mapper.selectByOwnerId(alm.getId(), ownerId));
   }
 
-  public Optional<String> getOwerId(DbSession dbSession, ALM alm, String installationId) {
+  public Optional<AlmAppInstallDto> selectByInstallationId(DbSession dbSession, ALM alm, String installationId) {
     AlmAppInstallMapper mapper = getMapper(dbSession);
-    return Optional.ofNullable(mapper.selectOwnerId(alm.getId(), installationId));
+    return Optional.ofNullable(mapper.selectByInstallationId(alm.getId(), installationId));
   }
 
   public List<AlmAppInstallDto> findAllWithNoOwnerType(DbSession dbSession) {
@@ -88,7 +89,7 @@ public class AlmAppInstallDao implements Dao {
   }
 
   private static void checkAlm(@Nullable ALM alm) {
-    Objects.requireNonNull(alm, "alm can't be null");
+    requireNonNull(alm, "alm can't be null");
   }
 
   private static void checkOwnerId(@Nullable String ownerId) {
index cb979c2217fe1e245efa293514d18894dc59a2ed..f840a5aa07bf0e0ed07f5ad1de29d5cc1b7d9636 100644 (file)
@@ -27,10 +27,10 @@ import org.apache.ibatis.annotations.Param;
 public interface AlmAppInstallMapper {
 
   @CheckForNull
-  AlmAppInstallDto selectByOwner(@Param("almId") String almId, @Param("ownerId") String ownerId);
+  AlmAppInstallDto selectByOwnerId(@Param("almId") String almId, @Param("ownerId") String ownerId);
 
   @CheckForNull
-  String selectOwnerId(@Param("almId") String almId, @Param("installId") String installId);
+  AlmAppInstallDto selectByInstallationId(@Param("almId") String almId, @Param("installId") String installId);
 
   List<AlmAppInstallDto> selectAllWithNoOwnerType();
 
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/AlmTesting.java b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/AlmTesting.java
new file mode 100644 (file)
index 0000000..656be2f
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+public class AlmTesting {
+
+  private AlmTesting() {
+    // only statics
+  }
+
+
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/OrganizationAlmBindingDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/OrganizationAlmBindingDao.java
new file mode 100644 (file)
index 0000000..3612870
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * 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.Collection;
+import java.util.List;
+import java.util.Optional;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+import org.sonar.db.organization.OrganizationDto;
+
+import static org.sonar.db.DatabaseUtils.executeLargeInputs;
+
+public class OrganizationAlmBindingDao implements Dao {
+
+  private final System2 system2;
+  private final UuidFactory uuidFactory;
+
+  public OrganizationAlmBindingDao(System2 system2, UuidFactory uuidFactory) {
+    this.system2 = system2;
+    this.uuidFactory = uuidFactory;
+  }
+
+  public Optional<OrganizationAlmBindingDto> selectByOrganization(DbSession dbSession, OrganizationDto organization) {
+    return Optional.ofNullable(getMapper(dbSession).selectByOrganizationUuid(organization.getUuid()));
+  }
+
+  public List<OrganizationAlmBindingDto> selectByOrganizations(DbSession dbSession, Collection<OrganizationDto> organizations) {
+    return executeLargeInputs(organizations.stream().map(OrganizationDto::getUuid).collect(MoreCollectors.toSet()),
+      organizationUuids -> getMapper(dbSession).selectByOrganizationUuids(organizationUuids));
+  }
+
+  public void insert(DbSession dbSession, OrganizationDto organization, AlmAppInstallDto almAppInstall, String url, String userUuid) {
+    long now = system2.now();
+    getMapper(dbSession).insert(new OrganizationAlmBindingDto()
+      .setUuid(uuidFactory.create())
+      .setOrganizationUuid(organization.getUuid())
+      .setAlmAppInstallUuid(almAppInstall.getUuid())
+      .setAlmId(almAppInstall.getAlm())
+      .setUrl(url)
+      .setUserUuid(userUuid)
+      .setCreatedAt(now));
+  }
+
+  public void deleteByOrganization(DbSession dbSession, OrganizationDto organization) {
+    getMapper(dbSession).deleteByOrganizationUuid(organization.getUuid());
+  }
+
+  public void deleteByAlmAppInstall(DbSession dbSession, AlmAppInstallDto almAppInstall) {
+    getMapper(dbSession).deleteByAlmAppInstallUuid(almAppInstall.getUuid());
+  }
+
+  private static OrganizationAlmBindingMapper getMapper(DbSession dbSession) {
+    return dbSession.getMapper(OrganizationAlmBindingMapper.class);
+  }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/OrganizationAlmBindingDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/OrganizationAlmBindingDto.java
new file mode 100644 (file)
index 0000000..30a2795
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * 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.Arrays;
+
+/**
+ * This DTO is used to link an {@link org.sonar.db.organization.OrganizationDto} to a {@link AlmAppInstallDto}
+ */
+public class OrganizationAlmBindingDto {
+
+  /**
+   * Not empty. Max size is 40. Obviously it is unique.
+   */
+  private String uuid;
+  /**
+   * The UUID of the organization. Can't be null. Max size is 40.
+   * It's unique, as an organization is only linked to one installation (at least for the moment).
+   */
+  private String organizationUuid;
+  /**
+   * The UUID of ALM installation. Can't be null. Max size is 40.
+   * It's unique, as an installation is related to only one organization.
+   */
+  private String almAppInstallUuid;
+  /**
+   * The id of the ALM. Can't be null. Max size is 40.
+   */
+  private String rawAlmId;
+  /**
+   * The url of the ALM organization. Can't be null. Max size is 2000.
+   */
+  private String url;
+  /**
+   * The UUID of the user who has created the link between the organization and the ALN installation. Can't be null. Max size is 255.
+   */
+  private String userUuid;
+  /**
+   * Technical creation date
+   */
+  private long createdAt;
+
+  public String getUuid() {
+    return uuid;
+  }
+
+  OrganizationAlmBindingDto setUuid(String uuid) {
+    this.uuid = uuid;
+    return this;
+  }
+
+  public String getOrganizationUuid() {
+    return organizationUuid;
+  }
+
+  public OrganizationAlmBindingDto setOrganizationUuid(String organizationUuid) {
+    this.organizationUuid = organizationUuid;
+    return this;
+  }
+
+  public String getAlmAppInstallUuid() {
+    return almAppInstallUuid;
+  }
+
+  public OrganizationAlmBindingDto setAlmAppInstallUuid(String almAppInstallUuid) {
+    this.almAppInstallUuid = almAppInstallUuid;
+    return this;
+  }
+
+  public ALM getAlm() {
+    return Arrays.stream(ALM.values())
+      .filter(a -> a.getId().equals(rawAlmId))
+      .findAny()
+      .orElseThrow(() -> new IllegalStateException("ALM id " + rawAlmId + " is invalid"));
+  }
+
+  public OrganizationAlmBindingDto setAlmId(ALM alm) {
+    this.rawAlmId = alm.getId();
+    return this;
+  }
+
+  public String getUrl() {
+    return url;
+  }
+
+  public OrganizationAlmBindingDto setUrl(String url) {
+    this.url = url;
+    return this;
+  }
+
+  public String getUserUuid() {
+    return userUuid;
+  }
+
+  public OrganizationAlmBindingDto setUserUuid(String userUuid) {
+    this.userUuid = userUuid;
+    return this;
+  }
+
+  public long getCreatedAt() {
+    return createdAt;
+  }
+
+  OrganizationAlmBindingDto setCreatedAt(long createdAt) {
+    this.createdAt = createdAt;
+    return this;
+  }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/OrganizationAlmBindingMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/OrganizationAlmBindingMapper.java
new file mode 100644 (file)
index 0000000..f664a50
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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.Collection;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.apache.ibatis.annotations.Param;
+
+public interface OrganizationAlmBindingMapper {
+
+  @CheckForNull
+  OrganizationAlmBindingDto selectByOrganizationUuid(@Param("organizationUuid") String organizationUuid);
+
+  List<OrganizationAlmBindingDto> selectByOrganizationUuids(@Param("organizationUuids") Collection<String> organizationUuids);
+
+  void insert(@Param("dto") OrganizationAlmBindingDto dto);
+
+  void deleteByOrganizationUuid(@Param("organizationUuid") String organizationUuid);
+
+  void deleteByAlmAppInstallUuid(@Param("almAppInstallUuid") String almAppInstallUuid);
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingDao.java
new file mode 100644 (file)
index 0000000..f2dc943
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * 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.Collection;
+import java.util.List;
+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;
+import static org.sonar.db.DatabaseUtils.executeLargeInputs;
+
+public class ProjectAlmBindingDao implements Dao {
+
+  private final System2 system2;
+  private final UuidFactory uuidFactory;
+
+  public ProjectAlmBindingDao(System2 system2, UuidFactory uuidFactory) {
+    this.system2 = system2;
+    this.uuidFactory = uuidFactory;
+  }
+
+  public void insertOrUpdate(DbSession dbSession, ALM alm, String repoId, String projectUuid, @Nullable String githubSlug, String url) {
+    checkAlm(alm);
+    checkRepoId(repoId);
+    checkArgument(isNotEmpty(projectUuid), "projectUuid can't be null nor empty");
+    checkArgument(isNotEmpty(url), "url can't be null nor empty");
+
+    ProjectAlmBindingMapper mapper = getMapper(dbSession);
+    long now = system2.now();
+
+    if (mapper.update(alm.getId(), repoId, projectUuid, githubSlug, url, now) == 0) {
+      mapper.insert(uuidFactory.create(), alm.getId(), repoId, projectUuid, githubSlug, url, now);
+    }
+  }
+
+  public Optional<ProjectAlmBindingDto> selectByProjectUuid(DbSession session, String projectUuid) {
+    return Optional.ofNullable(getMapper(session).selectByProjectUuid(projectUuid));
+  }
+
+  /**
+   * Gets a list of bindings by their repo_id. The result does NOT contain {@code null} values for bindings not found, so
+   * the size of result may be less than the number of ids.
+   * <p>Results may be in a different order as input ids.</p>
+   */
+  public List<ProjectAlmBindingDto> selectByRepoIds(final DbSession session, ALM alm, Collection<String> repoIds) {
+    return executeLargeInputs(repoIds, partionnedIds -> getMapper(session).selectByRepoIds(alm.getId(), partionnedIds));
+  }
+
+  public Optional<ProjectAlmBindingDto> selectByRepoId(final DbSession session, ALM alm, String repoId) {
+    return Optional.ofNullable(getMapper(session).selectByRepoId(alm.getId(), repoId));
+  }
+
+  public Optional<String> findProjectKey(DbSession dbSession, ALM alm, String repoId) {
+    checkAlm(alm);
+    checkRepoId(repoId);
+
+    ProjectAlmBindingMapper mapper = getMapper(dbSession);
+    return Optional.ofNullable(mapper.selectProjectKey(alm.getId(), repoId));
+  }
+
+  private static void checkAlm(@Nullable ALM alm) {
+    Objects.requireNonNull(alm, "alm can't be null");
+  }
+
+  private static void checkRepoId(@Nullable String repoId) {
+    checkArgument(isNotEmpty(repoId), "repoId can't be null nor empty");
+  }
+
+  private static ProjectAlmBindingMapper getMapper(DbSession dbSession) {
+    return dbSession.getMapper(ProjectAlmBindingMapper.class);
+  }
+
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingMapper.java
new file mode 100644 (file)
index 0000000..1849cf1
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.ibatis.annotations.Param;
+
+public interface ProjectAlmBindingMapper {
+
+  int bindingCount(@Param("almId") String almId, @Param("repoId") String repoId);
+
+  void insert(@Param("uuid") String uuid, @Param("almId") String almId, @Param("repoId") String repoId, @Param("projectUuid") String projectUuid,
+    @Nullable @Param("githubSlug") String githubSlug, @Param("url") String url, @Param("now") long now);
+
+  int update(@Param("almId") String almId, @Param("repoId") String repoId, @Param("projectUuid") String projectUuid,
+    @Nullable @Param("githubSlug") String githubSlug, @Param("url") String url, @Param("now") long now);
+
+  List<ProjectAlmBindingDto> selectByRepoIds(@Param("almId") String almId, @Param("repoIds") List<String> repoIds);
+
+  ProjectAlmBindingDto selectByRepoId(@Param("almId") String almId, @Param("repoId") String repoId);
+
+  ProjectAlmBindingDto selectByProjectUuid(@Param("projectUuid") String projectUuid);
+
+  @CheckForNull
+  String selectProjectKey(@Param("almId") String almId, @Param("repoId") String repoId);
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingsDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingsDao.java
deleted file mode 100644 (file)
index 61b9bc5..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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.Collection;
-import java.util.List;
-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;
-import static org.sonar.db.DatabaseUtils.executeLargeInputs;
-
-public class ProjectAlmBindingsDao implements Dao {
-
-  private final System2 system2;
-  private final UuidFactory uuidFactory;
-
-  public ProjectAlmBindingsDao(System2 system2, UuidFactory uuidFactory) {
-    this.system2 = system2;
-    this.uuidFactory = uuidFactory;
-  }
-
-  public void insertOrUpdate(DbSession dbSession, ALM alm, String repoId, String projectUuid, @Nullable String githubSlug, String url) {
-    checkAlm(alm);
-    checkRepoId(repoId);
-    checkArgument(isNotEmpty(projectUuid), "projectUuid can't be null nor empty");
-    checkArgument(isNotEmpty(url), "url can't be null nor empty");
-
-    ProjectAlmBindingsMapper mapper = getMapper(dbSession);
-    long now = system2.now();
-
-    if (mapper.update(alm.getId(), repoId, projectUuid, githubSlug, url, now) == 0) {
-      mapper.insert(uuidFactory.create(), alm.getId(), repoId, projectUuid, githubSlug, url, now);
-    }
-  }
-
-  public Optional<ProjectAlmBindingDto> selectByProjectUuid(DbSession session, String projectUuid) {
-    return Optional.ofNullable(getMapper(session).selectByProjectUuid(projectUuid));
-  }
-
-  /**
-   * Gets a list of bindings by their repo_id. The result does NOT contain {@code null} values for bindings not found, so
-   * the size of result may be less than the number of ids.
-   * <p>Results may be in a different order as input ids.</p>
-   */
-  public List<ProjectAlmBindingDto> selectByRepoIds(final DbSession session, ALM alm, Collection<String> repoIds) {
-    return executeLargeInputs(repoIds, partionnedIds -> getMapper(session).selectByRepoIds(alm.getId(), partionnedIds));
-  }
-
-  public Optional<ProjectAlmBindingDto> selectByRepoId(final DbSession session, ALM alm, String repoId) {
-    return Optional.ofNullable(getMapper(session).selectByRepoId(alm.getId(), repoId));
-  }
-
-  public Optional<String> findProjectKey(DbSession dbSession, ALM alm, String repoId) {
-    checkAlm(alm);
-    checkRepoId(repoId);
-
-    ProjectAlmBindingsMapper mapper = getMapper(dbSession);
-    return Optional.ofNullable(mapper.selectProjectKey(alm.getId(), repoId));
-  }
-
-  private static void checkAlm(@Nullable ALM alm) {
-    Objects.requireNonNull(alm, "alm can't be null");
-  }
-
-  private static void checkRepoId(@Nullable String repoId) {
-    checkArgument(isNotEmpty(repoId), "repoId can't be null nor empty");
-  }
-
-  private static ProjectAlmBindingsMapper getMapper(DbSession dbSession) {
-    return dbSession.getMapper(ProjectAlmBindingsMapper.class);
-  }
-
-}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingsMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingsMapper.java
deleted file mode 100644 (file)
index df0484e..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.List;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.apache.ibatis.annotations.Param;
-
-public interface ProjectAlmBindingsMapper {
-
-  int bindingCount(@Param("almId") String almId, @Param("repoId") String repoId);
-
-  void insert(@Param("uuid") String uuid, @Param("almId") String almId, @Param("repoId") String repoId, @Param("projectUuid") String projectUuid,
-    @Nullable @Param("githubSlug") String githubSlug, @Param("url") String url, @Param("now") long now);
-
-  int update(@Param("almId") String almId, @Param("repoId") String repoId, @Param("projectUuid") String projectUuid,
-    @Nullable @Param("githubSlug") String githubSlug, @Param("url") String url, @Param("now") long now);
-
-  List<ProjectAlmBindingDto> selectByRepoIds(@Param("almId") String almId, @Param("repoIds") List<String> repoIds);
-
-  ProjectAlmBindingDto selectByRepoId(@Param("almId") String almId, @Param("repoId") String repoId);
-
-  ProjectAlmBindingDto selectByProjectUuid(@Param("projectUuid") String projectUuid);
-
-  @CheckForNull
-  String selectProjectKey(@Param("almId") String almId, @Param("repoId") String repoId);
-}
index d96a33dfe13335a7b92a4d25a5b2f570c5b5a2d1..2fe08a4dd5ef8080d10f0c72721051d80f772c8f 100644 (file)
@@ -13,7 +13,7 @@
     updated_at as updatedAt
   </sql>
 
-  <select id="selectByOwner" parameterType="Map" resultType="org.sonar.db.alm.AlmAppInstallDto">
+  <select id="selectByOwnerId" parameterType="Map" resultType="org.sonar.db.alm.AlmAppInstallDto">
     select <include refid="sqlColumns" />
     from
       alm_app_installs
       and owner_id = #{ownerId, jdbcType=VARCHAR}
   </select>
 
-  <select id="selectOwnerId" parameterType="Map" resultType="String">
+  <select id="selectByInstallationId" parameterType="Map" resultType="org.sonar.db.alm.AlmAppInstallDto">
     select
-    owner_id as ownerId
+    <include refid="sqlColumns"/>
     from
-    alm_app_installs
+      alm_app_installs
     where
-    alm_id = #{almId, jdbcType=VARCHAR}
-    and install_id = #{installId, jdbcType=VARCHAR}
+      alm_id = #{almId, jdbcType=VARCHAR}
+      and install_id = #{installId, jdbcType=VARCHAR}
   </select>
 
   <select id="selectAllWithNoOwnerType" parameterType="Map" resultType="org.sonar.db.alm.AlmAppInstallDto">
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/alm/OrganizationAlmBindingMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/alm/OrganizationAlmBindingMapper.xml
new file mode 100644 (file)
index 0000000..395eb91
--- /dev/null
@@ -0,0 +1,67 @@
+<?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.OrganizationAlmBindingMapper">
+
+  <sql id="columns">
+    uuid,
+    organization_uuid as organizationUuid,
+    alm_app_install_uuid as almAppInstallUuid,
+    alm_id as rawAlmId,
+    url,
+    user_uuid as userUuid,
+    created_at as createdAt
+  </sql>
+
+  <select id="selectByOrganizationUuid" parameterType="String" resultType="org.sonar.db.alm.OrganizationAlmBindingDto">
+    select
+      <include refid="columns"/>
+    from
+      organization_alm_bindings
+    where
+      organization_uuid = #{organizationUuid, jdbcType=VARCHAR}
+  </select>
+
+  <select id="selectByOrganizationUuids" parameterType="String" resultType="org.sonar.db.alm.OrganizationAlmBindingDto">
+    select
+    <include refid="columns"/>
+    from
+    organization_alm_bindings
+    where
+    organization_uuid in
+    <foreach collection="organizationUuids" open="(" close=")" item="organizationUuid" separator=",">
+      #{organizationUuid , jdbcType=VARCHAR}
+    </foreach>
+  </select>
+
+  <insert id="insert" parameterType="Map" useGeneratedKeys="false">
+    INSERT INTO organization_alm_bindings
+    (
+      uuid,
+      organization_uuid,
+      alm_app_install_uuid,
+      alm_id,
+      url,
+      user_uuid,
+      created_at
+    )
+    VALUES (
+      #{dto.uuid, jdbcType=VARCHAR},
+      #{dto.organizationUuid, jdbcType=VARCHAR},
+      #{dto.almAppInstallUuid, jdbcType=VARCHAR},
+      #{dto.rawAlmId, jdbcType=VARCHAR},
+      #{dto.url, jdbcType=VARCHAR},
+      #{dto.userUuid, jdbcType=VARCHAR},
+      #{dto.createdAt, jdbcType=BIGINT}
+    )
+  </insert>
+
+  <delete id="deleteByOrganizationUuid" parameterType="String">
+    DELETE FROM organization_alm_bindings WHERE organization_uuid = #{organizationUuid, jdbcType=VARCHAR}
+  </delete>
+
+  <delete id="deleteByAlmAppInstallUuid" parameterType="String">
+    DELETE FROM organization_alm_bindings WHERE alm_app_install_uuid = #{almAppInstallUuid, jdbcType=VARCHAR}
+  </delete>
+
+</mapper>
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/alm/ProjectAlmBindingMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/alm/ProjectAlmBindingMapper.xml
new file mode 100644 (file)
index 0000000..845af9e
--- /dev/null
@@ -0,0 +1,105 @@
+<?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.ProjectAlmBindingMapper">
+
+  <sql id="columns">
+    uuid,
+    alm_id as rawAlmId,
+    repo_id as repoId,
+    project_uuid as projectUuid,
+    github_slug as githubSlug,
+    url
+  </sql>
+
+  <select id="bindingCount" parameterType="Map" resultType="int">
+    select
+      count(*) as count
+    from
+      project_alm_bindings
+    where
+      alm_id = #{almId, jdbcType=VARCHAR}
+      and repo_id = #{repoId, jdbcType=VARCHAR}
+  </select>
+
+  <insert id="insert" parameterType="Map" useGeneratedKeys="false">
+    insert into project_alm_bindings
+    (
+      uuid,
+      alm_id,
+      repo_id,
+      project_uuid,
+      github_slug,
+      url,
+      created_at,
+      updated_at
+    )
+    values (
+      #{uuid, jdbcType=VARCHAR},
+      #{almId, jdbcType=VARCHAR},
+      #{repoId, jdbcType=VARCHAR},
+      #{projectUuid, jdbcType=VARCHAR},
+      #{githubSlug, jdbcType=VARCHAR},
+      #{url, jdbcType=VARCHAR},
+      #{now, jdbcType=BIGINT},
+      #{now, jdbcType=BIGINT}
+    )
+  </insert>
+
+  <update id="update" parameterType="map">
+    update project_alm_bindings
+    set
+      project_uuid = #{projectUuid, jdbcType=VARCHAR},
+      github_slug = #{githubSlug, jdbcType=VARCHAR},
+      url = #{url, jdbcType=VARCHAR},
+      updated_at = #{now, jdbcType=BIGINT}
+    where
+      alm_id = #{almId, jdbcType=VARCHAR}
+      and repo_id = #{repoId, jdbcType=VARCHAR}
+  </update>
+
+  <select id="selectByRepoIds" parameterType="map" resultType="ProjectAlmBinding">
+    select
+      <include refid="columns"/>
+    from
+      project_alm_bindings
+    where
+      alm_id = #{almId, jdbcType=VARCHAR}
+      and repo_id in
+    <foreach collection="repoIds" open="(" close=")" item="repoId" separator=",">
+      #{repoId,jdbcType=VARCHAR}
+    </foreach>
+  </select>
+
+  <select id="selectByRepoId" parameterType="map" resultType="ProjectAlmBinding">
+    select
+      <include refid="columns"/>
+    from
+      project_alm_bindings
+    where
+      alm_id = #{almId, jdbcType=VARCHAR}
+      and repo_id = #{repoId, jdbcType=VARCHAR}
+  </select>
+
+  <select id="selectByProjectUuid" parameterType="map" resultType="ProjectAlmBinding">
+    select
+      <include refid="columns"/>
+    from
+      project_alm_bindings
+    where
+      project_uuid = #{projectUuid, jdbcType=VARCHAR}
+  </select>
+
+  <select id="selectProjectKey" parameterType="Map" resultType="String">
+    select
+      p.kee as projectKey
+    from
+      project_alm_bindings b
+    inner join projects p
+      on b.project_uuid = p.project_uuid
+    where
+      alm_id = #{almId, jdbcType=VARCHAR}
+      and repo_id = #{repoId, jdbcType=VARCHAR}
+  </select>
+
+</mapper>
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/alm/ProjectAlmBindingsMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/alm/ProjectAlmBindingsMapper.xml
deleted file mode 100644 (file)
index 27f5d1f..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-<?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.ProjectAlmBindingsMapper">
-
-  <sql id="columns">
-    uuid,
-    alm_id as rawAlmId,
-    repo_id as repoId,
-    project_uuid as projectUuid,
-    github_slug as githubSlug,
-    url
-  </sql>
-
-  <select id="bindingCount" parameterType="Map" resultType="int">
-    select
-      count(*) as count
-    from
-      project_alm_bindings
-    where
-      alm_id = #{almId, jdbcType=VARCHAR}
-      and repo_id = #{repoId, jdbcType=VARCHAR}
-  </select>
-
-  <insert id="insert" parameterType="Map" useGeneratedKeys="false">
-    insert into project_alm_bindings
-    (
-      uuid,
-      alm_id,
-      repo_id,
-      project_uuid,
-      github_slug,
-      url,
-      created_at,
-      updated_at
-    )
-    values (
-      #{uuid, jdbcType=VARCHAR},
-      #{almId, jdbcType=VARCHAR},
-      #{repoId, jdbcType=VARCHAR},
-      #{projectUuid, jdbcType=VARCHAR},
-      #{githubSlug, jdbcType=VARCHAR},
-      #{url, jdbcType=VARCHAR},
-      #{now, jdbcType=BIGINT},
-      #{now, jdbcType=BIGINT}
-    )
-  </insert>
-
-  <update id="update" parameterType="map">
-    update project_alm_bindings
-    set
-      project_uuid = #{projectUuid, jdbcType=VARCHAR},
-      github_slug = #{githubSlug, jdbcType=VARCHAR},
-      url = #{url, jdbcType=VARCHAR},
-      updated_at = #{now, jdbcType=BIGINT}
-    where
-      alm_id = #{almId, jdbcType=VARCHAR}
-      and repo_id = #{repoId, jdbcType=VARCHAR}
-  </update>
-
-  <select id="selectByRepoIds" parameterType="map" resultType="ProjectAlmBinding">
-    select
-      <include refid="columns"/>
-    from
-      project_alm_bindings
-    where
-      alm_id = #{almId, jdbcType=VARCHAR}
-      and repo_id in
-    <foreach collection="repoIds" open="(" close=")" item="repoId" separator=",">
-      #{repoId,jdbcType=VARCHAR}
-    </foreach>
-  </select>
-
-  <select id="selectByRepoId" parameterType="map" resultType="ProjectAlmBinding">
-    select
-      <include refid="columns"/>
-    from
-      project_alm_bindings
-    where
-      alm_id = #{almId, jdbcType=VARCHAR}
-      and repo_id = #{repoId, jdbcType=VARCHAR}
-  </select>
-
-  <select id="selectByProjectUuid" parameterType="map" resultType="ProjectAlmBinding">
-    select
-      <include refid="columns"/>
-    from
-      project_alm_bindings
-    where
-      project_uuid = #{projectUuid, jdbcType=VARCHAR}
-  </select>
-
-  <select id="selectProjectKey" parameterType="Map" resultType="String">
-    select
-      p.kee as projectKey
-    from
-      project_alm_bindings b
-    inner join projects p
-      on b.project_uuid = p.project_uuid
-    where
-      alm_id = #{almId, jdbcType=VARCHAR}
-      and repo_id = #{repoId, jdbcType=VARCHAR}
-  </select>
-
-</mapper>
index 307a4db0861aaeabd9cb484863b247bf3d54e78f..116af4636e49be2310d6ef50fe7e0c1cad8412cb 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 + 57);
+    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 58);
   }
 }
index 15f11f5d2d02f73e3908936d5201c525efd157b6..82194a971a442489d69f0ad9b9fbd58608b6852d 100644 (file)
@@ -29,6 +29,7 @@ import org.apache.commons.lang.StringUtils;
 import org.picocontainer.containers.TransientPicoContainer;
 import org.sonar.api.utils.System2;
 import org.sonar.core.util.SequenceUuidFactory;
+import org.sonar.db.alm.AlmDbTester;
 import org.sonar.db.component.ComponentDbTester;
 import org.sonar.db.component.ProjectLinkDbTester;
 import org.sonar.db.event.EventDbTester;
@@ -87,6 +88,7 @@ public class DbTester extends AbstractDbTester<TestDb> {
   private final PluginDbTester pluginDbTester;
   private final WebhookDbTester webhookDbTester;
   private final WebhookDeliveryDbTester webhookDeliveryDbTester;
+  private final AlmDbTester almDbTester;
 
   public DbTester(System2 system2, @Nullable String schemaPath) {
     super(TestDb.create(schemaPath));
@@ -112,6 +114,7 @@ public class DbTester extends AbstractDbTester<TestDb> {
     this.pluginDbTester = new PluginDbTester(this);
     this.webhookDbTester = new WebhookDbTester(this);
     this.webhookDeliveryDbTester = new WebhookDeliveryDbTester(this);
+    this.almDbTester = new AlmDbTester(this);
   }
 
   public static DbTester create() {
@@ -262,6 +265,10 @@ public class DbTester extends AbstractDbTester<TestDb> {
     return webhookDeliveryDbTester;
   }
 
+  public AlmDbTester alm() {
+    return almDbTester;
+  }
+
   @Override
   protected void after() {
     if (session != null) {
index 00955915f03eb0263b82b5e4938013aefcc908c2..ddc76bdf31650f612a8066505a24535b1de8b3b0 100644 (file)
@@ -60,47 +60,54 @@ public class AlmAppInstallDaoTest {
   private AlmAppInstallDao underTest = new AlmAppInstallDao(system2, uuidFactory);
 
   @Test
-  public void selectByOwner() {
+  public void selectByOwnerId() {
     when(uuidFactory.create()).thenReturn(A_UUID);
     when(system2.now()).thenReturn(DATE);
     underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER, true, AN_INSTALL);
 
-    assertThat(underTest.selectByOwner(dbSession, GITHUB, A_OWNER).get())
-      .extracting(AlmAppInstallDto::getUuid, AlmAppInstallDto::getInstallId, AlmAppInstallDto::getOwnerId, AlmAppInstallDto::getAlm, AlmAppInstallDto::getCreatedAt, AlmAppInstallDto::getUpdatedAt)
-      .containsExactlyInAnyOrder(A_UUID, AN_INSTALL, A_OWNER, ALM.GITHUB, DATE, DATE);
-    assertThat(underTest.selectByOwner(dbSession, GITHUB, "unknown")).isEmpty();
-    assertThat(underTest.selectByOwner(dbSession, BITBUCKETCLOUD, A_OWNER)).isEmpty();
+    assertThat(underTest.selectByOwnerId(dbSession, GITHUB, A_OWNER).get())
+      .extracting(AlmAppInstallDto::getUuid, AlmAppInstallDto::getAlm, AlmAppInstallDto::getInstallId, AlmAppInstallDto::getOwnerId,
+        AlmAppInstallDto::getCreatedAt, AlmAppInstallDto::getUpdatedAt)
+      .contains(A_UUID, GITHUB, A_OWNER, AN_INSTALL, DATE, DATE);
+
+    assertThat(underTest.selectByOwnerId(dbSession, BITBUCKETCLOUD, A_OWNER)).isNotPresent();
+    assertThat(underTest.selectByOwnerId(dbSession, GITHUB, "Unknown owner")).isNotPresent();
   }
 
   @Test
   public void selectByOwner_throws_NPE_when_alm_is_null() {
     expectAlmNPE();
 
-    underTest.selectByOwner(dbSession, null, A_OWNER);
+    underTest.selectByOwnerId(dbSession, null, A_OWNER);
   }
 
   @Test
   public void selectByOwner_throws_IAE_when_owner_id_is_null() {
     expectOwnerIdNullOrEmptyIAE();
 
-    underTest.selectByOwner(dbSession, GITHUB, null);
+    underTest.selectByOwnerId(dbSession, GITHUB, null);
   }
 
   @Test
   public void selectByOwner_throws_IAE_when_owner_id_is_empty() {
     expectOwnerIdNullOrEmptyIAE();
 
-    underTest.selectByOwner(dbSession, GITHUB, EMPTY_STRING);
+    underTest.selectByOwnerId(dbSession, GITHUB, EMPTY_STRING);
   }
 
   @Test
-  public void getOwnerId() {
+  public void selectByInstallationId() {
     when(uuidFactory.create()).thenReturn(A_UUID);
+    when(system2.now()).thenReturn(DATE);
     underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER, true, AN_INSTALL);
 
-    assertThat(underTest.getOwerId(dbSession, GITHUB, AN_INSTALL)).contains(A_OWNER);
-    assertThat(underTest.getOwerId(dbSession, GITHUB, "unknown")).isEmpty();
-    assertThat(underTest.getOwerId(dbSession, BITBUCKETCLOUD, AN_INSTALL)).isEmpty();
+    assertThat(underTest.selectByInstallationId(dbSession, GITHUB, AN_INSTALL).get())
+      .extracting(AlmAppInstallDto::getUuid, AlmAppInstallDto::getAlm, AlmAppInstallDto::getInstallId, AlmAppInstallDto::getOwnerId,
+        AlmAppInstallDto::getCreatedAt, AlmAppInstallDto::getUpdatedAt)
+      .contains(A_UUID, GITHUB, A_OWNER, AN_INSTALL, DATE, DATE);
+
+    assertThat(underTest.selectByInstallationId(dbSession, GITHUB, "unknown installation")).isEmpty();
+    assertThat(underTest.selectByInstallationId(dbSession, BITBUCKETCLOUD, AN_INSTALL)).isEmpty();
   }
 
   @Test
@@ -166,7 +173,7 @@ public class AlmAppInstallDaoTest {
   }
 
   @Test
-  public void delete_doesn_t_fail() {
+  public void delete_does_not_fail() {
     assertThatAlmAppInstall(GITHUB, A_OWNER).doesNotExist();
 
     underTest.delete(dbSession, GITHUB, A_OWNER);
@@ -179,7 +186,7 @@ public class AlmAppInstallDaoTest {
     underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER, true, AN_INSTALL);
 
     when(system2.now()).thenReturn(DATE_LATER);
-    underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER,true,  OTHER_INSTALL);
+    underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER, true, OTHER_INSTALL);
 
     assertThatAlmAppInstall(GITHUB, A_OWNER)
       .hasInstallId(OTHER_INSTALL)
@@ -235,7 +242,7 @@ public class AlmAppInstallDaoTest {
     }
 
     private static AlmAppInstallDto asAlmAppInstall(DbTester db, DbSession dbSession, ALM alm, String ownerId) {
-      Optional<AlmAppInstallDto> almAppInstall = db.getDbClient().almAppInstallDao().selectByOwner(dbSession, alm, ownerId);
+      Optional<AlmAppInstallDto> almAppInstall = db.getDbClient().almAppInstallDao().selectByOwnerId(dbSession, alm, ownerId);
       return almAppInstall.orElse(null);
     }
 
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/alm/AlmDbTester.java b/server/sonar-db-dao/src/test/java/org/sonar/db/alm/AlmDbTester.java
new file mode 100644 (file)
index 0000000..f129b59
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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 org.sonar.db.DbTester;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.user.UserDto;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.sonar.db.alm.ALM.GITHUB;
+
+public class AlmDbTester {
+
+  private final DbTester db;
+
+  public AlmDbTester(DbTester db) {
+    this.db = db;
+  }
+
+  public OrganizationAlmBindingDto insertOrganizationAlmBinding(OrganizationDto organization, AlmAppInstallDto almAppInstall) {
+    UserDto user = db.users().insertUser();
+    db.getDbClient().organizationAlmBindingDao().insert(db.getSession(), organization, almAppInstall, randomAlphabetic(10), user.getUuid());
+    return db.getDbClient().organizationAlmBindingDao().selectByOrganization(db.getSession(), organization).get();
+  }
+
+  public AlmAppInstallDto insertAlmAppInstall() {
+    ALM alm = GITHUB;
+    String ownerId = randomAlphanumeric(10);
+    db.getDbClient().almAppInstallDao().insertOrUpdate(db.getSession(), alm, ownerId, false, randomAlphanumeric(10));
+    return db.getDbClient().almAppInstallDao().selectByOwnerId(db.getSession(), alm, ownerId).get();
+  }
+
+}
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/alm/OrganizationAlmBindingDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/alm/OrganizationAlmBindingDaoTest.java
new file mode 100644 (file)
index 0000000..8972e20
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * 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.Optional;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.internal.TestSystem2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.DbTester;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.user.UserDto;
+
+import static java.util.Arrays.asList;
+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.entry;
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class OrganizationAlmBindingDaoTest {
+
+  private static final long NOW = 1_600_000_000_000L;
+
+  private System2 system2 = new TestSystem2().setNow(NOW);
+
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  private UuidFactory uuidFactory = mock(UuidFactory.class);
+
+  private OrganizationAlmBindingDao underTest = new OrganizationAlmBindingDao(system2, uuidFactory);
+
+  @Test
+  public void selectByOrganization() {
+    OrganizationDto organization = db.organizations().insert();
+    AlmAppInstallDto almAppInstall = db.alm().insertAlmAppInstall();
+    OrganizationAlmBindingDto dto = db.alm().insertOrganizationAlmBinding(organization, almAppInstall);
+
+    Optional<OrganizationAlmBindingDto> result = underTest.selectByOrganization(db.getSession(), organization);
+
+    assertThat(result).isPresent();
+    assertThat(result.get())
+      .extracting(OrganizationAlmBindingDto::getUuid, OrganizationAlmBindingDto::getOrganizationUuid, OrganizationAlmBindingDto::getAlmAppInstallUuid,
+        OrganizationAlmBindingDto::getUrl, OrganizationAlmBindingDto::getAlm,
+        OrganizationAlmBindingDto::getUserUuid, OrganizationAlmBindingDto::getCreatedAt)
+      .containsExactlyInAnyOrder(dto.getUuid(), organization.getUuid(), dto.getAlmAppInstallUuid(),
+        dto.getUrl(), ALM.GITHUB,
+        dto.getUserUuid(), NOW);
+  }
+
+  @Test
+  public void selectByOrganization_returns_empty_when_organization_is_not_bound_to_installation() {
+    OrganizationDto organization = db.organizations().insert();
+    AlmAppInstallDto almAppInstall = db.alm().insertAlmAppInstall();
+    db.alm().insertOrganizationAlmBinding(organization, almAppInstall);
+    // No binding on other organization
+    OrganizationDto otherOrganization = db.organizations().insert();
+
+    Optional<OrganizationAlmBindingDto> result = underTest.selectByOrganization(db.getSession(), otherOrganization);
+
+    assertThat(result).isEmpty();
+  }
+
+  @Test
+  public void selectByOrganizations() {
+    OrganizationDto organization1 = db.organizations().insert();
+    OrganizationAlmBindingDto organizationAlmBinding1 = db.alm().insertOrganizationAlmBinding(organization1, db.alm().insertAlmAppInstall());
+    OrganizationDto organization2 = db.organizations().insert();
+    OrganizationAlmBindingDto organizationAlmBinding2 = db.alm().insertOrganizationAlmBinding(organization2, db.alm().insertAlmAppInstall());
+    OrganizationDto organizationNotBound = db.organizations().insert();
+
+    assertThat(underTest.selectByOrganizations(db.getSession(), asList(organization1, organization2, organizationNotBound)))
+      .extracting(OrganizationAlmBindingDto::getUuid, OrganizationAlmBindingDto::getOrganizationUuid)
+      .containsExactlyInAnyOrder(
+        tuple(organizationAlmBinding1.getUuid(), organization1.getUuid()),
+        tuple(organizationAlmBinding2.getUuid(), organization2.getUuid()));
+
+    assertThat(underTest.selectByOrganizations(db.getSession(), singletonList(organizationNotBound))).isEmpty();
+  }
+
+  @Test
+  public void insert() {
+    when(uuidFactory.create()).thenReturn("ABCD");
+    OrganizationDto organization = db.organizations().insert();
+    UserDto user = db.users().insertUser();
+    AlmAppInstallDto almAppInstall = db.alm().insertAlmAppInstall();
+
+    underTest.insert(db.getSession(), organization, almAppInstall, "http://myorg.com", user.getUuid());
+
+    assertThat(db.selectFirst(db.getSession(),
+      "select" +
+        "   uuid as \"uuid\", organization_uuid as \"organizationUuid\", alm_app_install_uuid as \"almAppInstallUuid\", url as \"url\", alm_id as \"almId\"," +
+        " user_uuid as \"userUuid\", created_at as \"createdAt\"" +
+        " from organization_alm_bindings" +
+        "   where organization_uuid='" + organization.getUuid() + "'"))
+          .contains(
+            entry("uuid", "ABCD"),
+            entry("organizationUuid", organization.getUuid()),
+            entry("almAppInstallUuid", almAppInstall.getUuid()),
+            entry("almId", "github"),
+            entry("url", "http://myorg.com"),
+            entry("userUuid", user.getUuid()),
+            entry("createdAt", NOW));
+  }
+
+  @Test
+  public void deleteByOrganization() {
+    OrganizationDto organization = db.organizations().insert();
+    AlmAppInstallDto almAppInstall = db.alm().insertAlmAppInstall();
+    db.alm().insertOrganizationAlmBinding(organization, almAppInstall);
+    OrganizationDto otherOrganization = db.organizations().insert();
+    AlmAppInstallDto otherAlmAppInstall = db.alm().insertAlmAppInstall();
+    db.alm().insertOrganizationAlmBinding(otherOrganization, otherAlmAppInstall);
+
+    underTest.deleteByOrganization(db.getSession(), organization);
+
+    assertThat(underTest.selectByOrganization(db.getSession(), organization)).isNotPresent();
+    assertThat(underTest.selectByOrganization(db.getSession(), otherOrganization)).isPresent();
+  }
+
+  @Test
+  public void deleteByAlmAppInstall() {
+    OrganizationDto organization = db.organizations().insert();
+    AlmAppInstallDto almAppInstall = db.alm().insertAlmAppInstall();
+    db.alm().insertOrganizationAlmBinding(organization, almAppInstall);
+    OrganizationDto otherOrganization = db.organizations().insert();
+    AlmAppInstallDto otherAlmAppInstall = db.alm().insertAlmAppInstall();
+    db.alm().insertOrganizationAlmBinding(otherOrganization, otherAlmAppInstall);
+
+    underTest.deleteByAlmAppInstall(db.getSession(), almAppInstall);
+
+    assertThat(underTest.selectByOrganization(db.getSession(), organization)).isNotPresent();
+    assertThat(underTest.selectByOrganization(db.getSession(), otherOrganization)).isPresent();
+  }
+
+}
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/alm/ProjectAlmBindingDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/alm/ProjectAlmBindingDaoTest.java
new file mode 100644 (file)
index 0000000..2dd6635
--- /dev/null
@@ -0,0 +1,409 @@
+/*
+ * 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.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+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.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.alm.ALM.BITBUCKETCLOUD;
+import static org.sonar.db.alm.ALM.GITHUB;
+
+public class ProjectAlmBindingDaoTest {
+
+  private static final String A_UUID = "abcde1234";
+  private static final String ANOTHER_UUID = "xyz789";
+  private static final String EMPTY_STRING = "";
+
+  private static final String A_REPO = "my_repo";
+  private static final String ANOTHER_REPO = "another_repo";
+
+  private static final String A_GITHUB_SLUG = null;
+  private static final String ANOTHER_GITHUB_SLUG = "example/foo";
+
+  private static final String A_URL = "foo url";
+  private static final String ANOTHER_URL = "bar url";
+
+  private static final long DATE = 1_600_000_000_000L;
+  private static final long DATE_LATER = 1_700_000_000_000L;
+
+  private System2 system2 = mock(System2.class);
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public DbTester dbTester = DbTester.create(system2);
+  private DbClient dbClient = dbTester.getDbClient();
+  private DbSession dbSession = dbTester.getSession();
+
+  private UuidFactory uuidFactory = mock(UuidFactory.class);
+  private ProjectAlmBindingDao underTest = new ProjectAlmBindingDao(system2, uuidFactory);
+
+  @Test
+  public void insert_throws_NPE_if_alm_is_null() {
+    expectAlmNPE();
+
+    underTest.insertOrUpdate(dbSession, null, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL);
+  }
+
+  @Test
+  public void insert_throws_IAE_if_repo_id_is_null() {
+    expectRepoIdNullOrEmptyIAE();
+
+    underTest.insertOrUpdate(dbSession, GITHUB, null, A_UUID, A_GITHUB_SLUG, A_URL);
+  }
+
+  @Test
+  public void insert_throws_IAE_if_repo_id_is_empty() {
+    expectRepoIdNullOrEmptyIAE();
+
+    underTest.insertOrUpdate(dbSession, GITHUB, EMPTY_STRING, A_UUID, A_GITHUB_SLUG, A_URL);
+  }
+
+  @Test
+  public void insert_throws_IAE_if_project_uuid_is_null() {
+    expectProjectUuidNullOrEmptyIAE();
+
+    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, null, A_GITHUB_SLUG, A_URL);
+  }
+
+  @Test
+  public void insert_throws_IAE_if_project_uuid_is_empty() {
+    expectProjectUuidNullOrEmptyIAE();
+
+    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, EMPTY_STRING, A_GITHUB_SLUG, A_URL);
+  }
+
+  @Test
+  public void insert_throws_IAE_if_url_is_null() {
+    expectUrlNullOrEmptyIAE();
+
+    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, null);
+  }
+
+  @Test
+  public void insert_throws_IAE_if_url_is_empty() {
+    expectUrlNullOrEmptyIAE();
+
+    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, EMPTY_STRING);
+  }
+
+  @Test
+  public void insert() {
+    when(system2.now()).thenReturn(DATE);
+    when(uuidFactory.create()).thenReturn("uuid1");
+    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL);
+
+    assertThatProjectAlmBinding(GITHUB, A_REPO)
+      .hasProjectUuid(A_UUID)
+      .hasGithubSlug(A_GITHUB_SLUG)
+      .hasUrl(A_URL)
+      .hasCreatedAt(DATE)
+      .hasUpdatedAt(DATE);
+  }
+
+  @Test
+  public void update() {
+    when(system2.now()).thenReturn(DATE);
+    when(uuidFactory.create()).thenReturn("uuid1");
+    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL);
+
+    when(system2.now()).thenReturn(DATE_LATER);
+    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, ANOTHER_UUID, ANOTHER_GITHUB_SLUG, ANOTHER_URL);
+
+    assertThatProjectAlmBinding(GITHUB, A_REPO)
+      .hasProjectUuid(ANOTHER_UUID)
+      .hasGithubSlug(ANOTHER_GITHUB_SLUG)
+      .hasUrl(ANOTHER_URL)
+      .hasCreatedAt(DATE)
+      .hasUpdatedAt(DATE_LATER);
+  }
+
+  @Test
+  public void insert_multiple() {
+    when(system2.now()).thenReturn(DATE);
+    when(uuidFactory.create()).thenReturn("uuid1").thenReturn("uuid2");
+    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL);
+    underTest.insertOrUpdate(dbSession, GITHUB, ANOTHER_REPO, ANOTHER_UUID, ANOTHER_GITHUB_SLUG, ANOTHER_URL);
+
+    assertThatProjectAlmBinding(GITHUB, A_REPO)
+      .hasProjectUuid(A_UUID)
+      .hasGithubSlug(A_GITHUB_SLUG)
+      .hasUrl(A_URL)
+      .hasCreatedAt(DATE)
+      .hasUpdatedAt(DATE);
+
+    assertThatProjectAlmBinding(GITHUB, ANOTHER_REPO)
+      .hasProjectUuid(ANOTHER_UUID)
+      .hasGithubSlug(ANOTHER_GITHUB_SLUG)
+      .hasUrl(ANOTHER_URL)
+      .hasCreatedAt(DATE)
+      .hasUpdatedAt(DATE);
+  }
+
+  @Test
+  public void select_by_repo_id() {
+    when(system2.now()).thenReturn(DATE);
+    when(uuidFactory.create())
+      .thenReturn("uuid1")
+      .thenReturn("uuid2")
+      .thenReturn("uuid3");
+    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL);
+    underTest.insertOrUpdate(dbSession, GITHUB, ANOTHER_REPO, ANOTHER_UUID, null, ANOTHER_URL);
+    underTest.insertOrUpdate(dbSession, BITBUCKETCLOUD, ANOTHER_REPO, "foo", null, "http://foo");
+
+    assertThat(underTest.selectByRepoId(dbSession, GITHUB, "foo")).isNotPresent();
+
+    Optional<ProjectAlmBindingDto> dto = underTest.selectByRepoId(dbSession, GITHUB, A_REPO);
+    assertThat(dto).isPresent();
+    assertThat(dto.get().getUuid()).isEqualTo("uuid1");
+    assertThat(dto.get().getAlm()).contains(GITHUB);
+    assertThat(dto.get().getRepoId()).isEqualTo(A_REPO);
+    assertThat(dto.get().getProjectUuid()).isEqualTo(A_UUID);
+    assertThat(dto.get().getUrl()).isEqualTo(A_URL);
+    assertThat(dto.get().getGithubSlug()).isEqualTo(A_GITHUB_SLUG);
+  }
+
+  @Test
+  public void select_by_project_uuid() {
+    when(system2.now()).thenReturn(DATE);
+    when(uuidFactory.create())
+      .thenReturn("uuid1")
+      .thenReturn("uuid2")
+      .thenReturn("uuid3");
+    underTest.insertOrUpdate(dbSession, BITBUCKETCLOUD, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL);
+    underTest.insertOrUpdate(dbSession, BITBUCKETCLOUD, ANOTHER_REPO, ANOTHER_UUID, null, ANOTHER_URL);
+    underTest.insertOrUpdate(dbSession, GITHUB, ANOTHER_REPO, "foo", null, "http://foo");
+
+    assertThat(underTest.selectByProjectUuid(dbSession, "missing")).isNotPresent();
+
+    Optional<ProjectAlmBindingDto> dto = underTest.selectByProjectUuid(dbSession, A_UUID);
+    assertThat(dto).isPresent();
+    assertThat(dto.get().getUuid()).isEqualTo("uuid1");
+    assertThat(dto.get().getAlm()).contains(BITBUCKETCLOUD);
+    assertThat(dto.get().getRepoId()).isEqualTo(A_REPO);
+    assertThat(dto.get().getProjectUuid()).isEqualTo(A_UUID);
+    assertThat(dto.get().getUrl()).isEqualTo(A_URL);
+    assertThat(dto.get().getGithubSlug()).isEqualTo(A_GITHUB_SLUG);
+  }
+
+  @Test
+  public void select_by_repo_ids() {
+    when(system2.now()).thenReturn(DATE);
+    when(uuidFactory.create())
+      .thenReturn("uuid1")
+      .thenReturn("uuid2")
+      .thenReturn("uuid3");
+
+    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL);
+    underTest.insertOrUpdate(dbSession, GITHUB, ANOTHER_REPO, ANOTHER_UUID, null, ANOTHER_URL);
+    underTest.insertOrUpdate(dbSession, BITBUCKETCLOUD, ANOTHER_REPO, "foo", null, "http://foo");
+
+    assertThat(underTest.selectByRepoIds(dbSession, GITHUB, Arrays.asList(A_REPO, ANOTHER_REPO, "foo")))
+      .extracting(ProjectAlmBindingDto::getUuid, t -> t.getAlm().get(), ProjectAlmBindingDto::getRepoId, ProjectAlmBindingDto::getProjectUuid,
+        ProjectAlmBindingDto::getUrl, ProjectAlmBindingDto::getGithubSlug)
+      .containsExactlyInAnyOrder(
+        tuple("uuid1", GITHUB, A_REPO, A_UUID, A_URL, A_GITHUB_SLUG),
+        tuple("uuid2", GITHUB, ANOTHER_REPO, ANOTHER_UUID, ANOTHER_URL, null));
+  }
+
+  @Test
+  public void findProjectKey_throws_NPE_when_alm_is_null() {
+    expectAlmNPE();
+
+    underTest.findProjectKey(dbSession, null, A_REPO);
+  }
+
+  @Test
+  public void findProjectKey_throws_IAE_when_repo_id_is_null() {
+    expectRepoIdNullOrEmptyIAE();
+
+    underTest.findProjectKey(dbSession, GITHUB, null);
+  }
+
+  @Test
+  public void findProjectKey_throws_IAE_when_repo_id_is_empty() {
+    expectRepoIdNullOrEmptyIAE();
+
+    underTest.findProjectKey(dbSession, GITHUB, EMPTY_STRING);
+  }
+
+  @Test
+  public void findProjectKey_returns_empty_when_entry_does_not_exist_in_DB() {
+    assertThat(underTest.findProjectKey(dbSession, GITHUB, A_REPO)).isEmpty();
+  }
+
+  @Test
+  public void findProjectKey_returns_projectKey_when_entry_exists() {
+    String projectKey = randomAlphabetic(10);
+    ComponentDto project = createProject(projectKey);
+    when(uuidFactory.create()).thenReturn("uuid1");
+    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, project.projectUuid(), A_GITHUB_SLUG, A_URL);
+
+    assertThat(underTest.findProjectKey(dbSession, GITHUB, A_REPO)).contains(projectKey);
+  }
+
+  private void expectAlmNPE() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("alm can't be null");
+  }
+
+  private void expectRepoIdNullOrEmptyIAE() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("repoId can't be null nor empty");
+  }
+
+  private void expectProjectUuidNullOrEmptyIAE() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("projectUuid can't be null nor empty");
+  }
+
+  private void expectUrlNullOrEmptyIAE() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("url can't be null nor empty");
+  }
+
+  private ProjectAlmBindingAssert assertThatProjectAlmBinding(ALM alm, String repoId) {
+    return new ProjectAlmBindingAssert(dbTester, dbSession, alm, repoId);
+  }
+
+  private static class ProjectAlmBindingAssert extends AbstractAssert<ProjectAlmBindingAssert, ProjectAlmBinding> {
+
+    private ProjectAlmBindingAssert(DbTester dbTester, DbSession dbSession, ALM alm, String repoId) {
+      super(asProjectAlmBinding(dbTester, dbSession, alm, repoId), ProjectAlmBindingAssert.class);
+    }
+
+    private static ProjectAlmBinding asProjectAlmBinding(DbTester dbTester, DbSession dbSession, ALM alm, String repoId) {
+      List<Map<String, Object>> rows = dbTester.select(
+        dbSession,
+        "select" +
+          " project_uuid as \"projectUuid\", github_slug as \"githubSlug\", url as \"url\", " +
+          " created_at as \"createdAt\", updated_at as \"updatedAt\"" +
+          " from project_alm_bindings" +
+          " where alm_id='" + alm.getId() + "' and repo_id='" + repoId + "'");
+      if (rows.isEmpty()) {
+        return null;
+      }
+      if (rows.size() > 1) {
+        throw new IllegalStateException("Unique index violation");
+      }
+      return new ProjectAlmBinding(
+        (String) rows.get(0).get("projectUuid"),
+        (String) rows.get(0).get("githubSlug"),
+        (String) rows.get(0).get("url"),
+        (Long) rows.get(0).get("createdAt"),
+        (Long) rows.get(0).get("updatedAt"));
+    }
+
+    public void doesNotExist() {
+      isNull();
+    }
+
+    ProjectAlmBindingAssert hasProjectUuid(String expected) {
+      isNotNull();
+
+      if (!Objects.equals(actual.projectUuid, expected)) {
+        failWithMessage("Expected Project ALM Binding to have column PROJECT_UUID to be <%s> but was <%s>", expected, actual.projectUuid);
+      }
+      return this;
+    }
+
+    ProjectAlmBindingAssert hasGithubSlug(String expected) {
+      isNotNull();
+
+      if (!Objects.equals(actual.githubSlug, expected)) {
+        failWithMessage("Expected Project ALM Binding to have column GITHUB_SLUG to be <%s> but was <%s>", expected, actual.githubSlug);
+      }
+      return this;
+    }
+
+    ProjectAlmBindingAssert hasUrl(String expected) {
+      isNotNull();
+
+      if (!Objects.equals(actual.url, expected)) {
+        failWithMessage("Expected Project ALM Binding to have column URL to be <%s> but was <%s>", expected, actual.url);
+      }
+      return this;
+    }
+
+    ProjectAlmBindingAssert hasCreatedAt(long expected) {
+      isNotNull();
+
+      if (!Objects.equals(actual.createdAt, expected)) {
+        failWithMessage("Expected Project ALM Binding to have column CREATED_AT to be <%s> but was <%s>", expected, actual.createdAt);
+      }
+
+      return this;
+    }
+
+    ProjectAlmBindingAssert hasUpdatedAt(long expected) {
+      isNotNull();
+
+      if (!Objects.equals(actual.updatedAt, expected)) {
+        failWithMessage("Expected Project ALM Binding to have column UPDATED_AT to be <%s> but was <%s>", expected, actual.updatedAt);
+      }
+
+      return this;
+    }
+
+  }
+
+  private static final class ProjectAlmBinding {
+    private final String projectUuid;
+    private final String githubSlug;
+    private final String url;
+    private final Long createdAt;
+    private final Long updatedAt;
+
+    ProjectAlmBinding(@Nullable String projectUuid, @Nullable String githubSlug, @Nullable String url, @Nullable Long createdAt, @Nullable Long updatedAt) {
+      this.projectUuid = projectUuid;
+      this.githubSlug = githubSlug;
+      this.url = url;
+      this.createdAt = createdAt;
+      this.updatedAt = updatedAt;
+    }
+  }
+
+  private ComponentDto createProject(String projectKey) {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert()).setDbKey(projectKey);
+    dbClient.componentDao().insert(dbSession, project);
+    dbSession.commit();
+    return project;
+  }
+}
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/alm/ProjectAlmBindingsDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/alm/ProjectAlmBindingsDaoTest.java
deleted file mode 100644 (file)
index 0db93b0..0000000
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
- * 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.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-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.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.tuple;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.alm.ALM.BITBUCKETCLOUD;
-import static org.sonar.db.alm.ALM.GITHUB;
-
-public class ProjectAlmBindingsDaoTest {
-
-  private static final String A_UUID = "abcde1234";
-  private static final String ANOTHER_UUID = "xyz789";
-  private static final String EMPTY_STRING = "";
-
-  private static final String A_REPO = "my_repo";
-  private static final String ANOTHER_REPO = "another_repo";
-
-  private static final String A_GITHUB_SLUG = null;
-  private static final String ANOTHER_GITHUB_SLUG = "example/foo";
-
-  private static final String A_URL = "foo url";
-  private static final String ANOTHER_URL = "bar url";
-
-  private static final long DATE = 1_600_000_000_000L;
-  private static final long DATE_LATER = 1_700_000_000_000L;
-
-  private System2 system2 = mock(System2.class);
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-  @Rule
-  public DbTester dbTester = DbTester.create(system2);
-  private DbClient dbClient = dbTester.getDbClient();
-  private DbSession dbSession = dbTester.getSession();
-
-  private UuidFactory uuidFactory = mock(UuidFactory.class);
-  private ProjectAlmBindingsDao underTest = new ProjectAlmBindingsDao(system2, uuidFactory);
-
-  @Test
-  public void insert_throws_NPE_if_alm_is_null() {
-    expectAlmNPE();
-
-    underTest.insertOrUpdate(dbSession, null, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL);
-  }
-
-  @Test
-  public void insert_throws_IAE_if_repo_id_is_null() {
-    expectRepoIdNullOrEmptyIAE();
-
-    underTest.insertOrUpdate(dbSession, GITHUB, null, A_UUID, A_GITHUB_SLUG, A_URL);
-  }
-
-  @Test
-  public void insert_throws_IAE_if_repo_id_is_empty() {
-    expectRepoIdNullOrEmptyIAE();
-
-    underTest.insertOrUpdate(dbSession, GITHUB, EMPTY_STRING, A_UUID, A_GITHUB_SLUG, A_URL);
-  }
-
-  @Test
-  public void insert_throws_IAE_if_project_uuid_is_null() {
-    expectProjectUuidNullOrEmptyIAE();
-
-    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, null, A_GITHUB_SLUG, A_URL);
-  }
-
-  @Test
-  public void insert_throws_IAE_if_project_uuid_is_empty() {
-    expectProjectUuidNullOrEmptyIAE();
-
-    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, EMPTY_STRING, A_GITHUB_SLUG, A_URL);
-  }
-
-  @Test
-  public void insert_throws_IAE_if_url_is_null() {
-    expectUrlNullOrEmptyIAE();
-
-    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, null);
-  }
-
-  @Test
-  public void insert_throws_IAE_if_url_is_empty() {
-    expectUrlNullOrEmptyIAE();
-
-    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, EMPTY_STRING);
-  }
-
-  @Test
-  public void insert() {
-    when(system2.now()).thenReturn(DATE);
-    when(uuidFactory.create()).thenReturn("uuid1");
-    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL);
-
-    assertThatProjectAlmBinding(GITHUB, A_REPO)
-      .hasProjectUuid(A_UUID)
-      .hasGithubSlug(A_GITHUB_SLUG)
-      .hasUrl(A_URL)
-      .hasCreatedAt(DATE)
-      .hasUpdatedAt(DATE);
-  }
-
-  @Test
-  public void update() {
-    when(system2.now()).thenReturn(DATE);
-    when(uuidFactory.create()).thenReturn("uuid1");
-    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL);
-
-    when(system2.now()).thenReturn(DATE_LATER);
-    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, ANOTHER_UUID, ANOTHER_GITHUB_SLUG, ANOTHER_URL);
-
-    assertThatProjectAlmBinding(GITHUB, A_REPO)
-      .hasProjectUuid(ANOTHER_UUID)
-      .hasGithubSlug(ANOTHER_GITHUB_SLUG)
-      .hasUrl(ANOTHER_URL)
-      .hasCreatedAt(DATE)
-      .hasUpdatedAt(DATE_LATER);
-  }
-
-  @Test
-  public void insert_multiple() {
-    when(system2.now()).thenReturn(DATE);
-    when(uuidFactory.create()).thenReturn("uuid1").thenReturn("uuid2");
-    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL);
-    underTest.insertOrUpdate(dbSession, GITHUB, ANOTHER_REPO, ANOTHER_UUID, ANOTHER_GITHUB_SLUG, ANOTHER_URL);
-
-    assertThatProjectAlmBinding(GITHUB, A_REPO)
-      .hasProjectUuid(A_UUID)
-      .hasGithubSlug(A_GITHUB_SLUG)
-      .hasUrl(A_URL)
-      .hasCreatedAt(DATE)
-      .hasUpdatedAt(DATE);
-
-    assertThatProjectAlmBinding(GITHUB, ANOTHER_REPO)
-      .hasProjectUuid(ANOTHER_UUID)
-      .hasGithubSlug(ANOTHER_GITHUB_SLUG)
-      .hasUrl(ANOTHER_URL)
-      .hasCreatedAt(DATE)
-      .hasUpdatedAt(DATE);
-  }
-
-  @Test
-  public void select_by_repo_id() {
-    when(system2.now()).thenReturn(DATE);
-    when(uuidFactory.create())
-      .thenReturn("uuid1")
-      .thenReturn("uuid2")
-      .thenReturn("uuid3");
-    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL);
-    underTest.insertOrUpdate(dbSession, GITHUB, ANOTHER_REPO, ANOTHER_UUID, null, ANOTHER_URL);
-    underTest.insertOrUpdate(dbSession, BITBUCKETCLOUD, ANOTHER_REPO, "foo", null, "http://foo");
-
-    assertThat(underTest.selectByRepoId(dbSession, GITHUB, "foo")).isNotPresent();
-
-    Optional<ProjectAlmBindingDto> dto = underTest.selectByRepoId(dbSession, GITHUB, A_REPO);
-    assertThat(dto).isPresent();
-    assertThat(dto.get().getUuid()).isEqualTo("uuid1");
-    assertThat(dto.get().getAlm()).contains(GITHUB);
-    assertThat(dto.get().getRepoId()).isEqualTo(A_REPO);
-    assertThat(dto.get().getProjectUuid()).isEqualTo(A_UUID);
-    assertThat(dto.get().getUrl()).isEqualTo(A_URL);
-    assertThat(dto.get().getGithubSlug()).isEqualTo(A_GITHUB_SLUG);
-  }
-
-  @Test
-  public void select_by_project_uuid() {
-    when(system2.now()).thenReturn(DATE);
-    when(uuidFactory.create())
-      .thenReturn("uuid1")
-      .thenReturn("uuid2")
-      .thenReturn("uuid3");
-    underTest.insertOrUpdate(dbSession, BITBUCKETCLOUD, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL);
-    underTest.insertOrUpdate(dbSession, BITBUCKETCLOUD, ANOTHER_REPO, ANOTHER_UUID, null, ANOTHER_URL);
-    underTest.insertOrUpdate(dbSession, GITHUB, ANOTHER_REPO, "foo", null, "http://foo");
-
-    assertThat(underTest.selectByProjectUuid(dbSession, "missing")).isNotPresent();
-
-    Optional<ProjectAlmBindingDto> dto = underTest.selectByProjectUuid(dbSession, A_UUID);
-    assertThat(dto).isPresent();
-    assertThat(dto.get().getUuid()).isEqualTo("uuid1");
-    assertThat(dto.get().getAlm()).contains(BITBUCKETCLOUD);
-    assertThat(dto.get().getRepoId()).isEqualTo(A_REPO);
-    assertThat(dto.get().getProjectUuid()).isEqualTo(A_UUID);
-    assertThat(dto.get().getUrl()).isEqualTo(A_URL);
-    assertThat(dto.get().getGithubSlug()).isEqualTo(A_GITHUB_SLUG);
-  }
-
-  @Test
-  public void select_by_repo_ids() {
-    when(system2.now()).thenReturn(DATE);
-    when(uuidFactory.create())
-      .thenReturn("uuid1")
-      .thenReturn("uuid2")
-      .thenReturn("uuid3");
-
-    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL);
-    underTest.insertOrUpdate(dbSession, GITHUB, ANOTHER_REPO, ANOTHER_UUID, null, ANOTHER_URL);
-    underTest.insertOrUpdate(dbSession, BITBUCKETCLOUD, ANOTHER_REPO, "foo", null, "http://foo");
-
-    assertThat(underTest.selectByRepoIds(dbSession, GITHUB, Arrays.asList(A_REPO, ANOTHER_REPO, "foo")))
-      .extracting(ProjectAlmBindingDto::getUuid, t -> t.getAlm().get(), ProjectAlmBindingDto::getRepoId, ProjectAlmBindingDto::getProjectUuid,
-        ProjectAlmBindingDto::getUrl, ProjectAlmBindingDto::getGithubSlug)
-      .containsExactlyInAnyOrder(
-        tuple("uuid1", GITHUB, A_REPO, A_UUID, A_URL, A_GITHUB_SLUG),
-        tuple("uuid2", GITHUB, ANOTHER_REPO, ANOTHER_UUID, ANOTHER_URL, null));
-  }
-
-  @Test
-  public void findProjectKey_throws_NPE_when_alm_is_null() {
-    expectAlmNPE();
-
-    underTest.findProjectKey(dbSession, null, A_REPO);
-  }
-
-  @Test
-  public void findProjectKey_throws_IAE_when_repo_id_is_null() {
-    expectRepoIdNullOrEmptyIAE();
-
-    underTest.findProjectKey(dbSession, GITHUB, null);
-  }
-
-  @Test
-  public void findProjectKey_throws_IAE_when_repo_id_is_empty() {
-    expectRepoIdNullOrEmptyIAE();
-
-    underTest.findProjectKey(dbSession, GITHUB, EMPTY_STRING);
-  }
-
-  @Test
-  public void findProjectKey_returns_empty_when_entry_does_not_exist_in_DB() {
-    assertThat(underTest.findProjectKey(dbSession, GITHUB, A_REPO)).isEmpty();
-  }
-
-  @Test
-  public void findProjectKey_returns_projectKey_when_entry_exists() {
-    String projectKey = randomAlphabetic(10);
-    ComponentDto project = createProject(projectKey);
-    when(uuidFactory.create()).thenReturn("uuid1");
-    underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, project.projectUuid(), A_GITHUB_SLUG, A_URL);
-
-    assertThat(underTest.findProjectKey(dbSession, GITHUB, A_REPO)).contains(projectKey);
-  }
-
-  private void expectAlmNPE() {
-    expectedException.expect(NullPointerException.class);
-    expectedException.expectMessage("alm can't be null");
-  }
-
-  private void expectRepoIdNullOrEmptyIAE() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("repoId can't be null nor empty");
-  }
-
-  private void expectProjectUuidNullOrEmptyIAE() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("projectUuid can't be null nor empty");
-  }
-
-  private void expectUrlNullOrEmptyIAE() {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("url can't be null nor empty");
-  }
-
-  private ProjectAlmBindingAssert assertThatProjectAlmBinding(ALM alm, String repoId) {
-    return new ProjectAlmBindingAssert(dbTester, dbSession, alm, repoId);
-  }
-
-  private static class ProjectAlmBindingAssert extends AbstractAssert<ProjectAlmBindingAssert, ProjectAlmBinding> {
-
-    private ProjectAlmBindingAssert(DbTester dbTester, DbSession dbSession, ALM alm, String repoId) {
-      super(asProjectAlmBinding(dbTester, dbSession, alm, repoId), ProjectAlmBindingAssert.class);
-    }
-
-    private static ProjectAlmBinding asProjectAlmBinding(DbTester dbTester, DbSession dbSession, ALM alm, String repoId) {
-      List<Map<String, Object>> rows = dbTester.select(
-        dbSession,
-        "select" +
-          " project_uuid as \"projectUuid\", github_slug as \"githubSlug\", url as \"url\", " +
-          " created_at as \"createdAt\", updated_at as \"updatedAt\"" +
-          " from project_alm_bindings" +
-          " where alm_id='" + alm.getId() + "' and repo_id='" + repoId + "'");
-      if (rows.isEmpty()) {
-        return null;
-      }
-      if (rows.size() > 1) {
-        throw new IllegalStateException("Unique index violation");
-      }
-      return new ProjectAlmBinding(
-        (String) rows.get(0).get("projectUuid"),
-        (String) rows.get(0).get("githubSlug"),
-        (String) rows.get(0).get("url"),
-        (Long) rows.get(0).get("createdAt"),
-        (Long) rows.get(0).get("updatedAt"));
-    }
-
-    public void doesNotExist() {
-      isNull();
-    }
-
-    ProjectAlmBindingAssert hasProjectUuid(String expected) {
-      isNotNull();
-
-      if (!Objects.equals(actual.projectUuid, expected)) {
-        failWithMessage("Expected Project ALM Binding to have column PROJECT_UUID to be <%s> but was <%s>", expected, actual.projectUuid);
-      }
-      return this;
-    }
-
-    ProjectAlmBindingAssert hasGithubSlug(String expected) {
-      isNotNull();
-
-      if (!Objects.equals(actual.githubSlug, expected)) {
-        failWithMessage("Expected Project ALM Binding to have column GITHUB_SLUG to be <%s> but was <%s>", expected, actual.githubSlug);
-      }
-      return this;
-    }
-
-    ProjectAlmBindingAssert hasUrl(String expected) {
-      isNotNull();
-
-      if (!Objects.equals(actual.url, expected)) {
-        failWithMessage("Expected Project ALM Binding to have column URL to be <%s> but was <%s>", expected, actual.url);
-      }
-      return this;
-    }
-
-    ProjectAlmBindingAssert hasCreatedAt(long expected) {
-      isNotNull();
-
-      if (!Objects.equals(actual.createdAt, expected)) {
-        failWithMessage("Expected Project ALM Binding to have column CREATED_AT to be <%s> but was <%s>", expected, actual.createdAt);
-      }
-
-      return this;
-    }
-
-    ProjectAlmBindingAssert hasUpdatedAt(long expected) {
-      isNotNull();
-
-      if (!Objects.equals(actual.updatedAt, expected)) {
-        failWithMessage("Expected Project ALM Binding to have column UPDATED_AT to be <%s> but was <%s>", expected, actual.updatedAt);
-      }
-
-      return this;
-    }
-
-  }
-
-  private static final class ProjectAlmBinding {
-    private final String projectUuid;
-    private final String githubSlug;
-    private final String url;
-    private final Long createdAt;
-    private final Long updatedAt;
-
-    ProjectAlmBinding(@Nullable String projectUuid, @Nullable String githubSlug, @Nullable String url, @Nullable Long createdAt, @Nullable Long updatedAt) {
-      this.projectUuid = projectUuid;
-      this.githubSlug = githubSlug;
-      this.url = url;
-      this.createdAt = createdAt;
-      this.updatedAt = updatedAt;
-    }
-  }
-
-  private ComponentDto createProject(String projectKey) {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert()).setDbKey(projectKey);
-    dbClient.componentDao().insert(dbSession, project);
-    dbSession.commit();
-    return project;
-  }
-}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v75/CreateOrganizationsAlmBindingsTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v75/CreateOrganizationsAlmBindingsTable.java
new file mode 100644 (file)
index 0000000..6764556
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * 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.v75;
+
+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.newVarcharColumnDefBuilder;
+
+public class CreateOrganizationsAlmBindingsTable extends DdlChange {
+
+  private static final String TABLE_NAME = "organization_alm_bindings";
+
+  private static final VarcharColumnDef UUID_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("uuid")
+    .setIsNullable(false)
+    .setLimit(VarcharColumnDef.UUID_SIZE)
+    .build();
+  private static final VarcharColumnDef ORGANIZATION_UUID_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("organization_uuid")
+    .setIsNullable(false)
+    .setLimit(VarcharColumnDef.UUID_SIZE)
+    .build();
+  private static final VarcharColumnDef ALM_APP_INSTALL_UUID_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("alm_app_install_uuid")
+    .setIsNullable(false)
+    .setLimit(VarcharColumnDef.UUID_SIZE)
+    .build();
+  private static final VarcharColumnDef ALM_ID_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("alm_id")
+    .setIsNullable(false)
+    .setLimit(40)
+    .build();
+  private static final VarcharColumnDef URL_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("url")
+    .setIsNullable(false)
+    .setLimit(2000)
+    .build();
+  private static final VarcharColumnDef USER_UUID_COLUMN = newVarcharColumnDefBuilder()
+    .setColumnName("user_uuid")
+    .setIsNullable(false)
+    .setLimit(255)
+    .build();
+  private static final BigIntegerColumnDef CREATED_AT_COLUMN = newBigIntegerColumnDefBuilder()
+    .setColumnName("created_at")
+    .setIsNullable(false)
+    .build();
+
+  public CreateOrganizationsAlmBindingsTable(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(ORGANIZATION_UUID_COLUMN)
+        .addColumn(ALM_APP_INSTALL_UUID_COLUMN)
+        .addColumn(ALM_ID_COLUMN)
+        .addColumn(URL_COLUMN)
+        .addColumn(USER_UUID_COLUMN)
+        .addColumn(CREATED_AT_COLUMN)
+        .build());
+
+      context.execute(new CreateIndexBuilder(getDialect())
+        .addColumn(ORGANIZATION_UUID_COLUMN)
+        .setUnique(true)
+        .setTable(TABLE_NAME)
+        .setName("org_alm_bindings_org")
+        .build());
+
+      context.execute(new CreateIndexBuilder(getDialect())
+        .addColumn(ALM_APP_INSTALL_UUID_COLUMN)
+        .setUnique(true)
+        .setTable(TABLE_NAME)
+        .setName("org_alm_bindings_install")
+        .build());
+    }
+  }
+
+  private boolean tableExists() throws SQLException {
+    try (Connection connection = getDatabase().getDataSource().getConnection()) {
+      return DatabaseUtils.tableExists(TABLE_NAME, connection);
+    }
+  }
+}
index ec7c0d05c35176bb1d44db5f0c179974cb132214..65c33aa424e931622aae41e06bb5f77cdd808e4e 100644 (file)
@@ -28,6 +28,7 @@ public class DbVersion75 implements DbVersion {
   public void addSteps(MigrationStepRegistry registry) {
     registry
       .add(2400, "Add column IS_OWNER_USER in ALM_APP_INSTALLS", AddIsOwnerUserColumnInAlmAppInstall.class)
+      .add(2401, "Create ORGANIZATION_ALM_BINDINGS table", CreateOrganizationsAlmBindingsTable.class)
     ;
   }
 }
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v75/CreateOrganizationsAlmBindingsTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v75/CreateOrganizationsAlmBindingsTableTest.java
new file mode 100644 (file)
index 0000000..7cac352
--- /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.v75;
+
+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;
+
+public class CreateOrganizationsAlmBindingsTableTest {
+
+  private static final String TABLE = "organization_alm_bindings";
+
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createEmpty();
+
+  private CreateOrganizationsAlmBindingsTable underTest = new CreateOrganizationsAlmBindingsTable(db.database());
+
+  @Test
+  public void creates_table() 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, "organization_uuid", VARCHAR, 40, false);
+    db.assertColumnDefinition(TABLE, "alm_app_install_uuid", VARCHAR, 40, false);
+    db.assertColumnDefinition(TABLE, "alm_id", Types.VARCHAR, 40, false);
+    db.assertColumnDefinition(TABLE, "url", Types.VARCHAR, 2000, false);
+    db.assertColumnDefinition(TABLE, "user_uuid", Types.VARCHAR, 255, false);
+    db.assertColumnDefinition(TABLE, "created_at", Types.BIGINT, null, false);
+
+    db.assertUniqueIndex(TABLE, "org_alm_bindings_org", "organization_uuid");
+    db.assertUniqueIndex(TABLE, "org_alm_bindings_install", "alm_app_install_uuid");
+  }
+
+}
index c6d922ba3b0c9662568f8b7729d64557378b3b2d..0b940dbcb16fa3d85a98dfbc8941c2d52aa4812d 100644 (file)
@@ -35,6 +35,6 @@ public class DbVersion75Test {
 
   @Test
   public void verify_migration_count() {
-    verifyMigrationCount(underTest, 1);
+    verifyMigrationCount(underTest, 2);
   }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationAlmBinding.java b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationAlmBinding.java
new file mode 100644 (file)
index 0000000..7fccdc9
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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.organization;
+
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.DbSession;
+import org.sonar.db.organization.OrganizationDto;
+
+@ServerSide
+public interface OrganizationAlmBinding {
+
+  void bindOrganization(DbSession dbSession, OrganizationDto organization, String installationId);
+}
index 60bb5006439ce5609159f9d38f35e11608a2102a..6bb14d6fb72b41267c0377915674756c6875c4fd 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.organization;
 
 import java.util.Optional;
+import java.util.function.Consumer;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.api.web.UserRole;
@@ -72,7 +73,7 @@ public interface OrganizationUpdater {
    * @throws KeyConflictException if an organization with the specified key already exists
    * @throws IllegalArgumentException if any field of {@code newOrganization} is invalid according to {@link OrganizationValidation}
    */
-  OrganizationDto create(DbSession dbSession, UserDto userCreator, NewOrganization newOrganization) throws KeyConflictException;
+  OrganizationDto create(DbSession dbSession, UserDto userCreator, NewOrganization newOrganization, Consumer<OrganizationDto> beforeCommit) throws KeyConflictException;
 
   /**
    * Create a new guarded organization which details are based on the login of the specified User.
index cc6b889a0c8725d058f7afa14060a2964a7f2bc9..5765db7c88cf467cec90a69d515be926681bf6f3 100644 (file)
@@ -94,7 +94,7 @@ public class OrganizationUpdaterImpl implements OrganizationUpdater {
   }
 
   @Override
-  public OrganizationDto create(DbSession dbSession, UserDto userCreator, NewOrganization newOrganization) throws KeyConflictException {
+  public OrganizationDto create(DbSession dbSession, UserDto userCreator, NewOrganization newOrganization, Consumer<OrganizationDto> beforeCommit) throws KeyConflictException {
     validate(newOrganization);
     String key = newOrganization.getKey();
     if (organizationKeyIsUsed(dbSession, key)) {
@@ -103,16 +103,16 @@ public class OrganizationUpdaterImpl implements OrganizationUpdater {
 
     QualityGateDto builtInQualityGate = dbClient.qualityGateDao().selectBuiltIn(dbSession);
     OrganizationDto organization = insertOrganization(dbSession, newOrganization, builtInQualityGate);
+    beforeCommit.accept(organization);
     insertOrganizationMember(dbSession, organization, userCreator.getId());
     dbClient.qualityGateDao().associate(dbSession, uuidFactory.create(), organization, builtInQualityGate);
     GroupDto ownerGroup = insertOwnersGroup(dbSession, organization);
     GroupDto defaultGroup = defaultGroupCreator.create(dbSession, organization.getUuid());
     insertDefaultTemplateOnGroups(dbSession, organization, ownerGroup, defaultGroup);
+    addCurrentUserToGroup(dbSession, ownerGroup, userCreator.getId());
+    addCurrentUserToGroup(dbSession, defaultGroup, userCreator.getId());
     try (DbSession batchDbSession = dbClient.openSession(true)) {
       insertQualityProfiles(dbSession, batchDbSession, organization);
-      addCurrentUserToGroup(dbSession, ownerGroup, userCreator.getId());
-      addCurrentUserToGroup(dbSession, defaultGroup, userCreator.getId());
-
       batchDbSession.commit();
 
       // Elasticsearch is updated when DB session is committed
index 136866aff699cb7c6c833c04bd2381a011c5b6ee..992add6137228da589dff1c98184b30e4b950189 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.organization.OrganizationAlmBinding;
 import org.sonar.server.organization.OrganizationFlags;
 import org.sonar.server.organization.OrganizationUpdater;
 import org.sonar.server.organization.OrganizationValidation;
@@ -38,6 +39,7 @@ import org.sonar.server.user.UserSession;
 import org.sonarqube.ws.Organizations.CreateWsResponse;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
 import static org.sonar.server.organization.OrganizationUpdater.NewOrganization.newOrganizationBuilder;
 import static org.sonar.server.organization.OrganizationValidation.KEY_MAX_LENGTH;
 import static org.sonar.server.organization.OrganizationValidation.KEY_MIN_LENGTH;
@@ -45,7 +47,9 @@ import static org.sonar.server.organization.ws.OrganizationsWsSupport.PARAM_KEY;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 
 public class CreateAction implements OrganizationsWsAction {
+
   private static final String ACTION = "create";
+  private static final String PARAM_INSTALLATION_ID = "installationId";
 
   private final Configuration config;
   private final UserSession userSession;
@@ -54,9 +58,12 @@ public class CreateAction implements OrganizationsWsAction {
   private final OrganizationValidation organizationValidation;
   private final OrganizationUpdater organizationUpdater;
   private final OrganizationFlags organizationFlags;
+  @CheckForNull
+  private final OrganizationAlmBinding organizationAlmBinding;
 
   public CreateAction(Configuration config, UserSession userSession, DbClient dbClient, OrganizationsWsSupport wsSupport,
-                      OrganizationValidation organizationValidation, OrganizationUpdater organizationUpdater, OrganizationFlags organizationFlags) {
+    OrganizationValidation organizationValidation, OrganizationUpdater organizationUpdater, OrganizationFlags organizationFlags,
+    @Nullable OrganizationAlmBinding organizationAlmBinding) {
     this.config = config;
     this.userSession = userSession;
     this.dbClient = dbClient;
@@ -64,6 +71,12 @@ public class CreateAction implements OrganizationsWsAction {
     this.organizationValidation = organizationValidation;
     this.organizationUpdater = organizationUpdater;
     this.organizationFlags = organizationFlags;
+    this.organizationAlmBinding = organizationAlmBinding;
+  }
+
+  public CreateAction(Configuration config, UserSession userSession, DbClient dbClient, OrganizationsWsSupport wsSupport,
+    OrganizationValidation organizationValidation, OrganizationUpdater organizationUpdater, OrganizationFlags organizationFlags) {
+    this(config, userSession, dbClient, wsSupport, organizationValidation, organizationUpdater, organizationFlags, null);
   }
 
   @Override
@@ -77,8 +90,7 @@ public class CreateAction implements OrganizationsWsAction {
       .setSince("6.2")
       .setChangelog(
         new Change("7.4", "Maximal number of character of name and key is 300 characters"),
-        new Change("7.2", "Minimal number of character of name and key is one character")
-      )
+        new Change("7.2", "Minimal number of character of name and key is one character"))
       .setHandler(this);
 
     action.createParam(PARAM_KEY)
@@ -90,6 +102,11 @@ public class CreateAction implements OrganizationsWsAction {
         "When not specified, the key is computed from the name. <br />" +
         "All chars must be lower-case letters (a to z), digits or dash (but dash can neither be trailing nor heading)")
       .setExampleValue("foo-company");
+    action.createParam(PARAM_INSTALLATION_ID)
+      .setRequired(false)
+      .setInternal(true)
+      .setDescription("Installation ID of the GitHub/Bitbucket application")
+      .setExampleValue("387133");
 
     wsSupport.addOrganizationDetailsParams(action, true);
   }
@@ -111,7 +128,7 @@ public class CreateAction implements OrganizationsWsAction {
 
     try (DbSession dbSession = dbClient.openSession(false)) {
       organizationFlags.checkEnabled(dbSession);
-      UserDto currentUser = dbClient.userDao().selectActiveUserByLogin(dbSession, userSession.getLogin());
+      UserDto currentUser = requireNonNull(dbClient.userDao().selectActiveUserByLogin(dbSession, requireNonNull(userSession.getLogin())));
       OrganizationDto organization = organizationUpdater.create(
         dbSession,
         currentUser,
@@ -121,7 +138,8 @@ public class CreateAction implements OrganizationsWsAction {
           .setDescription(description)
           .setUrl(url)
           .setAvatarUrl(avatar)
-          .build());
+          .build(),
+        o -> bindOrganization(request, dbSession, o));
 
       writeResponse(request, response, organization);
     } catch (OrganizationUpdater.KeyConflictException e) {
@@ -130,6 +148,17 @@ public class CreateAction implements OrganizationsWsAction {
     }
   }
 
+  private void bindOrganization(Request request, DbSession dbSession, OrganizationDto organization) {
+    if (organizationAlmBinding == null) {
+      return;
+    }
+    String installationId = request.param(PARAM_INSTALLATION_ID);
+    if (installationId == null) {
+      return;
+    }
+    organizationAlmBinding.bindOrganization(dbSession, organization, installationId);
+  }
+
   @CheckForNull
   private String getAndCheckKey(Request request) {
     String rqstKey = request.param(PARAM_KEY);
index 7b1685f8385ced143b5be21b3fda1e16cda7d0f9..ad2a2d80fce3953ce558d6918b6e04a35080a1af 100644 (file)
@@ -120,6 +120,7 @@ public class DeleteAction implements OrganizationsWsAction {
       deleteGroups(dbSession, organization);
       deleteQualityProfiles(dbSession, organization);
       deleteQualityGates(dbSession, organization);
+      deleteOrganizationAlmBinding(dbSession, organization);
       deleteOrganization(dbSession, organization);
       billingValidations.onDelete(new BillingValidations.Organization(organization.getKey(), organization.getUuid()));
 
@@ -148,16 +149,12 @@ public class DeleteAction implements OrganizationsWsAction {
 
   private void deletePermissions(DbSession dbSession, OrganizationDto organization) {
     dbClient.permissionTemplateDao().deleteByOrganization(dbSession, organization.getUuid());
-    dbSession.commit();
     dbClient.userPermissionDao().deleteByOrganization(dbSession, organization.getUuid());
-    dbSession.commit();
     dbClient.groupPermissionDao().deleteByOrganization(dbSession, organization.getUuid());
-    dbSession.commit();
   }
 
   private void deleteGroups(DbSession dbSession, OrganizationDto organization) {
     dbClient.groupDao().deleteByOrganization(dbSession, organization.getUuid());
-    dbSession.commit();
   }
 
   private void deleteQualityProfiles(DbSession dbSession, OrganizationDto organization) {
@@ -174,6 +171,10 @@ public class DeleteAction implements OrganizationsWsAction {
     dbClient.qualityGateDao().deleteOrgQualityGatesByOrganization(dbSession, organization);
   }
 
+  private void deleteOrganizationAlmBinding(DbSession dbSession, OrganizationDto organization){
+    dbClient.organizationAlmBindingDao().deleteByOrganization(dbSession, organization);
+  }
+
   private void deleteOrganization(DbSession dbSession, OrganizationDto organization) {
     Collection<String> uuids = dbClient.organizationMemberDao().selectUserUuidsByOrganizationUuid(dbSession, organization.getUuid());
     dbClient.organizationMemberDao().deleteByOrganizationUuid(dbSession, organization.getUuid());
index 25f59dd801dd520ecec7b6edcb96cab2ecc951c2..9be33f51eede0312c5370b04b1e6b3bce66acc8e 100644 (file)
@@ -42,7 +42,6 @@ public class OrganizationsWsSupport {
   static final String PARAM_DESCRIPTION = "description";
   static final String PARAM_URL = "url";
   static final String PARAM_AVATAR_URL = "avatar";
-
   static final String PARAM_LOGIN = "login";
 
   private final OrganizationValidation organizationValidation;
@@ -109,10 +108,7 @@ public class OrganizationsWsSupport {
   }
 
   Organization.Builder toOrganization(OrganizationDto dto) {
-    return toOrganization(Organization.newBuilder(), dto);
-  }
-
-  Organization.Builder toOrganization(Organization.Builder builder, OrganizationDto dto) {
+    Organization.Builder builder = Organization.newBuilder();
     builder
       .setName(dto.getName())
       .setKey(dto.getKey())
index d9d9f6835b3a8a73ffb70b665aa2d728269935b8..625a16687d7070ed4d697f840dcfc889d5799b69 100644 (file)
 package org.sonar.server.organization.ws;
 
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
 import org.sonar.api.server.ws.Change;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
+import org.sonar.db.alm.OrganizationAlmBindingDto;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.organization.OrganizationQuery;
 import org.sonar.server.user.UserSession;
@@ -36,6 +40,7 @@ import org.sonarqube.ws.Organizations;
 import org.sonarqube.ws.Organizations.Organization;
 
 import static java.util.Collections.emptySet;
+import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.core.util.stream.MoreCollectors.toSet;
 import static org.sonar.db.Pagination.forPage;
 import static org.sonar.db.organization.OrganizationQuery.newOrganizationQueryBuilder;
@@ -52,12 +57,10 @@ public class SearchAction implements OrganizationsWsAction {
 
   private final DbClient dbClient;
   private final UserSession userSession;
-  private final OrganizationsWsSupport wsSupport;
 
-  public SearchAction(DbClient dbClient, UserSession userSession, OrganizationsWsSupport wsSupport) {
+  public SearchAction(DbClient dbClient, UserSession userSession) {
     this.dbClient = dbClient;
     this.userSession = userSession;
-    this.wsSupport = wsSupport;
   }
 
   @Override
@@ -90,7 +93,7 @@ public class SearchAction implements OrganizationsWsAction {
   @Override
   public void handle(Request request, Response response) throws Exception {
     boolean isMember = request.mandatoryParamAsBoolean(PARAM_MEMBER);
-    if (isMember){
+    if (isMember) {
       userSession.checkLoggedIn();
     }
 
@@ -100,7 +103,9 @@ public class SearchAction implements OrganizationsWsAction {
       Paging paging = buildWsPaging(request, total);
       List<OrganizationDto> organizations = dbClient.organizationDao().selectByQuery(dbSession, dbQuery, forPage(paging.getPageIndex()).andSize(paging.getPageSize()));
       Set<String> adminOrganizationUuids = searchOrganizationWithAdminPermission(dbSession);
-      writeResponse(request, response, organizations, adminOrganizationUuids, paging);
+      Map<String, OrganizationAlmBindingDto> organizationAlmBindingByOrgUuid = dbClient.organizationAlmBindingDao().selectByOrganizations(dbSession, organizations)
+        .stream().collect(MoreCollectors.uniqueIndex(OrganizationAlmBindingDto::getOrganizationUuid));
+      writeResponse(request, response, organizations, adminOrganizationUuids, organizationAlmBindingByOrgUuid, paging);
     }
   }
 
@@ -117,20 +122,38 @@ public class SearchAction implements OrganizationsWsAction {
       : dbClient.organizationDao().selectByPermission(dbSession, userId, ADMINISTER.getKey()).stream().map(OrganizationDto::getUuid).collect(toSet());
   }
 
-  private void writeResponse(Request httpRequest, Response httpResponse, List<OrganizationDto> organizations, Set<String> adminOrganizationUuids, Paging paging) {
+  private static void writeResponse(Request httpRequest, Response httpResponse, List<OrganizationDto> organizations, Set<String> adminOrganizationUuids,
+    Map<String, OrganizationAlmBindingDto> organizationAlmBindingByOrgUuid,
+    Paging paging) {
     Organizations.SearchWsResponse.Builder response = Organizations.SearchWsResponse.newBuilder();
     response.setPaging(paging);
     Organization.Builder wsOrganization = Organization.newBuilder();
     organizations
       .forEach(o -> {
-        boolean isAdmin = adminOrganizationUuids.contains(o.getUuid());
         wsOrganization.clear();
+        boolean isAdmin = adminOrganizationUuids.contains(o.getUuid());
         wsOrganization.setIsAdmin(isAdmin);
-        response.addOrganizations(wsSupport.toOrganization(wsOrganization, o));
+        response.addOrganizations(toOrganization(wsOrganization, o, organizationAlmBindingByOrgUuid.get(o.getUuid())));
       });
     writeProtobuf(response.build(), httpRequest, httpResponse);
   }
 
+  private static Organization.Builder toOrganization(Organization.Builder builder, OrganizationDto organization, @Nullable OrganizationAlmBindingDto organizationAlmBinding) {
+    builder
+      .setName(organization.getName())
+      .setKey(organization.getKey())
+      .setGuarded(organization.isGuarded());
+    setNullable(organization.getDescription(), builder::setDescription);
+    setNullable(organization.getUrl(), builder::setUrl);
+    setNullable(organization.getAvatarUrl(), builder::setAvatar);
+    if (organizationAlmBinding != null) {
+      builder.setAlm(Organization.Alm.newBuilder()
+        .setKey(organizationAlmBinding.getAlm().getId())
+        .setUrl(organizationAlmBinding.getUrl()));
+    }
+    return builder;
+  }
+
   private static Paging buildWsPaging(Request request, int total) {
     return Paging.newBuilder()
       .setPageIndex(request.mandatoryParamAsInt(Param.PAGE))
index 4fb3bf2f420b966297a1119e9ebc06a4da44a5a6..eba360389989f01abebf7b720ba6ae7dd6a46687 100644 (file)
@@ -192,8 +192,10 @@ public class ComponentAction implements NavigationWsAction {
         .map(ALM::getId)
         .map(String::valueOf)
         .orElseThrow(() -> new IllegalStateException("Alm binding id DB has no ALM id"));
-      json.prop("almId", almId)
-        .prop("almRepoUrl", b.getUrl());
+      json.name("alm").beginObject()
+        .prop("key", almId)
+        .prop("url", b.getUrl())
+        .endObject();
     });
   }
 
index f389e9bc94731a4be9c249add1a6b671a0a95580..8d1b7019024586fe59487ec2c4df32a17e043213 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.server.organization;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.Consumer;
 import org.apache.commons.lang.RandomStringUtils;
 import org.junit.Rule;
 import org.junit.Test;
@@ -90,6 +91,9 @@ public class OrganizationUpdaterImplTest {
 
   private System2 system2 = new TestSystem2().setNow(A_DATE);
 
+  private static Consumer<OrganizationDto> EMPTY_ORGANIZATION_CONSUMER = o -> {
+  };
+
   @Rule
   public DbTester db = DbTester.create(system2);
   @Rule
@@ -122,7 +126,7 @@ public class OrganizationUpdaterImplTest {
     UserDto user = db.users().insertUser();
     db.qualityGates().insertBuiltInQualityGate();
 
-    underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+    underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER);
 
     OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get();
     assertThat(organization.getUuid()).isNotEmpty();
@@ -143,7 +147,7 @@ public class OrganizationUpdaterImplTest {
     builtInQProfileRepositoryRule.initialize();
     db.qualityGates().insertBuiltInQualityGate();
 
-    underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+    underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER);
 
     verifyGroupOwners(user, FULL_POPULATED_NEW_ORGANIZATION.getKey(), FULL_POPULATED_NEW_ORGANIZATION.getName());
   }
@@ -154,7 +158,7 @@ public class OrganizationUpdaterImplTest {
     builtInQProfileRepositoryRule.initialize();
     db.qualityGates().insertBuiltInQualityGate();
 
-    underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+    underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER);
 
     verifyMembersGroup(user, FULL_POPULATED_NEW_ORGANIZATION.getKey());
   }
@@ -168,7 +172,7 @@ public class OrganizationUpdaterImplTest {
     underTest.create(dbSession, user, newOrganizationBuilder()
       .setKey("key")
       .setName("name")
-      .build());
+      .build(), EMPTY_ORGANIZATION_CONSUMER);
 
     OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, "key").get();
     assertThat(organization.getKey()).isEqualTo("key");
@@ -185,7 +189,7 @@ public class OrganizationUpdaterImplTest {
     UserDto user = db.users().insertUser();
     db.qualityGates().insertBuiltInQualityGate();
 
-    underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+    underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER);
 
     OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get();
     GroupDto ownersGroup = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Owners").get();
@@ -210,7 +214,7 @@ public class OrganizationUpdaterImplTest {
     builtInQProfileRepositoryRule.initialize();
     db.qualityGates().insertBuiltInQualityGate();
 
-    OrganizationDto result = underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+    OrganizationDto result = underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER);
 
     assertThat(dbClient.organizationMemberDao().select(dbSession, result.getUuid(), user.getId())).isPresent();
     assertThat(userIndex.search(UserQuery.builder().setOrganizationUuid(result.getUuid()).setTextQuery(user.getLogin()).build(), new SearchOptions()).getTotal()).isEqualTo(1L);
@@ -226,7 +230,7 @@ public class OrganizationUpdaterImplTest {
     UserDto user = db.users().insertUser();
     db.qualityGates().insertBuiltInQualityGate();
 
-    underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+    underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER);
 
     OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get();
     List<QProfileDto> profiles = dbClient.qualityProfileDao().selectOrderedByOrganizationUuid(dbSession, organization);
@@ -252,12 +256,27 @@ public class OrganizationUpdaterImplTest {
     builtInQProfileRepositoryRule.initialize();
     UserDto user = db.users().insertUser();
 
-    underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+    underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, o -> {
+    });
 
     OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get();
     assertThat(dbClient.qualityGateDao().selectDefault(dbSession, organization).getUuid()).isEqualTo(builtInQualityGate.getUuid());
   }
 
+  @Test
+  public void create_calls_consumer() throws OrganizationUpdater.KeyConflictException {
+    UserDto user = db.users().insertUser();
+    builtInQProfileRepositoryRule.initialize();
+    db.qualityGates().insertBuiltInQualityGate();
+    Boolean[] isConsumerCalled = new Boolean[]{false};
+
+    underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, o -> {
+      isConsumerCalled[0] = true;
+    });
+
+    assertThat(isConsumerCalled[0]).isEqualTo(true);
+  }
+
   @Test
   public void create_throws_NPE_if_NewOrganization_arg_is_null() throws OrganizationUpdater.KeyConflictException {
     UserDto user = db.users().insertUser();
@@ -265,7 +284,7 @@ public class OrganizationUpdaterImplTest {
     expectedException.expect(NullPointerException.class);
     expectedException.expectMessage("newOrganization can't be null");
 
-    underTest.create(dbSession, user, null);
+    underTest.create(dbSession, user, null, EMPTY_ORGANIZATION_CONSUMER);
   }
 
   @Test
@@ -307,7 +326,7 @@ public class OrganizationUpdaterImplTest {
 
   private void createThrowsExceptionThrownByOrganizationValidation(UserDto user) throws OrganizationUpdater.KeyConflictException {
     try {
-      underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+      underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER);
       fail(exceptionThrownByOrganizationValidation + " should have been thrown");
     } catch (IllegalArgumentException e) {
       assertThat(e).isSameAs(exceptionThrownByOrganizationValidation);
@@ -322,7 +341,7 @@ public class OrganizationUpdaterImplTest {
     expectedException.expect(OrganizationUpdater.KeyConflictException.class);
     expectedException.expectMessage("Organization key '" + FULL_POPULATED_NEW_ORGANIZATION.getKey() + "' is already used");
 
-    underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+    underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION, EMPTY_ORGANIZATION_CONSUMER);
   }
 
   @Test
index 362b1be4bdc22df511ddb81b695901ed4e814b36..3fd6feef739e5aa84745af04d2c5c29d89685a61 100644 (file)
@@ -50,6 +50,7 @@ import org.sonar.db.user.UserMembershipQuery;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.organization.OrganizationAlmBinding;
 import org.sonar.server.organization.OrganizationUpdater;
 import org.sonar.server.organization.OrganizationUpdaterImpl;
 import org.sonar.server.organization.OrganizationValidation;
@@ -72,7 +73,11 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
 import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
 import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.sonar.core.config.CorePropertyDefinitions.ORGANIZATIONS_ANYONE_CAN_CREATE;
 import static org.sonar.server.organization.ws.OrganizationsWsSupport.PARAM_NAME;
 import static org.sonar.server.organization.ws.OrganizationsWsTestSupport.STRING_257_CHARS_LONG;
@@ -107,11 +112,12 @@ public class CreateActionTest {
     userIndexer,
     mock(BuiltInQProfileRepository.class), new DefaultGroupCreatorImpl(dbClient), permissionService);
   private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone().setEnabled(true);
+  private OrganizationAlmBinding organizationAlmBinding = mock(OrganizationAlmBinding.class);
 
   private WsActionTester wsTester = new WsActionTester(
     new CreateAction(settings.asConfig(), userSession, dbClient, new OrganizationsWsSupport(organizationValidation),
       organizationValidation,
-      organizationUpdater, organizationFlags));
+      organizationUpdater, organizationFlags, organizationAlmBinding));
 
   @Test
   public void create_organization() {
@@ -251,6 +257,47 @@ public class CreateActionTest {
     assertThat(organization.getName()).isEqualTo("a");
   }
 
+  @Test
+  public void bind_organization_when_installation_id_is_set() {
+    createUserAndLogInAsSystemAdministrator();
+    db.qualityGates().insertBuiltInQualityGate();
+
+    wsTester.newRequest()
+      .setParam(PARAM_NAME, "foo")
+      .setParam("installationId", "ABCD")
+      .execute();
+
+    verify(organizationAlmBinding).bindOrganization(any(DbSession.class), any(OrganizationDto.class), eq("ABCD"));
+  }
+
+  @Test
+  public void does_not_bind_organization_when_organizationAlmBinding_is_null() {
+    wsTester = new WsActionTester(
+      new CreateAction(settings.asConfig(), userSession, dbClient, new OrganizationsWsSupport(organizationValidation),
+        organizationValidation, organizationUpdater, organizationFlags, null));
+    createUserAndLogInAsSystemAdministrator();
+    db.qualityGates().insertBuiltInQualityGate();
+
+    wsTester.newRequest()
+      .setParam(PARAM_NAME, "foo")
+      .setParam("installationId", "ABCD")
+      .execute();
+
+    verifyZeroInteractions(organizationAlmBinding);
+  }
+
+  @Test
+  public void does_not_bind_organization_when_installation_id_is_not_set() {
+    createUserAndLogInAsSystemAdministrator();
+    db.qualityGates().insertBuiltInQualityGate();
+
+    wsTester.newRequest()
+      .setParam(PARAM_NAME, "foo")
+      .execute();
+
+    verifyZeroInteractions(organizationAlmBinding);
+  }
+
   @Test
   public void request_succeeds_if_user_is_system_administrator_and_logged_in_users_cannot_create_organizations() {
     createUserAndLogInAsSystemAdministrator();
@@ -520,7 +567,7 @@ public class CreateActionTest {
     assertThat(action.isInternal()).isTrue();
     assertThat(action.since()).isEqualTo("6.2");
     assertThat(action.handler()).isNotNull();
-    assertThat(action.params()).hasSize(5);
+    assertThat(action.params()).hasSize(6);
     assertThat(action.responseExample()).isEqualTo(getClass().getResource("create-example.json"));
 
     assertThat(action.param("name"))
@@ -547,6 +594,9 @@ public class CreateActionTest {
       .matches(param -> !param.isRequired())
       .matches(param -> "https://www.foo.com/foo.png".equals(param.exampleValue()))
       .matches(param -> param.description() != null);
+    assertThat(action.param("installationId"))
+      .matches(param -> !param.isRequired())
+      .matches(param -> param.isInternal());
   }
 
   @Test
index 3d709dc4c991ee758a66742ddf392096f313ceb6..3acddd3f63c4f2b6e36bfde574a8016ec303020a 100644 (file)
@@ -37,6 +37,8 @@ import org.sonar.core.util.UuidFactory;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
+import org.sonar.db.alm.ALM;
+import org.sonar.db.alm.AlmAppInstallDto;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
 import org.sonar.db.component.ResourceTypesRule;
@@ -76,6 +78,8 @@ import org.sonar.server.ws.WsActionTester;
 import static com.google.common.collect.ImmutableList.of;
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptySet;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -542,6 +546,21 @@ public class DeleteActionTest {
     verify(billingValidationsProxy).onDelete(any(BillingValidations.Organization.class));
   }
 
+  @Test
+  public void delete_organization_alm_binding() {
+    OrganizationDto organization = db.organizations().insert();
+    String installationId = randomAlphanumeric(10);
+    db.getDbClient().almAppInstallDao().insertOrUpdate(db.getSession(), ALM.GITHUB, randomAlphabetic(13), false, installationId);
+    AlmAppInstallDto almAppInstall = db.getDbClient().almAppInstallDao().selectByInstallationId(db.getSession(), ALM.GITHUB, installationId).get();
+    db.getDbClient().organizationAlmBindingDao().insert(db.getSession(), organization, almAppInstall, randomAlphabetic(10), db.users().insertUser().getUuid());
+    db.commit();
+    logInAsAdministrator(organization);
+
+    sendRequest(organization);
+
+    assertThat(db.getDbClient().organizationAlmBindingDao().selectByOrganization(db.getSession(), organization)).isNotPresent();
+  }
+
   @DataProvider
   public static Object[][] indexOfFailingProjectDeletion() {
     return new Object[][] {
index 1f3db59c0d8925a93549c88328bbc991ca55be1e..7d54fcb892a075b8e05cafb010c7e9f6ae9b5ce9 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.server.organization.ws;
 import com.google.common.base.Joiner;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
 import javax.annotation.Nullable;
 import org.junit.Rule;
@@ -31,11 +32,12 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.System2;
 import org.sonar.core.util.Uuids;
 import org.sonar.db.DbTester;
+import org.sonar.db.alm.AlmAppInstallDto;
+import org.sonar.db.alm.OrganizationAlmBindingDto;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.UserDto;
 import org.sonar.server.exceptions.UnauthorizedException;
-import org.sonar.server.organization.OrganizationValidationImpl;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.WsActionTester;
@@ -45,6 +47,8 @@ import org.sonarqube.ws.Organizations.Organization;
 import org.sonarqube.ws.Organizations.SearchWsResponse;
 
 import static java.lang.String.valueOf;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
 import static org.mockito.Mockito.mock;
@@ -68,7 +72,7 @@ public class SearchActionTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
-  private SearchAction underTest = new SearchAction(db.getDbClient(), userSession, new OrganizationsWsSupport(new OrganizationValidationImpl()));
+  private SearchAction underTest = new SearchAction(db.getDbClient(), userSession);
   private WsActionTester ws = new WsActionTester(underTest);
 
   @Test
@@ -212,6 +216,21 @@ public class SearchActionTest {
       .doesNotContain(organizationWithoutMember.getKey());
   }
 
+  @Test
+  public void return_alm_info() {
+    OrganizationDto organization = db.organizations().insert();
+    AlmAppInstallDto almAppInstall = db.alm().insertAlmAppInstall();
+    OrganizationAlmBindingDto organizationAlmBinding = db.alm().insertOrganizationAlmBinding(organization, almAppInstall);
+    OrganizationDto organizationNotBoundToAlm = db.organizations().insert();
+
+    SearchWsResponse result = call(ws.newRequest());
+
+    Map<String, Organization> orgByKey = result.getOrganizationsList().stream().collect(toMap(Organization::getKey, identity()));
+    assertThat(orgByKey.get(organization.getKey()).getAlm().getKey()).isEqualTo(organizationAlmBinding.getAlm().getId());
+    assertThat(orgByKey.get(organization.getKey()).getAlm().getUrl()).isEqualTo(organizationAlmBinding.getUrl());
+    assertThat(orgByKey.get(organizationNotBoundToAlm.getKey()).hasAlm()).isEqualTo(false);
+  }
+
   @Test
   public void request_on_empty_db_returns_an_empty_organization_list() {
     assertThat(executeRequestAndReturnList(null, null)).isEmpty();
index 9edc7b4733768adaeb4a31986e87038f0fe1535d..19e539c36df7189b7c8a177beebaabc091bf085f 100644 (file)
@@ -606,7 +606,19 @@ public class ComponentActionTest {
     userSession.addProjectPermission(UserRole.USER, project);
     init();
 
-    executeAndVerify(project.getDbKey(), "return_alm_infos_on_project.json");
+    String json = execute(project.getKey());
+
+    assertJson(json).isSimilarTo("{\n" +
+      "  \"organization\": \"my-org\",\n" +
+      "  \"key\": \"polop\",\n" +
+      "  \"id\": \"abcd\",\n" +
+      "  \"name\": \"Polop\",\n" +
+      "  \"description\": \"test project\",\n" +
+      "  \"alm\": {\n" +
+      "     \"key\": \"bitbucketcloud\",\n" +
+      "     \"url\": \"http://bitbucket.org/foo/bar\"\n" +
+      "  }\n" +
+      "}\n");
   }
 
   @Test
@@ -618,7 +630,18 @@ public class ComponentActionTest {
     userSession.addProjectPermission(UserRole.USER, project);
     init();
 
-    executeAndVerify(module.getDbKey(), "return_alm_infos_on_module.json");
+    String json = execute(module.getKey());
+
+    assertJson(json).isSimilarTo("{\n" +
+      "  \"organization\": \"my-org\",\n" +
+      "  \"key\": \"palap\",\n" +
+      "  \"id\": \"bcde\",\n" +
+      "  \"name\": \"Palap\",\n" +
+      "  \"alm\": {\n" +
+      "     \"key\": \"bitbucketcloud\",\n" +
+      "     \"url\": \"http://bitbucket.org/foo/bar\"\n" +
+      "  }\n" +
+      "}\n");
   }
 
   @Test
@@ -630,11 +653,24 @@ public class ComponentActionTest {
     userSession.addProjectPermission(UserRole.USER, project);
     init();
 
-    verify(ws.newRequest()
-      .setParam("componentKey", project.getDbKey())
+    String json = ws.newRequest()
+      .setParam("componentKey", project.getKey())
       .setParam("branch", branch.getBranch())
       .execute()
-      .getInput(), "return_alm_infos_on_branch.json");
+      .getInput();
+
+    assertJson(json).isSimilarTo("{\n" +
+      "  \"organization\": \"my-org\",\n" +
+      "  \"key\": \"polop\",\n" +
+      "  \"id\": \"xyz\",\n" +
+      "  \"branch\": \"feature1\"," +
+      "  \"name\": \"Polop\",\n" +
+      "  \"description\": \"test project\",\n" +
+      "  \"alm\": {\n" +
+      "     \"key\": \"bitbucketcloud\",\n" +
+      "     \"url\": \"http://bitbucket.org/foo/bar\"\n" +
+      "  }\n" +
+      "}\n");
   }
 
   private ComponentDto insertOrganizationAndProject() {
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentActionTest/return_alm_infos_on_branch.json b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentActionTest/return_alm_infos_on_branch.json
deleted file mode 100644 (file)
index 968b109..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "organization": "my-org",
-  "key": "polop",
-  "id": "xyz",
-  "branch": "feature1",
-  "name": "Polop",
-  "description": "test project",
-  "almId": "bitbucketcloud",
-  "almRepoUrl": "http://bitbucket.org/foo/bar"
-}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentActionTest/return_alm_infos_on_module.json b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentActionTest/return_alm_infos_on_module.json
deleted file mode 100644 (file)
index 0078c9f..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "organization": "my-org",
-  "key": "palap",
-  "id": "bcde",
-  "name": "Palap",
-  "almId": "bitbucketcloud",
-  "almRepoUrl": "http://bitbucket.org/foo/bar"
-}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentActionTest/return_alm_infos_on_project.json b/server/sonar-server/src/test/resources/org/sonar/server/ui/ws/ComponentActionTest/return_alm_infos_on_project.json
deleted file mode 100644 (file)
index 6ac0435..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "organization": "my-org",
-  "key": "polop",
-  "id": "abcd",
-  "name": "Polop",
-  "description": "test project",
-  "almId": "bitbucketcloud",
-  "almRepoUrl": "http://bitbucket.org/foo/bar"
-}
index 172bf31d85c966393c2352ab070f053dff76d245..1d729266ddc804571e5206b12d0017f6bcb73ffb 100644 (file)
@@ -61,6 +61,12 @@ message Organization {
   optional string avatar = 5;
   optional bool guarded = 6;
   optional bool isAdmin = 7;
+  optional Alm alm = 8;
+
+  message Alm {
+    optional string key = 1;
+    optional string url = 2;
+  }
 }
 
 message User {