]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16659 - Create ProjectKeyChanged Events
authorBelen Pruvost <belen.pruvost@sonarsource.com>
Fri, 29 Jul 2022 07:06:08 +0000 (09:06 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 29 Jul 2022 20:03:14 +0000 (20:03 +0000)
14 files changed:
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/scheduler/polling/PushEventPollScheduler.java
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClient.java
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientPermissionsValidator.java
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistry.java
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushAction.java
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushEvent.java
server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/polling/PushEventPollSchedulerTest.java
server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientPermissionsValidatorTest.java
server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientTest.java
server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistryTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentService.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ProjectKeyChangedEvent.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ProjectKeyChangedEventTest.java [new file with mode: 0644]

index d7b0f9d01ab4b7679d78acceee105ef21e9ff77b..a44bac88c75f6296bb450ee08245d4107080d250 100644 (file)
@@ -23,12 +23,10 @@ import java.util.Collection;
 import java.util.Deque;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
-import org.jetbrains.annotations.NotNull;
 import org.sonar.api.Startable;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.server.ServerSide;
@@ -37,7 +35,6 @@ import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
-import org.sonar.db.project.ProjectDto;
 import org.sonar.db.pushevent.PushEventDto;
 import org.sonar.server.pushapi.sonarlint.SonarLintClient;
 import org.sonar.server.pushapi.sonarlint.SonarLintClientsRegistry;
@@ -95,16 +92,15 @@ public class PushEventPollScheduler implements Startable {
       lastPullTimestamp = getLastPullTimestamp();
     }
 
-    var projectKeys = getClientsProjectKeys(clients);
+    var projectUuids = getClientsProjectUuids(clients);
 
     try (DbSession dbSession = dbClient.openSession(false)) {
-      var projectKeysByUuids = getProjectKeysByUuids(dbSession, projectKeys);
-      Deque<PushEventDto> events = getPushEvents(dbSession, projectKeysByUuids.keySet());
+      Deque<PushEventDto> events = getPushEvents(dbSession, projectUuids);
 
       LOG.debug("Received {} push events, attempting to broadcast to {} registered clients.", events.size(),
         clients.size());
 
-      events.forEach(pushEventDto -> mapToSonarLintPushEvent(pushEventDto, projectKeysByUuids)
+      events.forEach(pushEventDto -> mapToSonarLintPushEvent(pushEventDto)
         .ifPresent(clientsRegistry::broadcastMessage));
 
       if (!events.isEmpty()) {
@@ -115,19 +111,14 @@ public class PushEventPollScheduler implements Startable {
     }
   }
 
-  private static Optional<SonarLintPushEvent> mapToSonarLintPushEvent(PushEventDto pushEventDto, Map<String, String> projectKeysByUuids) {
-    var resolvedProjectKey = projectKeysByUuids.get(pushEventDto.getProjectUuid());
-    if (resolvedProjectKey == null) {
-      LOG.debug("Could not find key for project with uuid [{}]", pushEventDto.getProjectUuid());
-      return Optional.empty();
-    }
-    return Optional.of(new SonarLintPushEvent(pushEventDto.getName(), pushEventDto.getPayload(), resolvedProjectKey,
+  private static Optional<SonarLintPushEvent> mapToSonarLintPushEvent(PushEventDto pushEventDto) {
+    return Optional.of(new SonarLintPushEvent(pushEventDto.getName(), pushEventDto.getPayload(), pushEventDto.getProjectUuid(),
       pushEventDto.getLanguage()));
   }
 
-  private static Set<String> getClientsProjectKeys(List<SonarLintClient> clients) {
+  private static Set<String> getClientsProjectUuids(List<SonarLintClient> clients) {
     return clients.stream()
-      .map(SonarLintClient::getClientProjectKeys)
+      .map(SonarLintClient::getClientProjectUuids)
       .flatMap(Collection::stream)
       .collect(Collectors.toSet());
   }
@@ -139,13 +130,6 @@ public class PushEventPollScheduler implements Startable {
     return dbClient.pushEventDao().selectChunkByProjectUuids(dbSession, projectUuids, lastPullTimestamp, lastSeenUuid, getPageSize());
   }
 
-  @NotNull
-  private Map<String, String> getProjectKeysByUuids(DbSession dbSession, Set<String> projectKeys) {
-    return dbClient.projectDao().selectProjectsByKeys(dbSession, projectKeys)
-      .stream()
-      .collect(Collectors.toMap(ProjectDto::getUuid, ProjectDto::getKey));
-  }
-
   public long getInitialDelay() {
     // two minutes default initial delay
     return config.getLong(INITIAL_DELAY_IN_SECONDS).orElse(2 * 60L);
index 3d0f5cff07f986b409abc967276ba15f6522b7b6..d65d47a18cccc4c4a0595fdb3bcd24faa46ee901 100644 (file)
@@ -31,13 +31,13 @@ public class SonarLintClient extends ServerPushClient {
   private static final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
 
   private final Set<String> languages;
-  private final Set<String> projectKeys;
+  private final Set<String> projectUuids;
 
   private final String userUuid;
 
-  public SonarLintClient(AsyncContext asyncContext, Set<String> projectKeys, Set<String> languages, String userUuid) {
+  public SonarLintClient(AsyncContext asyncContext, Set<String> projectUuids, Set<String> languages, String userUuid) {
     super(scheduledExecutorService, asyncContext);
-    this.projectKeys = projectKeys;
+    this.projectUuids = projectUuids;
     this.languages = languages;
     this.userUuid = userUuid;
   }
@@ -46,8 +46,8 @@ public class SonarLintClient extends ServerPushClient {
     return languages;
   }
 
-  public Set<String> getClientProjectKeys() {
-    return projectKeys;
+  public Set<String> getClientProjectUuids() {
+    return projectUuids;
   }
 
   @Override
@@ -60,13 +60,13 @@ public class SonarLintClient extends ServerPushClient {
     }
     SonarLintClient that = (SonarLintClient) o;
     return languages.equals(that.languages)
-      && projectKeys.equals(that.projectKeys)
+      && projectUuids.equals(that.projectUuids)
       && asyncContext.equals(that.asyncContext);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(languages, projectKeys);
+    return Objects.hash(languages, projectUuids);
   }
 
   public String getUserUuid() {
index d8765332e901576b269ff74f5e22df5195f3e3ec..cff2e87f354708931af0d466275d353982cc5db6 100644 (file)
@@ -42,26 +42,36 @@ public class SonarLintClientPermissionsValidator {
     this.userSessionFactory = userSessionFactory;
   }
 
-  public void validateUserCanReceivePushEventForProjects(UserSession userSession, Set<String> projectKeys) {
+  public List<ProjectDto> validateUserCanReceivePushEventForProjects(UserSession userSession, Set<String> projectKeys) {
     List<ProjectDto> projectDtos;
     try (DbSession dbSession = dbClient.openSession(false)) {
       projectDtos = dbClient.projectDao().selectProjectsByKeys(dbSession, projectKeys);
     }
-    validateUsersDeactivationStatus(userSession);
-    for (ProjectDto projectDto : projectDtos) {
-      userSession.checkProjectPermission(UserRole.USER, projectDto);
-    }
+    validateProjectPermissions(userSession, projectDtos);
+    return projectDtos;
   }
 
-  public void validateUserCanReceivePushEventForProjects(String userUUID, Set<String> projectKeys) {
+  public void validateUserCanReceivePushEventForProjectUuids(String userUuid, Set<String> projectUuids) {
     UserDto userDto;
     try (DbSession dbSession = dbClient.openSession(false)) {
-      userDto = dbClient.userDao().selectByUuid(dbSession, userUUID);
+      userDto = dbClient.userDao().selectByUuid(dbSession, userUuid);
     }
     if (userDto == null) {
       throw new ForbiddenException("User does not exist");
     }
-    validateUserCanReceivePushEventForProjects(userSessionFactory.create(userDto), projectKeys);
+    UserSession userSession = userSessionFactory.create(userDto);
+    List<ProjectDto> projectDtos;
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      projectDtos = dbClient.projectDao().selectByUuids(dbSession, projectUuids);
+    }
+    validateProjectPermissions(userSession, projectDtos);
+  }
+
+  private static void validateProjectPermissions(UserSession userSession, List<ProjectDto> projectDtos) {
+    validateUsersDeactivationStatus(userSession);
+    for (ProjectDto projectDto : projectDtos) {
+      userSession.checkProjectPermission(UserRole.USER, projectDto);
+    }
   }
 
   private static void validateUsersDeactivationStatus(UserSession userSession) {
index ede8477eea32e272c2fc92a8deb58f788d72832f..3d564ba79707e4a910c86850df72adcb03f46257 100644 (file)
@@ -67,10 +67,10 @@ public class SonarLintClientsRegistry {
   public void broadcastMessage(SonarLintPushEvent event) {
     clients.stream().filter(client -> isRelevantEvent(event, client))
       .forEach(c -> {
-        Set<String> clientProjectKeys = new HashSet<>(c.getClientProjectKeys());
-        clientProjectKeys.retainAll(Set.of(event.getProjectKey()));
+        Set<String> clientProjectUuids = new HashSet<>(c.getClientProjectUuids());
+        clientProjectUuids.retainAll(Set.of(event.getProjectUuid()));
         try {
-          sonarLintClientPermissionsValidator.validateUserCanReceivePushEventForProjects(c.getUserUuid(), clientProjectKeys);
+          sonarLintClientPermissionsValidator.validateUserCanReceivePushEventForProjectUuids(c.getUserUuid(), clientProjectUuids);
           c.writeAndFlush(event.serialize());
         } catch (ForbiddenException forbiddenException) {
           logClientUnauthenticated(forbiddenException);
@@ -83,7 +83,7 @@ public class SonarLintClientsRegistry {
   }
 
   private static boolean isRelevantEvent(SonarLintPushEvent event, SonarLintClient client) {
-    return client.getClientProjectKeys().contains(event.getProjectKey())
+    return client.getClientProjectUuids().contains(event.getProjectUuid())
       && (!event.getName().equals("RuleSetChanged") || client.getLanguages().contains(event.getLanguage()));
   }
 
index 6a63de5f20a0895427798be100dc752fcd2702b5..ed9aabfbbd1c4f8525738f64b1b1695e657568e3 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.server.pushapi.sonarlint;
 import java.io.IOException;
 import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
 import javax.servlet.AsyncContext;
 import javax.servlet.http.HttpServletResponse;
 import org.sonar.api.server.ws.Request;
@@ -84,7 +85,7 @@ public class SonarLintPushAction extends ServerPushAction {
     var params = new SonarLintPushActionParamsValidator(request);
     params.validateParams();
 
-    permissionsValidator.validateUserCanReceivePushEventForProjects(userSession, params.projectKeys);
+    List<ProjectDto> projectDtos = permissionsValidator.validateUserCanReceivePushEventForProjects(userSession, params.projectKeys);
 
     if (!isServerSideEventsRequest(servletRequest)) {
       servletResponse.stream().setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
@@ -96,7 +97,9 @@ public class SonarLintPushAction extends ServerPushAction {
     AsyncContext asyncContext = servletRequest.startAsync();
     asyncContext.setTimeout(0);
 
-    SonarLintClient sonarLintClient = new SonarLintClient(asyncContext, params.getProjectKeys(), params.getLanguages(), userSession.getUuid());
+    Set<String> projectUuids = projectDtos.stream().map(ProjectDto::getUuid).collect(Collectors.toSet());
+
+    SonarLintClient sonarLintClient = new SonarLintClient(asyncContext, projectUuids, params.getLanguages(), userSession.getUuid());
 
     clientsRegistry.registerClient(sonarLintClient);
   }
@@ -113,10 +116,6 @@ public class SonarLintPushAction extends ServerPushAction {
       this.languages = parseParam(LANGUAGE_PARAM_KEY);
     }
 
-    Set<String> getProjectKeys() {
-      return projectKeys;
-    }
-
     Set<String> getLanguages() {
       return languages;
     }
index 9e7ee98f0441a02a0c94d20ea0e75ce1339c6328..24d5fc9daecf94fc91c5a283302ac6cdead8d61b 100644 (file)
@@ -28,18 +28,18 @@ public class SonarLintPushEvent {
 
   private final String name;
   private final byte[] data;
-  private final String projectKey;
+  private final String projectUuid;
   private final String language;
 
-  public SonarLintPushEvent(String name, byte[] data, String projectKey, @Nullable String language) {
+  public SonarLintPushEvent(String name, byte[] data, String projectUuid, @Nullable String language) {
     this.name = name;
     this.data = data;
-    this.projectKey = projectKey;
+    this.projectUuid = projectUuid;
     this.language = language;
   }
 
-  public String getProjectKey() {
-    return projectKey;
+  public String getProjectUuid() {
+    return projectUuid;
   }
 
   @CheckForNull
index 4dc96dfa0cb7ef23b9ee68f671a3a61deae0df9b..9403500af57318bb5a1b0df67532af86ba2fc198 100644 (file)
@@ -89,7 +89,7 @@ public class PushEventPollSchedulerTest {
     var project = db.components().insertPrivateProject();
 
     var sonarLintClient = mock(SonarLintClient.class);
-    when(sonarLintClient.getClientProjectKeys()).thenReturn(Set.of(project.getDbKey()));
+    when(sonarLintClient.getClientProjectUuids()).thenReturn(Set.of(project.uuid()));
     when(clientsRegistry.getClients()).thenReturn(List.of(sonarLintClient));
 
     var underTest = new PushEventPollScheduler(executorService, clientsRegistry, db.getDbClient(), system2, config);
@@ -106,7 +106,7 @@ public class PushEventPollSchedulerTest {
 
     system2.setNow(1L);
     var sonarLintClient = mock(SonarLintClient.class);
-    when(sonarLintClient.getClientProjectKeys()).thenReturn(Set.of("not-existing-project-key"));
+    when(sonarLintClient.getClientProjectUuids()).thenReturn(Set.of("not-existing-project-uuid"));
     when(clientsRegistry.getClients()).thenReturn(List.of(sonarLintClient));
 
     var underTest = new PushEventPollScheduler(executorService, clientsRegistry, db.getDbClient(), system2, config);
@@ -128,7 +128,7 @@ public class PushEventPollSchedulerTest {
 
     system2.setNow(1L);
     var sonarLintClient = mock(SonarLintClient.class);
-    when(sonarLintClient.getClientProjectKeys()).thenReturn(Set.of(project.getDbKey()));
+    when(sonarLintClient.getClientProjectUuids()).thenReturn(Set.of(project.uuid()));
     when(clientsRegistry.getClients()).thenReturn(List.of(sonarLintClient));
 
     var underTest = new PushEventPollScheduler(executorService, clientsRegistry, db.getDbClient(), system2, config);
@@ -164,7 +164,7 @@ public class PushEventPollSchedulerTest {
 
     system2.setNow(1L);
     var sonarLintClient = mock(SonarLintClient.class);
-    when(sonarLintClient.getClientProjectKeys()).thenReturn(Set.of(project.getDbKey()));
+    when(sonarLintClient.getClientProjectUuids()).thenReturn(Set.of(project.uuid()));
     when(clientsRegistry.getClients()).thenReturn(List.of(sonarLintClient), emptyList());
 
     var underTest = new PushEventPollScheduler(executorService, clientsRegistry, db.getDbClient(), system2, config);
index 0dea66827e5703d1834ac4a398fa4f2dad860f61..89ea1b7d6252e55cc93a71badd928a005bcb5031 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.server.pushapi.sonarlint;
 
 import java.util.List;
 import java.util.Set;
-import org.assertj.core.api.Assertions;
 import org.junit.Before;
 import org.junit.Test;
 import org.sonar.db.DbClient;
@@ -43,7 +42,7 @@ public class SonarLintClientPermissionsValidatorTest {
 
   private final static String USER_UUID = "USER_UUID";
 
-  private final Set<String> exampleProjectKeys = Set.of("project1", "project2");
+  private final Set<String> exampleProjectuuids = Set.of("project1", "project2");
   private final List<ProjectDto> projectDtos = List.of(mock(ProjectDto.class), mock(ProjectDto.class));
   private final DbClient dbClient = mock(DbClient.class);
   private final UserSessionFactory userSessionFactory = mock(UserSessionFactory.class);
@@ -59,6 +58,7 @@ public class SonarLintClientPermissionsValidatorTest {
     when(dbClient.projectDao()).thenReturn(projectDao);
     when(userSessionFactory.create(any())).thenReturn(userSession);
     when(projectDao.selectProjectsByKeys(any(), any())).thenReturn(projectDtos);
+    when(projectDao.selectByUuids(any(), any())).thenReturn(projectDtos);
   }
 
   @Test
@@ -67,7 +67,7 @@ public class SonarLintClientPermissionsValidatorTest {
     when(userDao.selectByUuid(any(), any())).thenReturn(userDto);
     when(userSession.isActive()).thenReturn(true);
 
-    assertThatCode(() -> underTest.validateUserCanReceivePushEventForProjects(USER_UUID, exampleProjectKeys))
+    assertThatCode(() -> underTest.validateUserCanReceivePushEventForProjectUuids(USER_UUID, exampleProjectuuids))
       .doesNotThrowAnyException();
   }
 
@@ -78,7 +78,7 @@ public class SonarLintClientPermissionsValidatorTest {
     when(userSession.isActive()).thenReturn(false);
 
     assertThrows(ForbiddenException.class,
-      () -> underTest.validateUserCanReceivePushEventForProjects(USER_UUID, exampleProjectKeys));
+      () -> underTest.validateUserCanReceivePushEventForProjectUuids(USER_UUID, exampleProjectuuids));
   }
 
   @Test
@@ -89,6 +89,6 @@ public class SonarLintClientPermissionsValidatorTest {
     when(userSession.checkProjectPermission(any(), any())).thenThrow(ForbiddenException.class);
 
     assertThrows(ForbiddenException.class,
-      () -> underTest.validateUserCanReceivePushEventForProjects(USER_UUID, exampleProjectKeys));
+      () -> underTest.validateUserCanReceivePushEventForProjectUuids(USER_UUID, exampleProjectuuids));
   }
 }
index cc890386a5b3e0b934cc042ec957a9c40c818b66..7e71e1ad6327131f1bd3964c02b5d1cb16a47bd3 100644 (file)
@@ -58,7 +58,7 @@ public class SonarLintClientTest {
   }
 
   @Test
-  public void equals_twoClientsWithDifferentProjectKeys() {
+  public void equals_twoClientsWithDifferentProjectUuids() {
     SonarLintClient first = new SonarLintClient(firstContext, Set.of("project1", "project2"), Set.of(), USER_UUID);
     SonarLintClient second = new SonarLintClient(firstContext, Set.of("project1"), Set.of(), USER_UUID);
 
index 2f5867216dcd65f047652f971f8e5072e007c107..bc39a7c724534bb4ebe3eaebc1a8bc98a645f657 100644 (file)
@@ -48,7 +48,7 @@ public class SonarLintClientsRegistryTest {
 
   private final AsyncContext defaultAsyncContext = mock(AsyncContext.class);
 
-  private final Set<String> exampleKeys = Set.of("project1", "project2", "project3");
+  private final Set<String> exampleProjectUuids = Set.of("project1", "project2", "project3");
   private final Set<String> languageKeys = Set.of("language1", "language2", "language3");
   private final String USER_UUID = "userUuid";
   private final ServletResponse response = mock(ServletResponse.class);
@@ -83,7 +83,7 @@ public class SonarLintClientsRegistryTest {
   public void registering10Clients_10ClientsAreRegistered() {
     for (int i = 0; i < 10; i++) {
       AsyncContext newAsyncContext = mock(AsyncContext.class);
-      SonarLintClient sonarLintClient = new SonarLintClient(newAsyncContext, exampleKeys, languageKeys, USER_UUID);
+      SonarLintClient sonarLintClient = new SonarLintClient(newAsyncContext, exampleProjectUuids, languageKeys, USER_UUID);
       underTest.registerClient(sonarLintClient);
     }
 
@@ -95,7 +95,7 @@ public class SonarLintClientsRegistryTest {
     Set<String> javaLanguageKey = Set.of("java");
     when(defaultAsyncContext.getResponse()).thenReturn(response);
     when(response.getOutputStream()).thenReturn(outputStream);
-    SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleKeys, javaLanguageKey, USER_UUID);
+    SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleProjectUuids, javaLanguageKey, USER_UUID);
 
     underTest.registerClient(sonarLintClient);
 
@@ -136,7 +136,7 @@ public class SonarLintClientsRegistryTest {
     Set<String> jsLanguageKey = Set.of("js");
     when(defaultAsyncContext.getResponse()).thenReturn(response);
     when(response.getOutputStream()).thenReturn(outputStream);
-    SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleKeys, jsLanguageKey, USER_UUID);
+    SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleProjectUuids, jsLanguageKey, USER_UUID);
 
     underTest.registerClient(sonarLintClient);
 
@@ -163,7 +163,7 @@ public class SonarLintClientsRegistryTest {
     underTest.broadcastMessage(event1);
     underTest.broadcastMessage(event2);
 
-    verify(permissionsValidator, times(1)).validateUserCanReceivePushEventForProjects(anyString(), argument.capture());
+    verify(permissionsValidator, times(1)).validateUserCanReceivePushEventForProjectUuids(anyString(), argument.capture());
     assertThat(argument.getValue()).hasSize(1).contains("projA");
   }
 
@@ -171,7 +171,7 @@ public class SonarLintClientsRegistryTest {
   public void listen_givenUserNotPermittedToReceiveEvent_closeConnection() {
     SonarLintClient sonarLintClient = createSampleSLClient();
     underTest.registerClient(sonarLintClient);
-    doThrow(new ForbiddenException("Access forbidden")).when(permissionsValidator).validateUserCanReceivePushEventForProjects(anyString(), anySet());
+    doThrow(new ForbiddenException("Access forbidden")).when(permissionsValidator).validateUserCanReceivePushEventForProjectUuids(anyString(), anySet());
 
     SonarLintPushEvent event = new SonarLintPushEvent(EVENT_NAME, "data".getBytes(StandardCharsets.UTF_8), "project1", "java");
 
@@ -207,7 +207,7 @@ public class SonarLintClientsRegistryTest {
 
     underTest.broadcastMessage(event);
 
-    verify(permissionsValidator, times(1)).validateUserCanReceivePushEventForProjects(anyString(), anySet());
+    verify(permissionsValidator, times(1)).validateUserCanReceivePushEventForProjectUuids(anyString(), anySet());
     verify(sonarLintClient, times(1)).writeAndFlush(anyString());
   }
 
@@ -220,7 +220,7 @@ public class SonarLintClientsRegistryTest {
 
     underTest.broadcastMessage(event);
 
-    verify(permissionsValidator, times(0)).validateUserCanReceivePushEventForProjects(anyString(), anySet());
+    verify(permissionsValidator, times(0)).validateUserCanReceivePushEventForProjectUuids(anyString(), anySet());
     verify(sonarLintClient, times(0)).close();
     verify(sonarLintClient, times(0)).writeAndFlush(anyString());
   }
@@ -231,7 +231,7 @@ public class SonarLintClientsRegistryTest {
 
     SonarLintClient sonarLintClient = createSampleSLClient();
     underTest.registerClient(sonarLintClient);
-    doThrow(new ForbiddenException("Access forbidden")).when(permissionsValidator).validateUserCanReceivePushEventForProjects(anyString(), anySet());
+    doThrow(new ForbiddenException("Access forbidden")).when(permissionsValidator).validateUserCanReceivePushEventForProjectUuids(anyString(), anySet());
 
     underTest.broadcastMessage(event);
 
@@ -267,7 +267,7 @@ public class SonarLintClientsRegistryTest {
   private SonarLintClient createSampleSLClient() {
     SonarLintClient mock = mock(SonarLintClient.class);
     when(mock.getLanguages()).thenReturn(Set.of("java"));
-    when(mock.getClientProjectKeys()).thenReturn(exampleKeys);
+    when(mock.getClientProjectUuids()).thenReturn(exampleProjectUuids);
     when(mock.getUserUuid()).thenReturn("userUuid");
     return mock;
   }
index c7c77c047f0c8ad5f8d934c615fd87f516f1412b..92406d4447d477736659a4dbdbcc95d06a4880a3 100644 (file)
  */
 package org.sonar.server.component;
 
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.web.UserRole;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.project.ProjectDto;
+import org.sonar.db.pushevent.PushEventDto;
 import org.sonar.server.es.ProjectIndexer;
 import org.sonar.server.es.ProjectIndexers;
 import org.sonar.server.project.Project;
@@ -31,12 +34,15 @@ import org.sonar.server.project.ProjectLifeCycleListeners;
 import org.sonar.server.project.RekeyedProject;
 import org.sonar.server.user.UserSession;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Collections.singleton;
 import static java.util.Collections.singletonList;
 import static org.sonar.core.component.ComponentKeys.checkProjectKey;
 
 @ServerSide
 public class ComponentService {
+  private static final Gson GSON = new GsonBuilder().create();
+
   private final DbClient dbClient;
   private final UserSession userSession;
   private final ProjectIndexers projectIndexers;
@@ -56,6 +62,23 @@ public class ComponentService {
     projectIndexers.commitAndIndexProjects(dbSession, singletonList(project), ProjectIndexer.Cause.PROJECT_KEY_UPDATE);
     Project newProject = new Project(project.getUuid(), newKey, project.getName(), project.getDescription(), project.getTags());
     projectLifeCycleListeners.onProjectsRekeyed(singleton(new RekeyedProject(newProject, project.getKey())));
+    persistEvent(project, newKey);
+  }
+
+  private void persistEvent(ProjectDto project, String newProjectKey) {
+    ProjectKeyChangedEvent event = new ProjectKeyChangedEvent(project.getKey(), newProjectKey);
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      PushEventDto eventDto = new PushEventDto()
+        .setName("ProjectKeyChanged")
+        .setProjectUuid(project.getUuid())
+        .setPayload(serializeIssueToPushEvent(event));
+      dbClient.pushEventDao().insert(dbSession, eventDto);
+      dbSession.commit();
+    }
+  }
+
+  private static byte[] serializeIssueToPushEvent(ProjectKeyChangedEvent event) {
+    return GSON.toJson(event).getBytes(UTF_8);
   }
 
 }
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ProjectKeyChangedEvent.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ProjectKeyChangedEvent.java
new file mode 100644 (file)
index 0000000..0fe1c1a
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.component;
+
+import java.io.Serializable;
+
+public class ProjectKeyChangedEvent implements Serializable {
+  private static final String EVENT = "ProjectKeyChanged";
+
+  private final String oldProjectKey;
+  private final String newProjectKey;
+
+  public ProjectKeyChangedEvent(String oldProjectKey, String newProjectKey) {
+    this.oldProjectKey = oldProjectKey;
+    this.newProjectKey = newProjectKey;
+  }
+
+  public String getEvent() {
+    return EVENT;
+  }
+
+  public String getOldProjectKey() {
+    return oldProjectKey;
+  }
+
+  public String getNewProjectKey() {
+    return newProjectKey;
+  }
+
+}
index 73bc00c7bb32de725a08a5294b18438b0e0b8bbd..5c4d384f0f16e922a3f7e9ff675f666f9ac20b0c 100644 (file)
  */
 package org.sonar.server.component;
 
+import java.nio.charset.StandardCharsets;
+import java.util.Deque;
+import java.util.Optional;
+import java.util.Set;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.utils.System2;
@@ -30,6 +34,7 @@ import org.sonar.db.component.ComponentDbTester;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
 import org.sonar.db.project.ProjectDto;
+import org.sonar.db.pushevent.PushEventDto;
 import org.sonar.server.es.ProjectIndexer;
 import org.sonar.server.es.TestProjectIndexers;
 import org.sonar.server.exceptions.ForbiddenException;
@@ -80,6 +85,17 @@ public class ComponentServiceUpdateKeyTest {
     assertThat(dbClient.componentDao().selectByKey(dbSession, inactiveFile.getDbKey())).isEmpty();
 
     assertThat(projectIndexers.hasBeenCalled(project.uuid(), ProjectIndexer.Cause.PROJECT_KEY_UPDATE)).isTrue();
+
+    Deque<PushEventDto> pushEvents = db.getDbClient().pushEventDao().selectChunkByProjectUuids(dbSession, Set.of(project.uuid()), 0L, "id", 20);
+
+    assertThat(pushEvents).isNotEmpty();
+
+    Optional<PushEventDto> event = pushEvents.stream().filter(e -> e.getProjectUuid().equals(project.uuid()) && e.getName().equals("ProjectKeyChanged")).findFirst();
+    assertThat(event).isNotEmpty();
+
+    String payload = new String(event.get().getPayload(), StandardCharsets.UTF_8);
+
+    assertThat(payload).isEqualTo("{\"oldProjectKey\":\"sample:root\",\"newProjectKey\":\"sample2:root\"}");
   }
 
   @Test
@@ -116,8 +132,8 @@ public class ComponentServiceUpdateKeyTest {
     String anotherProjectDbKey = anotherProject.getDbKey();
     assertThatThrownBy(() -> underTest.updateKey(dbSession, projectDto,
       anotherProjectDbKey))
-        .isInstanceOf(IllegalArgumentException.class)
-        .hasMessage("Impossible to update key: a component with key \"" + anotherProjectDbKey + "\" already exists.");
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Impossible to update key: a component with key \"" + anotherProjectDbKey + "\" already exists.");
   }
 
   @Test
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ProjectKeyChangedEventTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ProjectKeyChangedEventTest.java
new file mode 100644 (file)
index 0000000..e774ad9
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.component;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+public class ProjectKeyChangedEventTest {
+
+  @Test
+  public void gettersAndSettersWorkCorrectly() {
+    ProjectKeyChangedEvent underTest = new ProjectKeyChangedEvent("oldKey", "newKey");
+    assertThat(underTest.getEvent()).isEqualTo("ProjectKeyChanged");
+    assertThat(underTest.getOldProjectKey()).isEqualTo("oldKey");
+    assertThat(underTest.getNewProjectKey()).isEqualTo("newKey");
+  }
+}