]> source.dussan.org Git - sonarqube.git/commitdiff
SC-799 Extract organization deletion to OrganizationDeleter
authorJanos Gyerik <janos.gyerik@sonarsource.com>
Wed, 3 Jul 2019 09:11:08 +0000 (11:11 +0200)
committerSonarTech <sonartech@sonarsource.com>
Fri, 5 Jul 2019 18:21:13 +0000 (20:21 +0200)
server/sonar-server/src/main/java/org/sonar/server/organization/ws/DeleteAction.java
server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationDeleter.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsModule.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/OrganizationDeleterTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/organization/ws/OrganizationsWsModuleTest.java

index 152540a80276443b362d055ed56b8ed196633c79..9d76d8adf9cf9847535df8ddd2ec158bb3bb3496 100644 (file)
  */
 package org.sonar.server.organization.ws;
 
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.resources.Scopes;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
-import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
-import org.sonar.db.component.ComponentDto;
 import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.qualitygate.QualityGateDto;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.component.ComponentCleanerService;
-import org.sonar.server.organization.BillingValidations;
-import org.sonar.server.organization.BillingValidationsProxy;
 import org.sonar.server.organization.DefaultOrganization;
 import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.organization.OrganizationFlags;
-import org.sonar.server.project.Project;
-import org.sonar.server.project.ProjectLifeCycleListeners;
-import org.sonar.server.qualityprofile.QProfileFactory;
 import org.sonar.server.user.UserSession;
