]> source.dussan.org Git - sonarqube.git/commitdiff
GOV-331 trigger views refresh on api/projects/delete
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 27 Mar 2018 10:30:12 +0000 (12:30 +0200)
committerSonarTech <sonartech@sonarsource.com>
Fri, 6 Apr 2018 18:21:52 +0000 (20:21 +0200)
server/sonar-server/src/main/java/org/sonar/server/project/Project.java
server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListener.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListeners.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListenersImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/project/ws/DeleteAction.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java

index dd368746f45f5a2c5f4d189a2df8ce20b441e128..af220370e3417ec4748668518d31b0c6d5ea03ee 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.server.project;
 import java.util.Objects;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
+import org.sonar.db.component.ComponentDto;
 
 @Immutable
 public class Project {
@@ -42,6 +43,10 @@ public class Project {
     this.description = description;
   }
 
+  public static Project from(ComponentDto project) {
+    return new Project(project.uuid(), project.getDbKey(), project.name(), project.description());
+  }
+
   /**
    * Always links to a row that exists in database.
    */
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
new file mode 100644 (file)
index 0000000..5a53ef9
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.project;
+
+import org.sonar.api.server.ServerSide;
+
+@ServerSide
+public interface ProjectLifeCycleListener {
+  /**
+   * This method is called after the specified project has been deleted.
+   */
+  void onProjectDeleted(Project project);
+}
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
new file mode 100644 (file)
index 0000000..0198ae2
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.project;
+
+public interface ProjectLifeCycleListeners {
+  /**
+   * This method is called after the specified project has been deleted and will call method
+   * {@link ProjectLifeCycleListener#onProjectDeleted(Project) onProjectDeleted(Project)} of all known
+   * {@link ProjectLifeCycleListener} implementations.
+   *
+   * This method will ensure all {@link ProjectLifeCycleListener} implementations are called, even if one or more of
+   * them fail with an exception.
+   */
+  void onProjectDeleted(Project project);
+}
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
new file mode 100644 (file)
index 0000000..03cfbc9
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.project;
+
+import com.google.common.base.Preconditions;
+import java.util.Arrays;
+import java.util.function.Consumer;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+public class ProjectLifeCycleListenersImpl implements ProjectLifeCycleListeners {
+  private static final Logger LOG = Loggers.get(ProjectLifeCycleListenersImpl.class);
+
+  private final ProjectLifeCycleListener[] listeners;
+
+  /**
+   * Used by Pico when there is no ProjectLifeCycleListener implementation in container.
+   */
+  public ProjectLifeCycleListenersImpl() {
+    this.listeners = new ProjectLifeCycleListener[0];
+  }
+
+  public ProjectLifeCycleListenersImpl(ProjectLifeCycleListener[] listeners) {
+    this.listeners = listeners;
+  }
+
+  @Override
+  public void onProjectDeleted(Project project) {
+    Preconditions.checkNotNull(project, "project can't be null");
+
+    Arrays.stream(listeners)
+      .forEach(safelyCallListener(listener -> listener.onProjectDeleted(project)));
+  }
+
+  private static Consumer<ProjectLifeCycleListener> safelyCallListener(Consumer<ProjectLifeCycleListener> task) {
+    return listener -> {
+      try {
+        task.accept(listener);
+      } catch (Error | Exception e) {
+        LOG.error("Call to ProjectLifeCycleListener \"{}\" failed", listener.getClass(), e);
+      }
+    };
+  }
+}
index 3fd58c17d7f0ad4cea463fdf3406e4a803a8bedd..50b7f34e808b6da928648bd9cfbe2385681c768f 100644 (file)
@@ -26,12 +26,14 @@ import org.sonar.api.web.UserRole;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.component.ComponentDto;
+import org.sonar.db.permission.OrganizationPermission;
 import org.sonar.server.component.ComponentCleanerService;
 import org.sonar.server.component.ComponentFinder;
-import org.sonar.db.permission.OrganizationPermission;
+import org.sonar.server.project.ProjectLifeCycleListeners;
 import org.sonar.server.user.UserSession;
 
 import static org.sonar.server.component.ComponentFinder.ParamNames.PROJECT_ID_AND_PROJECT;
+import static org.sonar.server.project.Project.from;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
 import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT;
 import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT_ID;
@@ -43,12 +45,15 @@ public class DeleteAction implements ProjectsWsAction {
   private final ComponentFinder componentFinder;
   private final DbClient dbClient;
   private final UserSession userSession;
+  private final ProjectLifeCycleListeners projectLifeCycleListeners;
 
-  public DeleteAction(ComponentCleanerService componentCleanerService, ComponentFinder componentFinder, DbClient dbClient, UserSession userSession) {
+  public DeleteAction(ComponentCleanerService componentCleanerService, ComponentFinder componentFinder, DbClient dbClient,
+    UserSession userSession, ProjectLifeCycleListeners projectLifeCycleListeners) {
     this.componentCleanerService = componentCleanerService;
     this.componentFinder = componentFinder;
     this.dbClient = dbClient;
     this.userSession = userSession;
+    this.projectLifeCycleListeners = projectLifeCycleListeners;
   }
 
   @Override
@@ -86,6 +91,7 @@ public class DeleteAction implements ProjectsWsAction {
       ComponentDto project = componentFinder.getByUuidOrKey(dbSession, uuid, key, PROJECT_ID_AND_PROJECT);
       checkPermission(project);
       componentCleanerService.delete(dbSession, project);
+      projectLifeCycleListeners.onProjectDeleted(from(project));
     }
 
     response.noContent();
index 44c8a64de9a58dfcd0e55efc3d8a12034287994c..a466e01ba450df93009ade2ce611b2f7bdd360bd 100644 (file)
 package org.sonar.server.project.ws;
 
 import org.sonar.core.platform.Module;
+import org.sonar.server.project.ProjectLifeCycleListenersImpl;
 
 public class ProjectsWsModule extends Module {
+
   @Override
   protected void configureModule() {
     add(
+      ProjectLifeCycleListenersImpl.class,
       ProjectsWsSupport.class,
       ProjectsWs.class,
       CreateAction.class,
index 403e6ff8180ba47f88dcb7937415d070b180fafb..e09adfaf1974bac0571852e2078caa7332f062b6 100644 (file)
@@ -20,7 +20,6 @@
 package org.sonar.server.project.ws;
 
 import java.util.List;
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -41,6 +40,8 @@ import org.sonar.server.es.TestProjectIndexers;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.project.Project;
+import org.sonar.server.project.ProjectLifeCycleListeners;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.WsTester;
 
@@ -70,26 +71,18 @@ public class DeleteActionTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
-
-  private WsTester ws;
   private DbClient dbClient = db.getDbClient();
   private DbSession dbSession = db.getSession();
   private WebhookDbTester webhookDbTester = db.webhooks();
   private ComponentDbTester componentDbTester = new ComponentDbTester(db);
   private ComponentCleanerService componentCleanerService = mock(ComponentCleanerService.class);
-
-
-
-
-  @Before
-  public void setUp() {
-    ws = new WsTester(new ProjectsWs(
-      new DeleteAction(
-        componentCleanerService,
-        from(db),
-        dbClient,
-        userSessionRule)));
-  }
+  private ProjectLifeCycleListeners projectLifeCycleListeners = mock(ProjectLifeCycleListeners.class);
+  private WsTester ws = new WsTester(new ProjectsWs(
+    new DeleteAction(
+      componentCleanerService,
+      from(db),
+      dbClient,
+      userSessionRule, projectLifeCycleListeners)));
 
   @Test
   public void organization_administrator_deletes_project_by_id() throws Exception {
@@ -100,6 +93,7 @@ public class DeleteActionTest {
     call(request);
 
     assertThat(verifyDeletedKey()).isEqualTo(project.getDbKey());
+    verify(projectLifeCycleListeners).onProjectDeleted(Project.from(project));
   }
 
   @Test
@@ -110,12 +104,7 @@ public class DeleteActionTest {
     call(newRequest().setParam(PARAM_PROJECT, project.getDbKey()));
 
     assertThat(verifyDeletedKey()).isEqualTo(project.getDbKey());
-  }
-
-  private String verifyDeletedKey() {
-    ArgumentCaptor<ComponentDto> argument = ArgumentCaptor.forClass(ComponentDto.class);
-    verify(componentCleanerService).delete(any(DbSession.class), argument.capture());
-    return argument.getValue().getDbKey();
+    verify(projectLifeCycleListeners).onProjectDeleted(Project.from(project));
   }
 
   @Test
@@ -126,6 +115,7 @@ public class DeleteActionTest {
     call(newRequest().setParam(PARAM_PROJECT_ID, project.uuid()));
 
     assertThat(verifyDeletedKey()).isEqualTo(project.getDbKey());
+    verify(projectLifeCycleListeners).onProjectDeleted(Project.from(project));
   }
 
   @Test
@@ -136,6 +126,7 @@ public class DeleteActionTest {
     call(newRequest().setParam(PARAM_PROJECT, project.getDbKey()));
 
     assertThat(verifyDeletedKey()).isEqualTo(project.getDbKey());
+    verify(projectLifeCycleListeners).onProjectDeleted(Project.from(project));
   }
 
   @Test
@@ -150,7 +141,8 @@ public class DeleteActionTest {
     new WsTester(new ProjectsWs(
       new DeleteAction(
         new ComponentCleanerService(dbClient, new ResourceTypesRule().setAllQualifiers(PROJECT),
-          new TestProjectIndexers()), from(db), dbClient, userSessionRule)))
+          new TestProjectIndexers()),
+        from(db), dbClient, userSessionRule, projectLifeCycleListeners)))
           .newPostRequest(CONTROLLER, ACTION)
           .setParam(PARAM_PROJECT, project.getDbKey())
           .execute();
@@ -173,7 +165,8 @@ public class DeleteActionTest {
     new WsTester(new ProjectsWs(
       new DeleteAction(
         new ComponentCleanerService(dbClient, new ResourceTypesRule().setAllQualifiers(PROJECT),
-          new TestProjectIndexers()), from(db), dbClient, userSessionRule)))
+          new TestProjectIndexers()),
+        from(db), dbClient, userSessionRule, projectLifeCycleListeners)))
           .newPostRequest(CONTROLLER, ACTION)
           .setParam(PARAM_PROJECT, project.getDbKey())
           .execute();
@@ -229,6 +222,12 @@ public class DeleteActionTest {
     call(newRequest().setParam(PARAM_PROJECT_ID, branch.uuid()));
   }
 
+  private String verifyDeletedKey() {
+    ArgumentCaptor<ComponentDto> argument = ArgumentCaptor.forClass(ComponentDto.class);
+    verify(componentCleanerService).delete(any(DbSession.class), argument.capture());
+    return argument.getValue().getDbKey();
+  }
+
   private WsTester.TestRequest newRequest() {
     return ws.newPostRequest(CONTROLLER, ACTION);
   }
index 163887f398f9cd1cf1c93f3ef7eb9554defad5af..495366c2f14b5e3afdd64c992ceaf58f8f53a485 100644 (file)
@@ -30,6 +30,6 @@ public class ProjectsWsModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new ProjectsWsModule().configure(container);
-    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 13);
+    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 14);
   }
 }