From: Sébastien Lesaint Date: Tue, 27 Mar 2018 10:30:12 +0000 (+0200) Subject: GOV-331 trigger views refresh on api/projects/delete X-Git-Tag: 7.5~1391 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=7e92934e0f200f6cfd075cdaad024b79da62855f;p=sonarqube.git GOV-331 trigger views refresh on api/projects/delete --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/Project.java b/server/sonar-server/src/main/java/org/sonar/server/project/Project.java index dd368746f45..af220370e34 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/project/Project.java +++ b/server/sonar-server/src/main/java/org/sonar/server/project/Project.java @@ -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 index 00000000000..5a53ef9062d --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListener.java @@ -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 index 00000000000..0198ae28a3f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListeners.java @@ -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 index 00000000000..03cfbc958e6 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ProjectLifeCycleListenersImpl.java @@ -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 safelyCallListener(Consumer task) { + return listener -> { + try { + task.accept(listener); + } catch (Error | Exception e) { + LOG.error("Call to ProjectLifeCycleListener \"{}\" failed", listener.getClass(), e); + } + }; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/DeleteAction.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/DeleteAction.java index 3fd58c17d7f..50b7f34e808 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/project/ws/DeleteAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/DeleteAction.java @@ -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(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java index 44c8a64de9a..a466e01ba45 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java @@ -20,11 +20,14 @@ 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, diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java index 403e6ff8180..e09adfaf197 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java @@ -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 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 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); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java index 163887f398f..495366c2f14 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java @@ -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); } }