-import org.sonar.server.user.index.UserIndexer;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static org.sonar.db.permission.OrganizationPermission.ADMINISTER;
@@ -60,25 +43,16 @@ public class DeleteAction implements OrganizationsWsAction {
   private final UserSession userSession;
   private final DbClient dbClient;
   private final DefaultOrganizationProvider defaultOrganizationProvider;
-  private final ComponentCleanerService componentCleanerService;
   private final OrganizationFlags organizationFlags;
-  private final UserIndexer userIndexer;
-  private final QProfileFactory qProfileFactory;
-  private final ProjectLifeCycleListeners projectLifeCycleListeners;
-  private final BillingValidationsProxy billingValidations;
+  private final OrganizationDeleter organizationDeleter;
 
-  public DeleteAction(UserSession userSession, DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider, ComponentCleanerService componentCleanerService,
-    OrganizationFlags organizationFlags, UserIndexer userIndexer, QProfileFactory qProfileFactory, ProjectLifeCycleListeners projectLifeCycleListeners,
-    BillingValidationsProxy billingValidations) {
+  public DeleteAction(UserSession userSession, DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider,
+    OrganizationFlags organizationFlags, OrganizationDeleter organizationDeleter) {
     this.userSession = userSession;
     this.dbClient = dbClient;
     this.defaultOrganizationProvider = defaultOrganizationProvider;
-    this.componentCleanerService = componentCleanerService;
     this.organizationFlags = organizationFlags;
-    this.userIndexer = userIndexer;
-    this.qProfileFactory = qProfileFactory;
-    this.projectLifeCycleListeners = projectLifeCycleListeners;
-    this.billingValidations = billingValidations;
+    this.organizationDeleter = organizationDeleter;
   }
 
   @Override
@@ -111,78 +85,12 @@ public class DeleteAction implements OrganizationsWsAction {
       OrganizationDto organization = checkFoundWithOptional(dbClient.organizationDao().selectByKey(dbSession, key), "Organization with key '%s' not found", key);
       userSession.checkPermission(ADMINISTER, organization);
 
-      deleteProjects(dbSession, organization);
-      deletePermissions(dbSession, organization);
-      deleteGroups(dbSession, organization);
-      deleteQualityProfiles(dbSession, organization);
-      deleteQualityGates(dbSession, organization);
-      deleteOrganizationAlmBinding(dbSession, organization);
-      deleteOrganization(dbSession, organization);
-      billingValidations.onDelete(new BillingValidations.Organization(organization.getKey(), organization.getUuid(), organization.getName()));
+      organizationDeleter.delete(dbSession, organization);
 
       response.noContent();
     }
   }
 
-  private void deleteProjects(DbSession dbSession, OrganizationDto organization) {
-    List<ComponentDto> roots = dbClient.componentDao().selectProjectsByOrganization(dbSession, organization.getUuid());
-    try {
-      componentCleanerService.delete(dbSession, roots);
-    } finally {
-      Set<Project> projects = roots.stream()
-        .filter(DeleteAction::isMainProject)
-        .map(Project::from)
-        .collect(MoreCollectors.toSet());
-      projectLifeCycleListeners.onProjectsDeleted(projects);
-    }
-  }
-
-  private static boolean isMainProject(ComponentDto p) {
-    return Scopes.PROJECT.equals(p.scope())
-      && Qualifiers.PROJECT.equals(p.qualifier())
-      && p.getMainBranchProjectUuid() == null;
-  }
-
-  private void deletePermissions(DbSession dbSession, OrganizationDto organization) {
-    dbClient.permissionTemplateDao().deleteByOrganization(dbSession, organization.getUuid());
-    dbClient.userPermissionDao().deleteByOrganization(dbSession, organization.getUuid());
-    dbClient.groupPermissionDao().deleteByOrganization(dbSession, organization.getUuid());
-  }
-
-  private void deleteGroups(DbSession dbSession, OrganizationDto organization) {
-    dbClient.groupDao().deleteByOrganization(dbSession, organization.getUuid());
-  }
-
-  private void deleteQualityProfiles(DbSession dbSession, OrganizationDto organization) {
-    List<QProfileDto> profiles = dbClient.qualityProfileDao().selectOrderedByOrganizationUuid(dbSession, organization);
-    qProfileFactory.delete(dbSession, profiles);
-  }
-
-  private void deleteQualityGates(DbSession dbSession, OrganizationDto organization) {
-    Collection<QualityGateDto> qualityGates = dbClient.qualityGateDao().selectAll(dbSession, organization);
-    dbClient.qualityGateDao().deleteByUuids(dbSession, qualityGates.stream()
-      .filter(q -> !q.isBuiltIn())
-      .map(QualityGateDto::getUuid)
-      .collect(MoreCollectors.toList()));
-    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());
-    dbClient.organizationDao().deleteByUuid(dbSession, organization.getUuid());
-    dbClient.userDao().cleanHomepage(dbSession, organization);
-    dbClient.webhookDao().selectByOrganizationUuid(dbSession, organization.getUuid())
-      .forEach(wh -> dbClient.webhookDeliveryDao().deleteByWebhook(dbSession, wh));
-    dbClient.webhookDao().deleteByOrganization(dbSession, organization);
-    List<UserDto> users = dbClient.userDao().selectByUuids(dbSession, uuids);
-    userIndexer.commitAndIndex(dbSession, users);
-  }
-
   private static void preventDeletionOfDefaultOrganization(String key, DefaultOrganization defaultOrganization) {
     checkArgument(!defaultOrganization.getKey().equals(key), "Default Organization can't be deleted");
   }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationDeleter.java b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationDeleter.java
