]> source.dussan.org Git - sonarqube.git/commitdiff
GOV-334 trigger refresh of views on api/projects/bulk_update_key
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 29 Mar 2018 13:23:39 +0000 (15:23 +0200)
committerSonarTech <sonartech@sonarsource.com>
Fri, 6 Apr 2018 18:21:52 +0000 (20:21 +0200)
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java
server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java
server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListener.java
server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListeners.java
server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListenersImpl.java
server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java
server/sonar-server/src/test/java/org/sonar/server/project/ProjectLifeCycleListenersImplTest.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/UpdateKeyActionTest.java

index 8bc0a216bb8fb79c968468b5bc235dac381e0877..3563b946a247e81cf4f974040976c7df6b44f84b 100644 (file)
@@ -28,9 +28,13 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
+import java.util.function.BiConsumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import javax.annotation.Nullable;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.db.Dao;
@@ -71,7 +75,8 @@ public class ComponentKeyUpdaterDao implements Dao {
       });
 
     // and then proceed with the batch UPDATE at once
-    runBatchUpdateForAllResources(resources, projectOldKey, newKey, mapper);
+    runBatchUpdateForAllResources(resources, projectOldKey, newKey, mapper, (resource, oldKey) -> {
+    });
   }
 
   public static void checkIsProjectOrModule(ComponentDto component) {
@@ -106,7 +111,8 @@ public class ComponentKeyUpdaterDao implements Dao {
     return key.replace(stringToReplace, replacementString);
   }
 
-  public void bulkUpdateKey(DbSession session, String projectUuid, String stringToReplace, String replacementString) {
+  public Set<RekeyedResource> bulkUpdateKey(DbSession session, String projectUuid, String stringToReplace, String replacementString,
+    Predicate<RekeyedResource> rekeyedResourceFilter) {
     ComponentKeyUpdaterMapper mapper = session.getMapper(ComponentKeyUpdaterMapper.class);
     // must SELECT first everything
     Set<ResourceDto> modules = collectAllModules(projectUuid, stringToReplace, mapper, true);
@@ -128,6 +134,7 @@ public class ComponentKeyUpdaterDao implements Dao {
       allResourcesByModuleMap.put(module, mapper.selectProjectResources(module.getUuid()));
     }
 
+    Set<RekeyedResource> rekeyedResources = new HashSet<>();
     // and then proceed with the batch UPDATE at once
     for (ResourceDto module : modules) {
       String oldModuleKey = module.getKey();
@@ -135,8 +142,15 @@ public class ComponentKeyUpdaterDao implements Dao {
       String newModuleKey = computeNewKey(oldModuleKey, stringToReplace, replacementString);
       Collection<ResourceDto> resources = Lists.newArrayList(module);
       resources.addAll(allResourcesByModuleMap.get(module));
-      runBatchUpdateForAllResources(resources, oldModuleKey, newModuleKey, mapper);
+      runBatchUpdateForAllResources(resources, oldModuleKey, newModuleKey, mapper,
+        (resource, oldKey) -> {
+          RekeyedResource rekeyedResource = new RekeyedResource(resource, oldKey);
+          if (rekeyedResourceFilter.test(rekeyedResource)) {
+            rekeyedResources.add(rekeyedResource);
+          }
+        });
     }
+    return rekeyedResources;
   }
 
   private static String branchBaseKey(String key) {
@@ -151,7 +165,8 @@ public class ComponentKeyUpdaterDao implements Dao {
     return key;
   }
 
-  private static void runBatchUpdateForAllResources(Collection<ResourceDto> resources, String oldKey, String newKey, ComponentKeyUpdaterMapper mapper) {
+  private static void runBatchUpdateForAllResources(Collection<ResourceDto> resources, String oldKey, String newKey, ComponentKeyUpdaterMapper mapper,
+    @Nullable BiConsumer<ResourceDto, String> consumer) {
     for (ResourceDto resource : resources) {
       String oldResourceKey = resource.getKey();
       String newResourceKey = newKey + oldResourceKey.substring(oldKey.length(), oldResourceKey.length());
@@ -162,6 +177,44 @@ public class ComponentKeyUpdaterDao implements Dao {
         resource.setDeprecatedKey(newResourceDeprecatedKey);
       }
       mapper.update(resource);
+      if (consumer != null) {
+        consumer.accept(resource, oldResourceKey);
+      }
+    }
+  }
+
+  public static final class RekeyedResource {
+    private final ResourceDto resource;
+    private final String oldKey;
+
+    public RekeyedResource(ResourceDto resource, String oldKey) {
+      this.resource = resource;
+      this.oldKey = oldKey;
+    }
+
+    public ResourceDto getResource() {
+      return resource;
+    }
+
+    public String getOldKey() {
+      return oldKey;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      RekeyedResource that = (RekeyedResource) o;
+      return Objects.equals(resource.getUuid(), that.resource.getUuid());
+    }
+
+    @Override
+    public int hashCode() {
+      return resource.getUuid().hashCode();
     }
   }
 
index b7f4e9cf381852ed7f57dc163c9cc3726149df7d..f16cf6561ca95fcb75e36991a7a04eb5c7b0011b 100644 (file)
@@ -22,6 +22,8 @@ package org.sonar.db.component;
 import com.google.common.base.Strings;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -29,6 +31,7 @@ import org.sonar.api.utils.System2;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentKeyUpdaterDao.RekeyedResource;
 import org.sonar.db.organization.OrganizationDto;
 
 import static com.google.common.collect.Lists.newArrayList;
@@ -140,8 +143,8 @@ public class ComponentKeyUpdaterDaoTest {
     ComponentDto project = db.components().insertMainBranch();
     ComponentDto branch = db.components().insertProjectBranch(project);
     ComponentDto module = db.components().insertComponent(prefixDbKeyWithKey(newModuleDto(branch), project.getKey()));
-    db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey()));
-    db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey()));
+    ComponentDto file1 = db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey()));
+    ComponentDto file2 = db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey()));
     int branchComponentCount = 4;
 
     String oldProjectKey = project.getKey();
