import java.util.Objects;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
+import org.sonar.db.component.ComponentDto;
@Immutable
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.
*/
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+ }
+ };
+ }
+}
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;
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
ComponentDto project = componentFinder.getByUuidOrKey(dbSession, uuid, key, PROJECT_ID_AND_PROJECT);
checkPermission(project);
componentCleanerService.delete(dbSession, project);
+ projectLifeCycleListeners.onProjectDeleted(from(project));
}
response.noContent();
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,
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;
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;
@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 {
call(request);
assertThat(verifyDeletedKey()).isEqualTo(project.getDbKey());
+ verify(projectLifeCycleListeners).onProjectDeleted(Project.from(project));
}
@Test
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
call(newRequest().setParam(PARAM_PROJECT_ID, project.uuid()));
assertThat(verifyDeletedKey()).isEqualTo(project.getDbKey());
+ verify(projectLifeCycleListeners).onProjectDeleted(Project.from(project));
}
@Test
call(newRequest().setParam(PARAM_PROJECT, project.getDbKey()));
assertThat(verifyDeletedKey()).isEqualTo(project.getDbKey());
+ verify(projectLifeCycleListeners).onProjectDeleted(Project.from(project));
}
@Test
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();
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();
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);
}
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);
}
}