new file mode 100644 (file)
index 0000000..fb638fe
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ws;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.organization.OrganizationQuery;
+import org.sonar.db.qualitygate.QualityGateDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.component.ComponentCleanerService;
+import org.sonar.server.organization.BillingValidations;
+import org.sonar.server.organization.BillingValidationsProxy;
+import org.sonar.server.project.Project;
+import org.sonar.server.project.ProjectLifeCycleListeners;
+import org.sonar.server.qualityprofile.QProfileFactory;
+import org.sonar.server.user.index.UserIndexer;
+
+import static org.sonar.db.Pagination.forPage;
+
+public class OrganizationDeleter {
+
+  private static final Logger LOGGER = Loggers.get(OrganizationDeleter.class);
+
+  @VisibleForTesting
+  static final int PAGE_SIZE = 100;
+
+  private final DbClient dbClient;
+  private final ComponentCleanerService componentCleanerService;
+  private final UserIndexer userIndexer;
+  private final QProfileFactory qProfileFactory;
+  private final ProjectLifeCycleListeners projectLifeCycleListeners;
+  private final BillingValidationsProxy billingValidations;
+
+  public OrganizationDeleter(DbClient dbClient, ComponentCleanerService componentCleanerService, UserIndexer userIndexer,
+    QProfileFactory qProfileFactory, ProjectLifeCycleListeners projectLifeCycleListeners,
+    BillingValidationsProxy billingValidations) {
+    this.dbClient = dbClient;
+    this.componentCleanerService = componentCleanerService;
+    this.userIndexer = userIndexer;
+    this.qProfileFactory = qProfileFactory;
+    this.projectLifeCycleListeners = projectLifeCycleListeners;
+    this.billingValidations = billingValidations;
+  }
+
+  void delete(DbSession dbSession, OrganizationDto organization) {
+    deleteProjects(dbSession, organization);
+    deletePermissions(dbSession, organization);
+    deleteGroups(dbSession, organization);
+    deleteQualityProfiles(dbSession, organization);
+    deleteQualityGates(dbSession, organization);
+    deleteOrganizationAlmBinding(dbSession, organization);
+    deleteOrganization(dbSession, organization);
+    billingValidations.onDelete(new BillingValidations.Organization(organization.getKey(), organization.getUuid(), organization.getName()));
+  }
+
+  private void deleteProjects(DbSession dbSession, OrganizationDto organization) {
+    List<ComponentDto> roots = dbClient.componentDao().selectProjectsByOrganization(dbSession, organization.getUuid());
+    try {
+      componentCleanerService.delete(dbSession, roots);
+    } finally {
+      Set<Project> projects = roots.stream()
+        .filter(OrganizationDeleter::isMainProject)
+        .map(Project::from)
+        .collect(MoreCollectors.toSet());
+      projectLifeCycleListeners.onProjectsDeleted(projects);
+    }
+  }
+
+  private static boolean isMainProject(ComponentDto p) {
+    return Scopes.PROJECT.equals(p.scope())
+      && Qualifiers.PROJECT.equals(p.qualifier())
+      && p.getMainBranchProjectUuid() == null;
+  }
+
+  private void deletePermissions(DbSession dbSession, OrganizationDto organization) {
+    dbClient.permissionTemplateDao().deleteByOrganization(dbSession, organization.getUuid());
+    dbClient.userPermissionDao().deleteByOrganization(dbSession, organization.getUuid());
+    dbClient.groupPermissionDao().deleteByOrganization(dbSession, organization.getUuid());
+  }
+
+  private void deleteGroups(DbSession dbSession, OrganizationDto organization) {
+    dbClient.groupDao().deleteByOrganization(dbSession, organization.getUuid());
+  }
+
+  private void deleteQualityProfiles(DbSession dbSession, OrganizationDto organization) {
+    List<QProfileDto> profiles = dbClient.qualityProfileDao().selectOrderedByOrganizationUuid(dbSession, organization);
+    qProfileFactory.delete(dbSession, profiles);
+  }
+
+  private void deleteQualityGates(DbSession dbSession, OrganizationDto organization) {
+    Collection<QualityGateDto> qualityGates = dbClient.qualityGateDao().selectAll(dbSession, organization);
+    dbClient.qualityGateDao().deleteByUuids(dbSession, qualityGates.stream()
+      .filter(q -> !q.isBuiltIn())
+      .map(QualityGateDto::getUuid)
+      .collect(MoreCollectors.toList()));
+    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());
+    dbClient.organizationDao().deleteByUuid(dbSession, organization.getUuid());
+    dbClient.userDao().cleanHomepage(dbSession, organization);
+    dbClient.webhookDao().selectByOrganizationUuid(dbSession, organization.getUuid())
+      .forEach(wh -> dbClient.webhookDeliveryDao().deleteByWebhook(dbSession, wh));
+    dbClient.webhookDao().deleteByOrganization(dbSession, organization);
+    List<UserDto> users = dbClient.userDao().selectByUuids(dbSession, uuids);
+    userIndexer.commitAndIndex(dbSession, users);
+  }
+
+  void deleteByQuery(OrganizationQuery query) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      deleteByQuery(dbSession, query);
+    }
+  }
+
+  private void deleteByQuery(DbSession dbSession, OrganizationQuery query) {
+    while (true) {
+      int total = dbClient.organizationDao().countByQuery(dbSession, query);
+      if (total == 0) {
+        return;
+      }
+
+      dbClient.organizationDao().selectByQuery(dbSession, query, forPage(1).andSize(PAGE_SIZE))
+        .forEach(org -> {
+          LOGGER.info("deleting organization {} ({})", org.getName(), org.getKey());
+          delete(dbSession, org);
+        });
+    }
+  }
+}
index 6c1d1eaa0b09cb8c760f6be25995825174814af6..4d9f5083e65b1895e3276e86b1021b45059a2d68 100644 (file)
@@ -50,6 +50,7 @@ public class OrganizationsWsModule extends Module {
         EnableSupportAction.class,
         AddMemberAction.class,
         CreateAction.class,
+        OrganizationDeleter.class,
         DeleteAction.class,
         RemoveMemberAction.class,
         UpdateAction.class);