@@ -151,16 +154,26 @@ public class ComponentKeyUpdaterDaoTest {
     assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldBranchKey)).hasSize(branchComponentCount);
 
     String newProjectKey = "newKey";
-    String newBranchKey = ComponentDto.generateBranchKey(newProjectKey, branch.getBranch());
-    underTest.bulkUpdateKey(dbSession, project.uuid(), oldProjectKey, newProjectKey);
+    Set<RekeyedResource> rekeyedResources = underTest.bulkUpdateKey(dbSession, project.uuid(), oldProjectKey, newProjectKey, a -> true);
 
     assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldProjectKey)).isEmpty();
     assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldBranchKey)).isEmpty();
 
     assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newProjectKey)).hasSize(1);
+    String newBranchKey = ComponentDto.generateBranchKey(newProjectKey, branch.getBranch());
     assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newBranchKey)).hasSize(branchComponentCount);
     db.select(dbSession, "select kee from projects")
       .forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey)));
+
+    assertThat(rekeyedResources)
+      .extracting(t -> t.getResource().getUuid())
+      .containsOnly(project.uuid(), branch.uuid(), module.uuid(), file1.uuid(), file2.uuid());
+    assertThat(rekeyedResources)
+      .extracting(t -> t.getResource().getKey())
+      .allMatch(t -> t.startsWith(newProjectKey));
+    assertThat(rekeyedResources)
+      .extracting(RekeyedResource::getOldKey)
+      .allMatch(t -> t.startsWith(oldProjectKey));
   }
 
   @Test
@@ -169,7 +182,7 @@ public class ComponentKeyUpdaterDaoTest {
     ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("branch/with/slash"));
     String newKey = "newKey";
 
-    underTest.bulkUpdateKey(dbSession, project.uuid(), project.getKey(), newKey);
+    underTest.bulkUpdateKey(dbSession, project.uuid(), project.getKey(), newKey, t -> true);
 
     assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newKey)).hasSize(1);
     assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, ComponentDto.generateBranchKey(newKey, branch.getBranch()))).hasSize(1);
