From: Sébastien Lesaint Date: Thu, 29 Mar 2018 13:23:39 +0000 (+0200) Subject: GOV-334 trigger refresh of views on api/projects/bulk_update_key X-Git-Tag: 7.5~1387 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=a76dbbef83ceb1cb8beb8e08e16e5c84d22ddaf0;p=sonarqube.git GOV-334 trigger refresh of views on api/projects/bulk_update_key --- diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java index 8bc0a216bb8..3563b946a24 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java @@ -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 bulkUpdateKey(DbSession session, String projectUuid, String stringToReplace, String replacementString, + Predicate rekeyedResourceFilter) { ComponentKeyUpdaterMapper mapper = session.getMapper(ComponentKeyUpdaterMapper.class); // must SELECT first everything Set modules = collectAllModules(projectUuid, stringToReplace, mapper, true); @@ -128,6 +134,7 @@ public class ComponentKeyUpdaterDao implements Dao { allResourcesByModuleMap.put(module, mapper.selectProjectResources(module.getUuid())); } + Set 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 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 resources, String oldKey, String newKey, ComponentKeyUpdaterMapper mapper) { + private static void runBatchUpdateForAllResources(Collection resources, String oldKey, String newKey, ComponentKeyUpdaterMapper mapper, + @Nullable BiConsumer 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(); } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java index b7f4e9cf381..f16cf6561ca 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java @@ -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 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 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 rekeyedResources = underTest.bulkUpdateKey(dbSession, "A", "my_", "your_", doNotReturnAnyRekeyedResource()); List 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 doNotReturnAnyRekeyedResource() { + return a -> false; + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java index 3c21cad71d7..b96e28f37dc 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java @@ -19,11 +19,17 @@ */ 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 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) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListener.java b/server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListener.java index 3c555307c6d..9b3c092b3db 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListener.java +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListener.java @@ -30,7 +30,7 @@ public interface ProjectLifeCycleListener { void onProjectsDeleted(Set 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 rekeyedProjects); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListeners.java b/server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListeners.java index c78083edf0b..9b2c7b76d59 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListeners.java +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListeners.java @@ -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. *

* 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 rekeyedProjects); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListenersImpl.java b/server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListenersImpl.java index 9714ee98d49..0ca49891b2e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListenersImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListenersImpl.java @@ -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 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 safelyCallListener(Consumer task) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java index 3883ad250ca..e638592f57a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java @@ -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) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ProjectLifeCycleListenersImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ProjectLifeCycleListenersImplTest.java index 6fb489bbf40..933e9a3ddc1 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/project/ProjectLifeCycleListenersImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/project/ProjectLifeCycleListenersImplTest.java @@ -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 projects) { + underTestNoListeners.onProjectsRekeyed(projects); + } + + @Test + @UseDataProvider("oneOrManyRekeyedProjects") + public void onProjectsRekeyed_calls_all_listeners_in_order_of_addition_to_constructor(Set 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 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 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"); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/UpdateKeyActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/UpdateKeyActionTest.java index bca7c9f01a1..f397bba193e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/project/ws/UpdateKeyActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/UpdateKeyActionTest.java @@ -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