index 8b19fa4b1ec0485464dab9f9d191b9f8853863c8..5f801accaadf174c532e9ab7b3374264761ec93e 100644 (file)
@@ -47,9 +47,6 @@ import org.sonar.db.qualitygate.QualityGateDto;
 import org.sonar.db.qualityprofile.QProfileDto;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.UserDto;
-import org.sonar.db.webhook.WebhookDbTester;
-import org.sonar.db.webhook.WebhookDeliveryDao;
-import org.sonar.db.webhook.WebhookDeliveryDbTester;
 import org.sonar.db.webhook.WebhookDto;
 import org.sonar.server.component.ComponentCleanerService;
 import org.sonar.server.es.EsTester;
@@ -115,14 +112,10 @@ public class DeleteActionTest {
   private QProfileFactory qProfileFactory = new QProfileFactoryImpl(dbClient, mock(UuidFactory.class), System2.INSTANCE, mock(ActiveRuleIndexer.class));
   private UserIndex userIndex = new UserIndex(es.client(), System2.INSTANCE);
   private UserIndexer userIndexer = new UserIndexer(dbClient, es.client());
-  private final WebhookDbTester webhookDbTester = db.webhooks();
-  private final WebhookDeliveryDao deliveryDao = dbClient.webhookDeliveryDao();
-  private final WebhookDeliveryDbTester webhookDeliveryDbTester = db.webhookDelivery();
   private ProjectLifeCycleListeners projectLifeCycleListeners = mock(ProjectLifeCycleListeners.class);
   private BillingValidationsProxy billingValidationsProxy = mock(BillingValidationsProxy.class);
-  private WsActionTester wsTester = new WsActionTester(
-    new DeleteAction(userSession, dbClient, defaultOrganizationProvider, spiedComponentCleanerService, organizationFlags, userIndexer, qProfileFactory, projectLifeCycleListeners,
-      billingValidationsProxy));
+  private OrganizationDeleter organizationDeleter = new OrganizationDeleter(dbClient, spiedComponentCleanerService, userIndexer, qProfileFactory, projectLifeCycleListeners, billingValidationsProxy);
+  private WsActionTester wsTester = new WsActionTester(new DeleteAction(userSession, dbClient, defaultOrganizationProvider, organizationFlags, organizationDeleter));
 
   @Test
   public void definition() {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/OrganizationDeleterTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/OrganizationDeleterTest.java
new file mode 100644 (file)
index 0000000..407e68e
--- /dev/null
@@ -0,0 +1,412 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ws;
+
+import com.google.common.collect.ImmutableSet;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.Pagination;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.component.ResourceTypesRule;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.organization.OrganizationQuery;
+import org.sonar.db.permission.template.PermissionTemplateDto;
+import org.sonar.db.qualitygate.QGateWithOrgDto;
+import org.sonar.db.qualitygate.QualityGateDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.db.webhook.WebhookDto;
+import org.sonar.server.component.ComponentCleanerService;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.ProjectIndexers;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.organization.BillingValidations;
+import org.sonar.server.organization.BillingValidationsProxy;
+import org.sonar.server.project.Project;
+import org.sonar.server.project.ProjectLifeCycleListeners;
+import org.sonar.server.qualityprofile.QProfileFactoryImpl;
+import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
+import org.sonar.server.user.index.UserIndex;
+import org.sonar.server.user.index.UserIndexer;
+import org.sonar.server.user.index.UserQuery;
+
+import static com.google.common.collect.ImmutableList.of;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singleton;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.sonar.api.resources.Qualifiers.APP;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+import static org.sonar.api.resources.Qualifiers.VIEW;
+import static org.sonar.core.util.stream.MoreCollectors.toSet;
+import static org.sonar.db.user.UserTesting.newUserDto;
+import static org.sonar.server.organization.ws.OrganizationDeleter.PAGE_SIZE;
+
+@RunWith(DataProviderRunner.class)
+public class OrganizationDeleterTest {
+
+  @Rule
+  public final DbTester db = DbTester.create(new System2()).setDisableDefaultOrganization(true);
+  private final DbClient dbClient = db.getDbClient();
+  private final DbSession dbSession = db.getSession();
+
+  @Rule
+  public final EsTester es = EsTester.create();
+  private final EsClient esClient = es.client();
+
+  private final ResourceTypesRule resourceTypes = new ResourceTypesRule().setRootQualifiers(PROJECT, VIEW, APP).setAllQualifiers(PROJECT, VIEW, APP);
+  private final ComponentCleanerService componentCleanerService = spy(new ComponentCleanerService(db.getDbClient(), resourceTypes, mock(ProjectIndexers.class)));
+  private final UserIndex userIndex = new UserIndex(esClient, System2.INSTANCE);
+  private final UserIndexer userIndexer = new UserIndexer(dbClient, esClient);
+  private final ProjectLifeCycleListeners projectLifeCycleListeners = mock(ProjectLifeCycleListeners.class);
+  private final BillingValidationsProxy billingValidations = mock(BillingValidationsProxy.class);
+
+  private final OrganizationDeleter underTest = new OrganizationDeleter(dbClient, componentCleanerService, userIndexer,
+    new QProfileFactoryImpl(dbClient, UuidFactoryFast.getInstance(), new System2(), new ActiveRuleIndexer(dbClient, esClient)),
+    projectLifeCycleListeners,
+    billingValidations);
+
+  @Test
+  public void delete_specified_organization() {
+    OrganizationDto organization = db.organizations().insert();
+
+    underTest.delete(dbSession, organization);
+
+    verifyOrganizationDoesNotExist(organization);
+    verify(projectLifeCycleListeners).onProjectsDeleted(emptySet());
+  }
+
+  @Test
+  public void delete_webhooks_of_organization_if_exist() {
+    OrganizationDto organization = db.organizations().insert();
+    db.webhooks().insertWebhook(organization);
+    ComponentDto project = db.components().insertPrivateProject(organization);
+    WebhookDto projectWebhook = db.webhooks().insertWebhook(project);
+    db.webhookDelivery().insert(projectWebhook);
+
+    underTest.delete(dbSession, organization);
+
+    assertThat(db.countRowsOfTable(db.getSession(), "webhooks")).isZero();
+    assertThat(db.countRowsOfTable(db.getSession(), "webhook_deliveries")).isZero();
+  }
+
+  @Test
+  public void clear_user_homepage_on_organization_if_exists() {
+    OrganizationDto organization = db.organizations().insert();
+    UserDto user = dbClient.userDao().insert(dbSession, newUserDto().setHomepageType("ORGANIZATION").setHomepageParameter(organization.getUuid()));
+    dbSession.commit();
+
+    underTest.delete(dbSession, organization);
+
+    UserDto userReloaded = dbClient.userDao().selectUserById(dbSession, user.getId());
+    assertThat(userReloaded.getHomepageType()).isNull();
+    assertThat(userReloaded.getHomepageParameter()).isNull();
+  }
+
+  @Test
+  public void clear_project_homepage_on_organization_if_exists() {
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto project = db.components().insertPrivateProject(organization);
+    UserDto user = dbClient.userDao().insert(dbSession,
+      newUserDto().setHomepageType("PROJECT").setHomepageParameter(project.uuid()));
+    dbSession.commit();
+
+    underTest.delete(dbSession, organization);
+
+    UserDto userReloaded = dbClient.userDao().selectUserById(dbSession, user.getId());
+    assertThat(userReloaded.getHomepageType()).isNull();
+    assertThat(userReloaded.getHomepageParameter()).isNull();
+    verify(projectLifeCycleListeners).onProjectsDeleted(ImmutableSet.of(Project.from(project)));
+  }
+
+  @Test
+  @UseDataProvider("OneOrMoreIterations")
+  public void delete_components(int numberOfIterations) {
+    OrganizationDto organization = db.organizations().insert();
+    Set<ComponentDto> projects = IntStream.range(0, numberOfIterations).mapToObj(i -> {
+      ComponentDto project = db.components().insertPrivateProject(organization);
+      ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project));
+      ComponentDto directory = db.components().insertComponent(ComponentTesting.newDirectory(module, "a/b" + i));
+      ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(module, directory));
+      ComponentDto branch = db.components().insertProjectBranch(project);
+      return project;
+    }).collect(toSet());
+
+
+    underTest.delete(dbSession, organization);
+
+    verifyOrganizationDoesNotExist(organization);
+    assertThat(db.countRowsOfTable(db.getSession(), "projects")).isZero();
+    verify(projectLifeCycleListeners).onProjectsDeleted(projects.stream().map(Project::from).collect(toSet()));
+  }
+
+  @DataProvider
+  public static Object[][] OneOrMoreIterations() {
+    return new Object[][] {
+      {1},
+      {1 + new Random().nextInt(10)},
+    };
+  }
+
+  @Test
+  public void delete_branches() {
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto project = db.components().insertMainBranch(organization);
+    ComponentDto branch = db.components().insertProjectBranch(project);
+
+    underTest.delete(dbSession, organization);
+
+    verifyOrganizationDoesNotExist(organization);
+    assertThat(db.countRowsOfTable(db.getSession(), "projects")).isZero();
+    assertThat(db.countRowsOfTable(db.getSession(), "project_branches")).isZero();
+    verify(projectLifeCycleListeners).onProjectsDeleted(ImmutableSet.of(Project.from(project)));
+  }
+
+  @Test
+  public void delete_permissions_templates_and_permissions_and_groups() {
+    OrganizationDto org = db.organizations().insert();
+    OrganizationDto otherOrg = db.organizations().insert();
+
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+    GroupDto group1 = db.users().insertGroup(org);
+    GroupDto group2 = db.users().insertGroup(org);
+    GroupDto otherGroup1 = db.users().insertGroup(otherOrg);
+    GroupDto otherGroup2 = db.users().insertGroup(otherOrg);
+
+    ComponentDto projectDto = db.components().insertPublicProject(org);
+    ComponentDto otherProjectDto = db.components().insertPublicProject(otherOrg);
+
+    db.users().insertPermissionOnAnyone(org, "u1");
+    db.users().insertPermissionOnAnyone(otherOrg, "not deleted u1");
+    db.users().insertPermissionOnUser(org, user1, "u2");
+    db.users().insertPermissionOnUser(otherOrg, user1, "not deleted u2");
+    db.users().insertPermissionOnGroup(group1, "u3");
+    db.users().insertPermissionOnGroup(otherGroup1, "not deleted u3");
+    db.users().insertProjectPermissionOnAnyone("u4", projectDto);
+    db.users().insertProjectPermissionOnAnyone("not deleted u4", otherProjectDto);
+    db.users().insertProjectPermissionOnGroup(group1, "u5", projectDto);
+    db.users().insertProjectPermissionOnGroup(otherGroup1, "not deleted u5", otherProjectDto);
+    db.users().insertProjectPermissionOnUser(user1, "u6", projectDto);
+    db.users().insertProjectPermissionOnUser(user1, "not deleted u6", otherProjectDto);
+
+    PermissionTemplateDto templateDto = db.permissionTemplates().insertTemplate(org);
+    PermissionTemplateDto otherTemplateDto = db.permissionTemplates().insertTemplate(otherOrg);
+
+    underTest.delete(dbSession, org);
+
+    verifyOrganizationDoesNotExist(org);
+    assertThat(dbClient.groupDao().selectByIds(dbSession, of(group1.getId(), otherGroup1.getId(), group2.getId(), otherGroup2.getId())))
+      .extracting(GroupDto::getId)
+      .containsOnly(otherGroup1.getId(), otherGroup2.getId());
+    assertThat(dbClient.permissionTemplateDao().selectByUuid(dbSession, templateDto.getUuid()))
+      .isNull();
+    assertThat(dbClient.permissionTemplateDao().selectByUuid(dbSession, otherTemplateDto.getUuid()))
+      .isNotNull();
+    assertThat(db.select("select role as \"role\" from USER_ROLES"))
+      .extracting(row -> (String) row.get("role"))
+      .doesNotContain("u2", "u6")
+      .contains("not deleted u2", "not deleted u6");
+    assertThat(db.select("select role as \"role\" from GROUP_ROLES"))
+      .extracting(row -> (String) row.get("role"))
+      .doesNotContain("u1", "u3", "u4", "u5")
+      .contains("not deleted u1", "not deleted u3", "not deleted u4", "not deleted u5");
+    verify(projectLifeCycleListeners).onProjectsDeleted(ImmutableSet.of(Project.from(projectDto)));
+  }
+
+  @Test
+  public void delete_members() {
+    OrganizationDto org = db.organizations().insert();
+    OrganizationDto otherOrg = db.organizations().insert();
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+    db.organizations().addMember(org, user1);
+    db.organizations().addMember(otherOrg, user1);
+    db.organizations().addMember(org, user2);
+    userIndexer.commitAndIndex(db.getSession(), asList(user1, user2));
+
+    underTest.delete(dbSession, org);
+
+    verifyOrganizationDoesNotExist(org);
+    assertThat(db.getDbClient().organizationMemberDao().select(db.getSession(), org.getUuid(), user1.getId())).isNotPresent();
+    assertThat(db.getDbClient().organizationMemberDao().select(db.getSession(), org.getUuid(), user2.getId())).isNotPresent();
+    assertThat(db.getDbClient().organizationMemberDao().select(db.getSession(), otherOrg.getUuid(), user1.getId())).isPresent();
+    assertThat(userIndex.search(UserQuery.builder().setOrganizationUuid(org.getUuid()).build(), new SearchOptions()).getTotal()).isEqualTo(0);
+    assertThat(userIndex.search(UserQuery.builder().setOrganizationUuid(otherOrg.getUuid()).build(), new SearchOptions()).getTotal()).isEqualTo(1);
+    verify(projectLifeCycleListeners).onProjectsDeleted(emptySet());
+  }
+
+  @Test
+  public void delete_quality_profiles() {
+    OrganizationDto org = db.organizations().insert();
+    OrganizationDto otherOrg = db.organizations().insert();
+    QProfileDto profileInOrg = db.qualityProfiles().insert(org);
+    QProfileDto profileInOtherOrg = db.qualityProfiles().insert(otherOrg);
+
+    underTest.delete(dbSession, org);
+
+    verifyOrganizationDoesNotExist(org);
+    assertThat(db.select("select uuid as \"profileKey\" from org_qprofiles"))
+      .extracting(row -> (String) row.get("profileKey"))
+      .containsOnly(profileInOtherOrg.getKee());
+  }
+
+  @Test
+  public void delete_quality_gates() {
+    QualityGateDto builtInQualityGate = db.qualityGates().insertBuiltInQualityGate();
+    OrganizationDto organization = db.organizations().insert();
+    db.qualityGates().associateQualityGateToOrganization(builtInQualityGate, organization);
+    OrganizationDto otherOrganization = db.organizations().insert();
+    db.qualityGates().associateQualityGateToOrganization(builtInQualityGate, otherOrganization);
+    QGateWithOrgDto qualityGate = db.qualityGates().insertQualityGate(organization);
+    QGateWithOrgDto qualityGateInOtherOrg = db.qualityGates().insertQualityGate(otherOrganization);
+
+    underTest.delete(dbSession, organization);
+
+    verifyOrganizationDoesNotExist(organization);
+    assertThat(db.select("select uuid as \"uuid\" from quality_gates"))
+      .extracting(row -> (String) row.get("uuid"))
+      .containsExactlyInAnyOrder(qualityGateInOtherOrg.getUuid(), builtInQualityGate.getUuid());
+    assertThat(db.select("select organization_uuid as \"organizationUuid\" from org_quality_gates"))
+      .extracting(row -> (String) row.get("organizationUuid"))
+      .containsOnly(otherOrganization.getUuid());
+
+    // Check built-in quality gate is still available in other organization
+    assertThat(db.getDbClient().qualityGateDao().selectByOrganizationAndName(db.getSession(), otherOrganization, "Sonar way")).isNotNull();
+    verify(projectLifeCycleListeners).onProjectsDeleted(emptySet());
+  }
+
+  @Test
+  @UseDataProvider("indexOfFailingProjectDeletion")
+  public void projectLifeCycleListener_are_notified_even_if_deletion_of_a_project_throws_an_Exception(int failingProjectIndex) {
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto[] projects = new ComponentDto[] {
+      db.components().insertPrivateProject(organization),
+      db.components().insertPrivateProject(organization),
+      db.components().insertPrivateProject(organization)
+    };
+    RuntimeException expectedException = new RuntimeException("Faking deletion of 2nd project throwing an exception");
+    doThrow(expectedException)
+      .when(componentCleanerService).delete(any(), eq(projects[failingProjectIndex]));
+
+    try {
+      underTest.delete(dbSession, organization);
+      fail("A RuntimeException should have been thrown");
+    } catch (RuntimeException e) {
+      assertThat(e).isSameAs(expectedException);
+      verify(projectLifeCycleListeners).onProjectsDeleted(Arrays.stream(projects).map(Project::from).collect(toSet()));
+    }
+  }
+
+  @Test
+  public void call_billing_validation_on_delete() {
+    OrganizationDto organization = db.organizations().insert();
+
+    underTest.delete(dbSession, organization);
+
+    verify(billingValidations).onDelete(any(BillingValidations.Organization.class));
+  }
+
+  @Test
+  public void delete_organization_alm_binding() {
+    OrganizationDto organization = db.organizations().insert();
+    db.alm().insertOrganizationAlmBinding(organization, db.alm().insertAlmAppInstall(), true);
+
+    underTest.delete(dbSession, organization);
+
+    assertThat(db.getDbClient().organizationAlmBindingDao().selectByOrganization(db.getSession(), organization)).isNotPresent();
+  }
+
+  @DataProvider
+  public static Object[][] indexOfFailingProjectDeletion() {
+    return new Object[][] {
+      {0},
+      {1},
+      {2}
+    };
+  }
+
+
+  @Test
+  @UseDataProvider("queriesAndUnmatchedOrganizationKeys")
+  public void delete_organizations_matched_by_query(OrganizationQuery query, Collection<String> unmatchedOrgKeys) {
+    db.organizations().insert(o -> o.setKey("org1"));
+    db.organizations().insert(o -> o.setKey("org2"));
+    db.organizations().insert(o -> o.setKey("org3"));
+
+    underTest.deleteByQuery(query);
+
+    assertThat(dbClient.organizationDao().selectByQuery(db.getSession(), OrganizationQuery.returnAll(), Pagination.all()))
+      .extracting(OrganizationDto::getKey)
+      .containsExactlyInAnyOrderElementsOf(unmatchedOrgKeys);
+  }
+
+  @DataProvider
+  public static Object[][] queriesAndUnmatchedOrganizationKeys() {
+    return new Object[][] {
+      {OrganizationQuery.returnAll(), Collections.emptyList()},
+      {OrganizationQuery.newOrganizationQueryBuilder().setKeys(singleton("nonexistent")).build(), Arrays.asList("org1", "org2", "org3")},
+      {OrganizationQuery.newOrganizationQueryBuilder().setKeys(singleton("org1")).build(), Arrays.asList("org2", "org3")},
+    };
+  }
+
+  @Test
+  public void delete_organizations_for_all_query_pages() {
+    int orgsCountGreaterThanPageSize = PAGE_SIZE + 1;
+
+    IntStream.range(0, orgsCountGreaterThanPageSize).forEach(ignored -> db.organizations().insert());
+
+    OrganizationQuery query = OrganizationQuery.returnAll();
+    assertThat(dbClient.organizationDao().countByQuery(db.getSession(), query)).isEqualTo(orgsCountGreaterThanPageSize);
+
+    underTest.deleteByQuery(query);
+
+    assertThat(dbClient.organizationDao().countByQuery(db.getSession(), query)).isZero();
+  }
+
+  private void verifyOrganizationDoesNotExist(OrganizationDto organization) {
+    assertThat(db.getDbClient().organizationDao().selectByKey(dbSession, organization.getKey())).isEmpty();
+  }
+}
index bc041d334eb6e359816244987ce13ea86c7584dd..d6023ac7374e1aa4616d55b1dfbfa8071e4547fc 100644 (file)
@@ -49,7 +49,7 @@ public class OrganizationsWsModuleTest {
     underTest.configure(container);
 
     assertThat(container.getPicoContainer().getComponentAdapters())
-      .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 12);
+      .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 13);
   }
 
 }