@@ -180,8 +193,8 @@ public class ComponentKeyUpdaterDaoTest {
     ComponentDto project = db.components().insertMainBranch();
     ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST));
     ComponentDto module = db.components().insertComponent(prefixDbKeyWithKey(newModuleDto(pullRequest), project.getKey()));
-    db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey()));
-    db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey()));
+    ComponentDto file1 = db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey()));
+    ComponentDto file2 = db.components().insertComponent(prefixDbKeyWithKey(newFileDto(module), module.getKey()));
     int branchComponentCount = 4;
 
     String oldProjectKey = project.getKey();
@@ -192,7 +205,7 @@ public class ComponentKeyUpdaterDaoTest {
 
     String newProjectKey = "newKey";
     String newPullRequestKey = ComponentDto.generatePullRequestKey(newProjectKey, pullRequest.getPullRequest());
-    underTest.bulkUpdateKey(dbSession, project.uuid(), oldProjectKey, newProjectKey);
+    Set<RekeyedResource> rekeyedResources = underTest.bulkUpdateKey(dbSession, project.uuid(), oldProjectKey, newProjectKey, t -> true);
 
     assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldProjectKey)).isEmpty();
     assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, oldPullRequestKey)).isEmpty();
@@ -201,6 +214,16 @@ public class ComponentKeyUpdaterDaoTest {
     assertThat(dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, newPullRequestKey)).hasSize(branchComponentCount);
     db.select(dbSession, "select kee from projects")
       .forEach(map -> map.values().forEach(k -> assertThat(k.toString()).startsWith(newProjectKey)));
+
+    assertThat(rekeyedResources)
+      .extracting(t -> t.getResource().getUuid())
+      .containsOnly(project.uuid(), pullRequest.uuid(), module.uuid(), file1.uuid(), file2.uuid());
+    assertThat(rekeyedResources)
+      .extracting(t -> t.getResource().getKey())
+      .allMatch(t -> t.startsWith(newProjectKey));
+    assertThat(rekeyedResources)
+      .extracting(RekeyedResource::getOldKey)
+      .allMatch(t -> t.startsWith(oldProjectKey));
   }
 
   private ComponentDto prefixDbKeyWithKey(ComponentDto componentDto, String key) {
@@ -223,7 +246,7 @@ public class ComponentKeyUpdaterDaoTest {
     db.components().insertComponent(newModuleDto(project).setDbKey("my_project:module"));
     db.components().insertComponent(newModuleDto(project).setDbKey("my_project:inactive_module").setEnabled(false));
 
-    underTest.bulkUpdateKey(dbSession, "A", "my_", "your_");
+    Set<RekeyedResource> rekeyedResources = underTest.bulkUpdateKey(dbSession, "A", "my_", "your_", doNotReturnAnyRekeyedResource());
 
     List<ComponentDto> result = dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, "your_project");
     assertThat(result)
@@ -236,7 +259,7 @@ public class ComponentKeyUpdaterDaoTest {
   public void shouldBulkUpdateKey() {
     db.prepareDbUnit(getClass(), "shared.xml");
 
-    underTest.bulkUpdateKey(dbSession, "A", "org.struts", "org.apache.struts");
+    underTest.bulkUpdateKey(dbSession, "A", "org.struts", "org.apache.struts", doNotReturnAnyRekeyedResource());
     dbSession.commit();
 
     db.assertDbUnit(getClass(), "shouldBulkUpdateKey-result.xml", "projects");
@@ -246,7 +269,7 @@ public class ComponentKeyUpdaterDaoTest {
   public void shouldBulkUpdateKeyOnOnlyOneSubmodule() {
     db.prepareDbUnit(getClass(), "shared.xml");
 
-    underTest.bulkUpdateKey(dbSession, "A", "struts-ui", "struts-web");
+    underTest.bulkUpdateKey(dbSession, "A", "struts-ui", "struts-web", doNotReturnAnyRekeyedResource());
     dbSession.commit();
 
     db.assertDbUnit(getClass(), "shouldBulkUpdateKeyOnOnlyOneSubmodule-result.xml", "projects");
@@ -259,7 +282,7 @@ public class ComponentKeyUpdaterDaoTest {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("Impossible to update key: a component with key \"foo:struts-core\" already exists.");
 
-    underTest.bulkUpdateKey(dbSession, "A", "org.struts", "foo");
+    underTest.bulkUpdateKey(dbSession, "A", "org.struts", "foo", doNotReturnAnyRekeyedResource());
     dbSession.commit();
   }
 
@@ -267,7 +290,7 @@ public class ComponentKeyUpdaterDaoTest {
   public void shouldNotUpdateAllSubmodules() {
     db.prepareDbUnit(getClass(), "shouldNotUpdateAllSubmodules.xml");
 
-    underTest.bulkUpdateKey(dbSession, "A", "org.struts", "org.apache.struts");
+    underTest.bulkUpdateKey(dbSession, "A", "org.struts", "org.apache.struts", doNotReturnAnyRekeyedResource());
     dbSession.commit();
 
     db.assertDbUnit(getClass(), "shouldNotUpdateAllSubmodules-result.xml", "projects");
@@ -293,7 +316,7 @@ public class ComponentKeyUpdaterDaoTest {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("Malformed key for 'my?project?key'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");
 
-    underTest.bulkUpdateKey(dbSession, project.uuid(), project.getDbKey(), "my?project?key");
+    underTest.bulkUpdateKey(dbSession, project.uuid(), project.getDbKey(), "my?project?key", doNotReturnAnyRekeyedResource());
   }
 
   @Test
@@ -362,4 +385,8 @@ public class ComponentKeyUpdaterDaoTest {
     assertThat(computeNewKey("my_project", "my_", "your_")).isEqualTo("your_project");
     assertThat(computeNewKey("my_project", "my_", "$()_")).isEqualTo("$()_project");
   }
+
+  private Predicate<RekeyedResource> doNotReturnAnyRekeyedResource() {
+    return a -> false;
+  }
 }
index 3c21cad71d76dc490ed03c7b71c3ebe0fefe4f29..b96e28f37dc19e4c648d12e75e348c0d4b823094 100644 (file)
  */
 package org.sonar.server.component;
 
+import java.util.Set;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.web.UserRole;
+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.component.ComponentKeyUpdaterDao;
+import org.sonar.db.component.ResourceDto;
 import org.sonar.server.es.ProjectIndexer;
 import org.sonar.server.es.ProjectIndexers;
 import org.sonar.server.project.Project;
@@ -31,6 +37,7 @@ import org.sonar.server.project.ProjectLifeCycleListeners;
 import org.sonar.server.project.RekeyedProject;
 import org.sonar.server.user.UserSession;
 
+import static java.util.Collections.singleton;
 import static java.util.Collections.singletonList;
 import static org.sonar.core.component.ComponentKeys.isValidModuleKey;
 import static org.sonar.db.component.ComponentKeyUpdaterDao.checkIsProjectOrModule;
@@ -56,15 +63,41 @@ public class ComponentService {
     checkProjectOrModuleKeyFormat(newKey);
     dbClient.componentKeyUpdaterDao().updateKey(dbSession, projectOrModule.uuid(), newKey);
     projectIndexers.commitAndIndex(dbSession, singletonList(projectOrModule), ProjectIndexer.Cause.PROJECT_KEY_UPDATE);
-    if (projectOrModule.isRootProject() && projectOrModule.getMainBranchProjectUuid() == null) {
+    if (isMainProject(projectOrModule)) {
       Project newProject = new Project(projectOrModule.uuid(), newKey, projectOrModule.name(), projectOrModule.description());
-      projectLifeCycleListeners.onProjectRekeyed(new RekeyedProject(newProject, projectOrModule.getDbKey()));
+      projectLifeCycleListeners.onProjectsRekeyed(singleton(new RekeyedProject(newProject, projectOrModule.getDbKey())));
     }
   }
 
+  private static boolean isMainProject(ComponentDto projectOrModule) {
+    return projectOrModule.isRootProject() && projectOrModule.getMainBranchProjectUuid() == null;
+  }
+
   public void bulkUpdateKey(DbSession dbSession, ComponentDto projectOrModule, String stringToReplace, String replacementString) {
-    dbClient.componentKeyUpdaterDao().bulkUpdateKey(dbSession, projectOrModule.uuid(), stringToReplace, replacementString);
+    Set<ComponentKeyUpdaterDao.RekeyedResource> rekeyedProjects = dbClient.componentKeyUpdaterDao().bulkUpdateKey(
+      dbSession, projectOrModule.uuid(), stringToReplace, replacementString,
+      ComponentService::isMainProject);
     projectIndexers.commitAndIndex(dbSession, singletonList(projectOrModule), ProjectIndexer.Cause.PROJECT_KEY_UPDATE);
+    if (!rekeyedProjects.isEmpty()) {
+      projectLifeCycleListeners.onProjectsRekeyed(rekeyedProjects.stream()
+        .map(ComponentService::toRekeyedProject)
+        .collect(MoreCollectors.toSet(rekeyedProjects.size())));
+    }
+  }
+
+  private static boolean isMainProject(ComponentKeyUpdaterDao.RekeyedResource rekeyedResource) {
+    ResourceDto resource = rekeyedResource.getResource();
+    String resourceKey = resource.getKey();
+    return Scopes.PROJECT.equals(resource.getScope())
+      && Qualifiers.PROJECT.equals(resource.getQualifier())
+      && !resourceKey.contains(ComponentDto.BRANCH_KEY_SEPARATOR)
+      && !resourceKey.contains(ComponentDto.PULL_REQUEST_SEPARATOR);
+  }
+
+  private static RekeyedProject toRekeyedProject(ComponentKeyUpdaterDao.RekeyedResource rekeyedResource) {
+    ResourceDto resource = rekeyedResource.getResource();
+    Project project = new Project(resource.getUuid(), resource.getKey(), resource.getName(), resource.getDescription());
+    return new RekeyedProject(project, rekeyedResource.getOldKey());
   }
 
   private static void checkProjectOrModuleKeyFormat(String key) {
index 3c555307c6df56fc8144c7f3bfbe15d3934e07fe..9b3c092b3db3fcc6026fb57d7f49127c521ff200 100644 (file)
@@ -30,7 +30,7 @@ public interface ProjectLifeCycleListener {
   void onProjectsDeleted(Set<Project> projects);
 
   /**
-   * This method is called after the specified project's key has been modified.
+   * This method is called after the specified projects' keys have been modified.
    */
-  void onProjectRekeyed(RekeyedProject rekeyedProject);
+  void onProjectsRekeyed(Set<RekeyedProject> rekeyedProjects);
 }
index c78083edf0bc205219210ed8fbea995e9aa8f842..9b2c7b76d5999ca387d45d097251115b1a685d8d 100644 (file)
@@ -34,12 +34,12 @@ public interface ProjectLifeCycleListeners {
 
   /**
    * This method is called after the specified project's key has been changed and will call method
-   * {@link ProjectLifeCycleListener#onProjectRekeyed(RekeyedProject) onProjectRekeyed(RekeyedProject)} of all known
+   * {@link ProjectLifeCycleListener#onProjectsRekeyed(Set) onProjectsRekeyed(Set)} of all known
    * {@link ProjectLifeCycleListener} implementations.
    * <p>
    * This method ensures all {@link ProjectLifeCycleListener} implementations are called, even if one or more of
    * them fail with an exception.
    */
-  void onProjectRekeyed(RekeyedProject rekeyedProject);
+  void onProjectsRekeyed(Set<RekeyedProject> rekeyedProjects);
 
 }
index 9714ee98d497124b63739ce68e71cbf9d30667d5..0ca49891b2ee1f58af549196cc30ab13f39fc6cd 100644 (file)
@@ -58,11 +58,14 @@ public class ProjectLifeCycleListenersImpl implements ProjectLifeCycleListeners
   }
 
   @Override
-  public void onProjectRekeyed(RekeyedProject rekeyedProject) {
-    checkNotNull(rekeyedProject, "rekeyedProject can't be null");
+  public void onProjectsRekeyed(Set<RekeyedProject> rekeyedProjects) {
+    checkNotNull(rekeyedProjects, "rekeyedProjects can't be null");
+    if (rekeyedProjects.isEmpty()) {
+      return;
+    }
 
     Arrays.stream(listeners)
-      .forEach(safelyCallListener(listener -> listener.onProjectRekeyed(rekeyedProject)));
+      .forEach(safelyCallListener(listener -> listener.onProjectsRekeyed(rekeyedProjects)));
   }
 
   private static Consumer<ProjectLifeCycleListener> safelyCallListener(Consumer<ProjectLifeCycleListener> task) {
index 3883ad250ca605bc60befe03c296bd479f79aaa3..e638592f57a0c07c1ad7bb81cdab1603dcd676c1 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.component;
 
+import com.google.common.collect.ImmutableSet;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -34,11 +35,14 @@ import org.sonar.server.es.ProjectIndexer;
 import org.sonar.server.es.TestProjectIndexers;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.project.Project;
 import org.sonar.server.project.ProjectLifeCycleListeners;
+import org.sonar.server.project.RekeyedProject;
 import org.sonar.server.tester.UserSessionRule;
 
 import static org.assertj.guava.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.component.ComponentTesting.newModuleDto;
 
@@ -191,6 +195,26 @@ public class ComponentServiceUpdateKeyTest {
     assertComponentKeyUpdated(file.getDbKey(), "your_project:root:module:src/File.xoo");
     assertComponentKeyUpdated(inactiveModule.getDbKey(), "your_project:root:inactive_module");
     assertComponentKeyUpdated(inactiveFile.getDbKey(), "your_project:root:module:src/InactiveFile.xoo");
+    verify(projectLifeCycleListeners).onProjectsRekeyed(ImmutableSet.of(
+      new RekeyedProject(new Project(project.uuid(), "your_project", project.name(), project.uuid()), "my_project")
+    ));
+  }
+
+  @Test
+  public void bulk_update_key_with_branch_and_pr() {
+    ComponentDto project = componentDb.insertComponent(ComponentTesting.newPrivateProjectDto(db.organizations().insert()).setDbKey("my_project"));
+    ComponentDto branch = componentDb.insertProjectBranch(project);
+    ComponentDto module = componentDb.insertComponent(newModuleDto(branch).setDbKey("my_project:root:module"));
+    ComponentDto file = componentDb.insertComponent(newFileDto(module, null).setDbKey("my_project:root:module:src/File.xoo"));
+
+    underTest.bulkUpdateKey(dbSession, project, "my_", "your_");
+
+    assertComponentKeyUpdated(project.getDbKey(), "your_project");
+    assertComponentKeyUpdated(module.getDbKey(), "your_project:root:module");
+    assertComponentKeyUpdated(file.getDbKey(), "your_project:root:module:src/File.xoo");
+    verify(projectLifeCycleListeners).onProjectsRekeyed(ImmutableSet.of(
+      new RekeyedProject(new Project(project.uuid(), "your_project", project.name(), project.uuid()), "my_project")
+    ));
   }
 
   private void assertComponentKeyUpdated(String oldKey, String newKey) {
index 6fb489bbf408353773072fa202a4af6f5d9af285..933e9a3ddc1953390711d26789e4a81d436cf3b9 100644 (file)
@@ -135,6 +135,90 @@ public class ProjectLifeCycleListenersImplTest {
       {IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> newUniqueProject()).collect(MoreCollectors.toSet())}
     };
   }
+  // SDSDS
+
+  @Test
+  public void onProjectsRekeyed_throws_NPE_if_set_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("rekeyedProjects can't be null");
+
+    underTestWithListeners.onProjectsRekeyed(null);
+  }
+
+  @Test
+  public void onProjectsRekeyed_throws_NPE_if_set_is_null_even_if_no_listeners() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("rekeyedProjects can't be null");
+
+    underTestNoListeners.onProjectsRekeyed(null);
+  }
+
+  @Test
+  public void onProjectsRekeyed_has_no_effect_if_set_is_empty() {
+    underTestNoListeners.onProjectsRekeyed(Collections.emptySet());
+
+    underTestWithListeners.onProjectsRekeyed(Collections.emptySet());
+    verifyZeroInteractions(listener1, listener2, listener3);
+  }
+
+  @Test
+  @UseDataProvider("oneOrManyRekeyedProjects")
+  public void onProjectsRekeyed_does_not_fail_if_there_is_no_listener(Set<RekeyedProject> projects) {
+    underTestNoListeners.onProjectsRekeyed(projects);
+  }
+
+  @Test
+  @UseDataProvider("oneOrManyRekeyedProjects")
+  public void onProjectsRekeyed_calls_all_listeners_in_order_of_addition_to_constructor(Set<RekeyedProject> projects) {
+    InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3);
+
+    underTestWithListeners.onProjectsRekeyed(projects);
+
+    inOrder.verify(listener1).onProjectsRekeyed(same(projects));
+    inOrder.verify(listener2).onProjectsRekeyed(same(projects));
+    inOrder.verify(listener3).onProjectsRekeyed(same(projects));
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  @UseDataProvider("oneOrManyRekeyedProjects")
+  public void onProjectsRekeyed_calls_all_listeners_even_if_one_throws_an_Exception(Set<RekeyedProject> projects) {
+    InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3);
+    doThrow(new RuntimeException("Faking listener2 throwing an exception"))
+      .when(listener2)
+      .onProjectsRekeyed(any());
+
+    underTestWithListeners.onProjectsRekeyed(projects);
+
+    inOrder.verify(listener1).onProjectsRekeyed(same(projects));
+    inOrder.verify(listener2).onProjectsRekeyed(same(projects));
+    inOrder.verify(listener3).onProjectsRekeyed(same(projects));
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  @UseDataProvider("oneOrManyRekeyedProjects")
+  public void onProjectsRekeyed_calls_all_listeners_even_if_one_throws_an_Error(Set<RekeyedProject> projects) {
+    InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3);
+    doThrow(new Error("Faking listener2 throwing an Error"))
+      .when(listener2)
+      .onProjectsRekeyed(any());
+
+    underTestWithListeners.onProjectsRekeyed(projects);
+
+    inOrder.verify(listener1).onProjectsRekeyed(same(projects));
+    inOrder.verify(listener2).onProjectsRekeyed(same(projects));
+    inOrder.verify(listener3).onProjectsRekeyed(same(projects));
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @DataProvider
+  public static Object[][] oneOrManyRekeyedProjects() {
+    return new Object[][] {
+      {singleton(newUniqueRekeyedProject())},
+      {IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> newUniqueRekeyedProject()).collect(MoreCollectors.toSet())}
+    };
+  }
 
   private static int counter = 3_989;
 
@@ -142,4 +226,10 @@ public class ProjectLifeCycleListenersImplTest {
     int base = counter++;
     return new Project(base + "_uuid", base + "_key", base + "_name");
   }
+
+  private static RekeyedProject newUniqueRekeyedProject() {
+    int base = counter++;
+    Project project = new Project(base + "_uuid", base + "_key", base + "_name");
+    return new RekeyedProject(project, base + "_old_key");
+  }
 }
index bca7c9f01a1c8fd5036ef404d6959593754d94a8..f397bba193e93cb908b8f0298a875a68952f386b 100644 (file)
@@ -37,6 +37,7 @@ import org.sonar.server.es.ProjectIndexers;
 import org.sonar.server.es.ProjectIndexersImpl;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.project.ProjectLifeCycleListenersImpl;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.WsActionTester;
@@ -57,7 +58,7 @@ public class UpdateKeyActionTest {
   public UserSessionRule userSessionRule = UserSessionRule.standalone();
   private DbClient dbClient = db.getDbClient();
   private ProjectIndexers projectIndexers = new ProjectIndexersImpl();
-  private ComponentService componentService = new ComponentService(dbClient, userSessionRule, projectIndexers);
+  private ComponentService componentService = new ComponentService(dbClient, userSessionRule, projectIndexers, new ProjectLifeCycleListenersImpl());
   private WsActionTester ws = new WsActionTester(new UpdateKeyAction(dbClient, componentService));
 
   @Test