import static org.sonar.db.DaoDatabaseUtils.buildLikeValue;
import static org.sonar.db.DatabaseUtils.checkThatNotTooManyConditions;
import static org.sonar.db.DatabaseUtils.executeLargeInputs;
+import static org.sonar.db.DatabaseUtils.executeLargeInputsIntoSet;
import static org.sonar.db.DatabaseUtils.executeLargeUpdates;
import static org.sonar.db.WildcardPosition.BEFORE_AND_AFTER;
import static org.sonar.db.component.ComponentDto.generateBranchKey;
/**
* Used by Governance
*/
- public Set<String> selectViewKeysWithEnabledCopyOfProject(DbSession session, String projectUuid) {
- return mapper(session).selectViewKeysWithEnabledCopyOfProject(projectUuid);
+ public Set<String> selectViewKeysWithEnabledCopyOfProject(DbSession session, Set<String> projectUuids) {
+ return executeLargeInputsIntoSet(projectUuids,
+ partition -> mapper(session).selectViewKeysWithEnabledCopyOfProject(partition),
+ i -> i);
}
public List<String> selectProjectsFromView(DbSession session, String viewUuid, String projectViewUuid) {
*/
List<KeyWithUuidDto> selectUuidsByKeyFromProjectKey(@Param("projectKey") String projectKey);
- Set<String> selectViewKeysWithEnabledCopyOfProject(@Param("projectUuid") String projectUuid);
+ Set<String> selectViewKeysWithEnabledCopyOfProject(@Param("projectUuids") Collection<String> projectUuids);
/**
* Return technical projects from a view or a sub-view
leaf.qualifier = 'TRK'
and leaf.scope = 'FIL'
and leaf.enabled = ${_true}
- and leaf.copy_component_uuid = #{projectUuid,jdbcType=VARCHAR}
+ and leaf.copy_component_uuid in
+ <foreach collection="projectUuids" open="(" close=")" item="uuid" separator=",">#{uuid,jdbcType=VARCHAR}</foreach>
where
p.enabled = ${_true}
and p.uuid = leaf.project_uuid
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
+import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.assertj.core.api.ListAssert;
import org.junit.Rule;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.source.FileSourceDto;
+import static com.google.common.collect.ImmutableSet.of;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newHashSet;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
+import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
+import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
}
@Test
- public void selectViewKeysWithEnabledCopyOfProject_returns_empty_when_there_is_no_view() {
- String projectUuid = randomAlphabetic(5);
+ public void selectViewKeysWithEnabledCopyOfProject_returns_empty_when_set_is_empty() {
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, emptySet()))
+ .isEmpty();
+ }
+
+ @Test
+ @UseDataProvider("oneOrMoreProjects")
+ public void selectViewKeysWithEnabledCopyOfProject_returns_empty_when_there_is_no_view(int projectCount) {
+ Set<String> projectUuids = IntStream.range(0, projectCount)
+ .mapToObj(i -> randomAlphabetic(5))
+ .collect(toSet());
+
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, projectUuids)).isEmpty();
+ }
- assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, projectUuid)).isEmpty();
+ @DataProvider
+ public static Object[][] oneOrMoreProjects() {
+ return new Object[][] {
+ {1},
+ {1 + new Random().nextInt(10)}
+ };
}
@Test
ComponentDto view = insertView(organization, rootViewQualifier);
insertProjectCopy(view, project);
- Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, project.uuid());
+ Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, singleton(project.uuid()));
assertThat(keys).containsOnly(view.getDbKey());
+
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project.uuid())))
+ .isEqualTo(keys);
+ }
+
+ @Test
+ @UseDataProvider("portfolioOrApplicationRootViewQualifier")
+ public void selectViewKeysWithEnabledCopyOfProject_returns_root_views_with_direct_copy_of_projects(String rootViewQualifier) {
+ OrganizationDto organization = db.organizations().insert();
+ ComponentDto project1 = insertProject(organization);
+ ComponentDto project2 = insertProject(organization);
+ ComponentDto view = insertView(organization, rootViewQualifier);
+ insertProjectCopy(view, project1);
+ insertProjectCopy(view, project2);
+ ComponentDto view2 = insertView(organization, rootViewQualifier);
+ ComponentDto project3 = insertProject(organization);
+ insertProjectCopy(view2, project3);
+
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, singleton(project1.uuid())))
+ .containsOnly(view.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project1.uuid())))
+ .containsOnly(view.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, singleton(project2.uuid())))
+ .containsOnly(view.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project2.uuid())))
+ .containsOnly(view.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, singleton(project3.uuid())))
+ .containsOnly(view2.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project3.uuid())))
+ .containsOnly(view2.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, of(project2.uuid(), project1.uuid())))
+ .containsOnly(view.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project2.uuid(), project1.uuid())))
+ .containsOnly(view.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, of(project1.uuid(), project3.uuid())))
+ .containsOnly(view.getDbKey(), view2.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project1.uuid(), project3.uuid())))
+ .containsOnly(view.getDbKey(), view2.getDbKey());
}
@Test
ComponentDto view2 = insertView(organization, rootViewQualifier);
insertProjectCopy(view2, project2);
- Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, project2.uuid());
+ Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, singleton(project2.uuid()));
assertThat(keys).containsOnly(view2.getDbKey());
+
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project2.uuid())))
+ .isEqualTo(keys);
}
@Test
ComponentDto view2 = insertView(organization, rootViewQualifier);
insertProjectCopy(view2, project, t -> t.setEnabled(false));
- Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, project.uuid());
+ Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, singleton(project.uuid()));
assertThat(keys).containsOnly(view1.getDbKey());
+
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project.uuid())))
+ .isEqualTo(keys);
}
@Test
ComponentDto view2 = insertView(organization, rootViewQualifier);
insertProjectCopy(view2, project);
- Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, project.uuid());
+ Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, singleton(project.uuid()));
assertThat(keys).containsOnly(view2.getDbKey());
+
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project.uuid())))
+ .isEqualTo(keys);
}
@Test
ComponentDto lowestSubview = insertSubviews(view);
insertProjectCopy(lowestSubview, project);
- Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, project.uuid());
+ Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, singleton(project.uuid()));
assertThat(keys).containsOnly(view.getDbKey());
+
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project.uuid())))
+ .isEqualTo(keys);
+ }
+
+ @Test
+ @UseDataProvider("portfolioOrApplicationRootViewQualifier")
+ public void selectViewKeysWithEnabledCopyOfProject_returns_root_views_with_indirect_copy_of_projects(String rootViewQualifier) {
+ OrganizationDto organization = db.organizations().insert();
+ ComponentDto project1 = insertProject(organization);
+ ComponentDto project2 = insertProject(organization);
+ ComponentDto view1 = insertView(organization, rootViewQualifier);
+ ComponentDto lowestSubview1 = insertSubviews(view1);
+ insertProjectCopy(lowestSubview1, project1);
+ insertProjectCopy(lowestSubview1, project2);
+ ComponentDto view2 = insertView(organization, rootViewQualifier);
+ ComponentDto lowestSubview2 = insertSubviews(view2);
+ ComponentDto project3 = insertProject(organization);
+ insertProjectCopy(lowestSubview2, project3);
+
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, singleton(project1.uuid())))
+ .containsOnly(view1.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project1.uuid())))
+ .containsOnly(view1.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, singleton(project2.uuid())))
+ .containsOnly(view1.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project2.uuid())))
+ .containsOnly(view1.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, singleton(project3.uuid())))
+ .containsOnly(view2.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project3.uuid())))
+ .containsOnly(view2.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, of(project2.uuid(), project1.uuid())))
+ .containsOnly(view1.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project2.uuid(), project1.uuid())))
+ .containsOnly(view1.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, of(project1.uuid(), project3.uuid())))
+ .containsOnly(view1.getDbKey(), view2.getDbKey());
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project1.uuid(), project3.uuid())))
+ .containsOnly(view1.getDbKey(), view2.getDbKey());
}
@Test
ComponentDto lowestSubview2 = insertSubviews(view2);
insertProjectCopy(lowestSubview2, project2);
- Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, project2.uuid());
+ Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, singleton(project2.uuid()));
assertThat(keys).containsOnly(view2.getDbKey());
+
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project2.uuid())))
+ .isEqualTo(keys);
}
@Test
ComponentDto lowestSubview2 = insertSubviews(view2);
insertProjectCopy(lowestSubview2, project, t -> t.setEnabled(false));
- Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, project.uuid());
+ Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, singleton(project.uuid()));
assertThat(keys).containsOnly(view1.getDbKey());
+
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project.uuid())))
+ .isEqualTo(keys);
}
@Test
ComponentDto lowestSubview2 = insertSubviews(view2);
insertProjectCopy(lowestSubview2, project);
- Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, project.uuid());
+ Set<String> keys = underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, singleton(project.uuid()));
assertThat(keys).containsOnly(view2.getDbKey());
+
+ assertThat(underTest.selectViewKeysWithEnabledCopyOfProject(dbSession, shuffleWithInexistingUuids(project.uuid())))
+ .isEqualTo(keys);
}
@DataProvider
@Test
public void countByQuery_throws_IAE_if_too_many_component_ids() {
- Set<Long> ids = LongStream.range(0L, 1_010L).boxed().collect(Collectors.toSet());
+ Set<Long> ids = LongStream.range(0L, 1_010L).boxed().collect(toSet());
ComponentQuery.Builder query = ComponentQuery.builder()
.setQualifiers(Qualifiers.PROJECT)
.setComponentIds(ids);
@Test
public void countByQuery_throws_IAE_if_too_many_component_keys() {
- Set<String> keys = IntStream.range(0, 1_010).mapToObj(String::valueOf).collect(Collectors.toSet());
+ Set<String> keys = IntStream.range(0, 1_010).mapToObj(String::valueOf).collect(toSet());
ComponentQuery.Builder query = ComponentQuery.builder()
.setQualifiers(Qualifiers.PROJECT)
.setComponentKeys(keys);
@Test
public void countByQuery_throws_IAE_if_too_many_component_uuids() {
- Set<String> uuids = IntStream.range(0, 1_010).mapToObj(String::valueOf).collect(Collectors.toSet());
+ Set<String> uuids = IntStream.range(0, 1_010).mapToObj(String::valueOf).collect(toSet());
ComponentQuery.Builder query = ComponentQuery.builder()
.setQualifiers(Qualifiers.PROJECT)
.setComponentUuids(uuids);
@Test
public void selectByQuery_throws_IAE_if_too_many_component_ids() {
- Set<Long> ids = LongStream.range(0L, 1_010L).boxed().collect(Collectors.toSet());
+ Set<Long> ids = LongStream.range(0L, 1_010L).boxed().collect(toSet());
ComponentQuery.Builder query = ComponentQuery.builder()
.setQualifiers(Qualifiers.PROJECT)
.setComponentIds(ids);
@Test
public void selectByQuery_throws_IAE_if_too_many_component_keys() {
- Set<String> keys = IntStream.range(0, 1_010).mapToObj(String::valueOf).collect(Collectors.toSet());
+ Set<String> keys = IntStream.range(0, 1_010).mapToObj(String::valueOf).collect(toSet());
ComponentQuery.Builder query = ComponentQuery.builder()
.setQualifiers(Qualifiers.PROJECT)
.setComponentKeys(keys);
@Test
public void selectByQuery_throws_IAE_if_too_many_component_uuids() {
- Set<String> uuids = IntStream.range(0, 1_010).mapToObj(String::valueOf).collect(Collectors.toSet());
+ Set<String> uuids = IntStream.range(0, 1_010).mapToObj(String::valueOf).collect(toSet());
ComponentQuery.Builder query = ComponentQuery.builder()
.setQualifiers(Qualifiers.PROJECT)
.setComponentUuids(uuids);
return underTest.selectByUuid(db.getSession(), uuid).get().isPrivate();
}
+ private static Set<String> shuffleWithInexistingUuids(String... uuids) {
+ return Stream.concat(
+ IntStream.range(0, 1 + new Random().nextInt(5)).mapToObj(i -> randomAlphabetic(9)),
+ Arrays.stream(uuids))
+ .collect(toSet());
+ }
+
}
*/
package org.sonar.server.project;
+import java.util.Set;
import org.sonar.api.server.ServerSide;
@ServerSide
public interface ProjectLifeCycleListener {
/**
- * This method is called after the specified project has been deleted.
+ * This method is called after the specified projects have been deleted.
*/
- void onProjectDeleted(Project project);
+ void onProjectsDeleted(Set<Project> projects);
}
*/
package org.sonar.server.project;
+import java.util.Set;
+
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
+ * This method is called after the specified projects have been deleted and will call method
+ * {@link ProjectLifeCycleListener#onProjectsDeleted(Set) onProjectsDeleted(Set)} of all known
* {@link ProjectLifeCycleListener} implementations.
- *
- * This method will ensure all {@link ProjectLifeCycleListener} implementations are called, even if one or more of
+ * <p>
+ * This method ensures all {@link ProjectLifeCycleListener} implementations are called, even if one or more of
* them fail with an exception.
*/
- void onProjectDeleted(Project project);
+ void onProjectsDeleted(Set<Project> projects);
}
import com.google.common.base.Preconditions;
import java.util.Arrays;
+import java.util.Set;
import java.util.function.Consumer;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
this.listeners = new ProjectLifeCycleListener[0];
}
+ /**
+ * Used by Pico when there is at least one ProjectLifeCycleListener implementation in container.
+ */
public ProjectLifeCycleListenersImpl(ProjectLifeCycleListener[] listeners) {
this.listeners = listeners;
}
@Override
- public void onProjectDeleted(Project project) {
- Preconditions.checkNotNull(project, "project can't be null");
+ public void onProjectsDeleted(Set<Project> projects) {
+ Preconditions.checkNotNull(projects, "projects can't be null");
+ if (projects.isEmpty()) {
+ return;
+ }
Arrays.stream(listeners)
- .forEach(safelyCallListener(listener -> listener.onProjectDeleted(project)));
+ .forEach(safelyCallListener(listener -> listener.onProjectsDeleted(projects)));
}
private static Consumer<ProjectLifeCycleListener> safelyCallListener(Consumer<ProjectLifeCycleListener> task) {
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.Param;
+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.ComponentQuery;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.permission.OrganizationPermission;
import org.sonar.server.component.ComponentCleanerService;
+import org.sonar.server.project.Project;
+import org.sonar.server.project.ProjectLifeCycleListeners;
import org.sonar.server.project.Visibility;
import org.sonar.server.user.UserSession;
private final DbClient dbClient;
private final UserSession userSession;
private final ProjectsWsSupport support;
+ private final ProjectLifeCycleListeners projectLifeCycleListeners;
public BulkDeleteAction(ComponentCleanerService componentCleanerService, DbClient dbClient, UserSession userSession,
- ProjectsWsSupport support) {
+ ProjectsWsSupport support, ProjectLifeCycleListeners projectLifeCycleListeners) {
this.componentCleanerService = componentCleanerService;
this.dbClient = dbClient;
this.userSession = userSession;
this.support = support;
+ this.projectLifeCycleListeners = projectLifeCycleListeners;
}
@Override
userSession.checkPermission(OrganizationPermission.ADMINISTER, organization);
ComponentQuery query = buildDbQuery(searchRequest);
- dbClient.componentDao().selectByQuery(dbSession, organization.getUuid(), query, 0, Integer.MAX_VALUE)
- .forEach(p -> componentCleanerService.delete(dbSession, p));
+ List<ComponentDto> componentDtos = dbClient.componentDao().selectByQuery(dbSession, organization.getUuid(), query, 0, Integer.MAX_VALUE);
+ try {
+ componentDtos.forEach(p -> componentCleanerService.delete(dbSession, p));
+ } finally {
+ projectLifeCycleListeners.onProjectsDeleted(componentDtos.stream().map(Project::from).collect(MoreCollectors.toSet(componentDtos.size())));
+ }
}
response.noContent();
}
import org.sonar.server.project.ProjectLifeCycleListeners;
import org.sonar.server.user.UserSession;
+import static java.util.Collections.singleton;
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;
ComponentDto project = componentFinder.getByUuidOrKey(dbSession, uuid, key, PROJECT_ID_AND_PROJECT);
checkPermission(project);
componentCleanerService.delete(dbSession, project);
- projectLifeCycleListeners.onProjectDeleted(from(project));
+ projectLifeCycleListeners.onProjectsDeleted(singleton(from(project)));
}
response.noContent();
--- /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.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Collections;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+import org.sonar.core.util.stream.MoreCollectors;
+
+import static java.util.Collections.singleton;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+@RunWith(DataProviderRunner.class)
+public class ProjectLifeCycleListenersImplTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private ProjectLifeCycleListener listener1 = mock(ProjectLifeCycleListener.class);
+ private ProjectLifeCycleListener listener2 = mock(ProjectLifeCycleListener.class);
+ private ProjectLifeCycleListener listener3 = mock(ProjectLifeCycleListener.class);
+ private ProjectLifeCycleListenersImpl underTestNoListeners = new ProjectLifeCycleListenersImpl();
+ private ProjectLifeCycleListenersImpl underTestWithListeners = new ProjectLifeCycleListenersImpl(
+ new ProjectLifeCycleListener[] {listener1, listener2, listener3});
+
+ @Test
+ public void onProjectsDeleted_throws_NPE_if_set_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("projects can't be null");
+
+ underTestWithListeners.onProjectsDeleted(null);
+ }
+
+ @Test
+ public void onProjectsDeleted_throws_NPE_if_set_is_null_even_if_no_listeners() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("projects can't be null");
+
+ underTestNoListeners.onProjectsDeleted(null);
+ }
+
+ @Test
+ public void onProjectsDeleted_has_no_effect_if_set_is_empty() {
+ underTestNoListeners.onProjectsDeleted(Collections.emptySet());
+
+ underTestWithListeners.onProjectsDeleted(Collections.emptySet());
+ verifyZeroInteractions(listener1, listener2, listener3);
+ }
+
+ @Test
+ @UseDataProvider("oneOrManyProjects")
+ public void onProjectsDeleted_does_not_fail_if_there_is_no_listener(Set<Project> projects) {
+ underTestNoListeners.onProjectsDeleted(projects);
+ }
+
+ @Test
+ @UseDataProvider("oneOrManyProjects")
+ public void onProjectsDeleted_calls_all_listeners_in_order_of_addition_to_constructor(Set<Project> projects) {
+ InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3);
+
+ underTestWithListeners.onProjectsDeleted(projects);
+
+ inOrder.verify(listener1).onProjectsDeleted(same(projects));
+ inOrder.verify(listener2).onProjectsDeleted(same(projects));
+ inOrder.verify(listener3).onProjectsDeleted(same(projects));
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ @UseDataProvider("oneOrManyProjects")
+ public void onProjectsDeleted_calls_all_listeners_even_if_one_throws_an_Exception(Set<Project> projects) {
+ InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3);
+ doThrow(new RuntimeException("Faking listener2 throwing an exception"))
+ .when(listener2)
+ .onProjectsDeleted(any());
+
+ underTestWithListeners.onProjectsDeleted(projects);
+
+ inOrder.verify(listener1).onProjectsDeleted(same(projects));
+ inOrder.verify(listener2).onProjectsDeleted(same(projects));
+ inOrder.verify(listener3).onProjectsDeleted(same(projects));
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ @UseDataProvider("oneOrManyProjects")
+ public void onProjectsDeleted_calls_all_listeners_even_if_one_throws_an_Error(Set<Project> projects) {
+ InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3);
+ doThrow(new Error("Faking listener2 throwing an Error"))
+ .when(listener2)
+ .onProjectsDeleted(any());
+
+ underTestWithListeners.onProjectsDeleted(projects);
+
+ inOrder.verify(listener1).onProjectsDeleted(same(projects));
+ inOrder.verify(listener2).onProjectsDeleted(same(projects));
+ inOrder.verify(listener3).onProjectsDeleted(same(projects));
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @DataProvider
+ public static Object[][] oneOrManyProjects() {
+ return new Object[][] {
+ {singleton(newUniqueProject())},
+ {IntStream.range(0, 1 + new Random().nextInt(10)).mapToObj(i -> newUniqueProject()).collect(MoreCollectors.toSet())}
+ };
+ }
+
+ private static int counter = 3_989;
+
+ private static Project newUniqueProject() {
+ int base = counter++;
+ return new Project(base + "_uuid", base + "_key", base + "_name");
+ }
+}
package org.sonar.server.project.ws;
import java.net.HttpURLConnection;
+import java.util.Arrays;
import java.util.Date;
import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.commons.lang.StringUtils;
import org.junit.Before;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.organization.BillingValidationsProxy;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
+import org.sonar.server.project.Project;
+import org.sonar.server.project.ProjectLifeCycleListeners;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
private ComponentCleanerService componentCleanerService = mock(ComponentCleanerService.class);
private DbClient dbClient = db.getDbClient();
private ProjectsWsSupport support = new ProjectsWsSupport(dbClient, TestDefaultOrganizationProvider.from(db), mock(BillingValidationsProxy.class));
+ private ProjectLifeCycleListeners projectLifeCycleListeners = mock(ProjectLifeCycleListeners.class);
- private BulkDeleteAction underTest = new BulkDeleteAction(componentCleanerService, dbClient, userSession, support);
+ private BulkDeleteAction underTest = new BulkDeleteAction(componentCleanerService, dbClient, userSession, support, projectLifeCycleListeners);
private WsActionTester ws = new WsActionTester(underTest);
private OrganizationDto org1;
assertThat(result.getStatus()).isEqualTo(HttpURLConnection.HTTP_NO_CONTENT);
assertThat(result.getInput()).isEmpty();
verifyDeleted(toDeleteInOrg2);
+ verifyListenersOnProjectsDeleted(toDeleteInOrg2);
}
@Test
.execute();
verifyDeleted(toDeleteInOrg1, toDeleteInOrg2);
+ verifyListenersOnProjectsDeleted(toDeleteInOrg1, toDeleteInOrg2);
}
@Test
.execute();
verifyDeleted(toDelete1, toDelete2);
+ verifyListenersOnProjectsDeleted(toDelete1, toDelete2);
}
@Test
.execute();
verifyDeleted(oldProject);
+ verifyListenersOnProjectsDeleted(oldProject);
}
@Test
ws.newRequest().setParam(PARAM_ON_PROVISIONED_ONLY, "true").execute();
verifyDeleted(provisionedProject);
+ verifyListenersOnProjectsDeleted(provisionedProject);
}
@Test
ws.newRequest().execute();
verifyDeleted(projects);
+ verifyListenersOnProjectsDeleted(projects);
}
@Test
ws.newRequest().setParam(PARAM_QUALIFIERS, String.join(",", Qualifiers.PROJECT, Qualifiers.VIEW)).execute();
verifyDeleted(project, view);
+ verifyListenersOnProjectsDeleted(project, view);
}
@Test
ws.newRequest().setParam(Param.TEXT_QUERY, "JeCt-_%-k").execute();
verifyDeleted(matchKeyProject, matchUppercaseKeyProject);
+ verifyListenersOnProjectsDeleted(matchKeyProject, matchUppercaseKeyProject);
}
@Test
expectedException.expect(ForbiddenException.class);
expectedException.expectMessage("Insufficient privileges");
- ws.newRequest()
- .setParam("projects", project.getDbKey())
- .execute();
-
- verifyNoDeletions();
+ try {
+ ws.newRequest()
+ .setParam("projects", project.getDbKey())
+ .execute();
+ } finally {
+ verifyNoDeletions();
+ verifyZeroInteractions(projectLifeCycleListeners);
+ }
}
/**
.execute();
verify(componentCleanerService, times(1_000)).delete(any(DbSession.class), any(ComponentDto.class));
+ ArgumentCaptor<Set<Project>> projectsCaptor = ArgumentCaptor.forClass(Set.class);
+ verify(projectLifeCycleListeners).onProjectsDeleted(projectsCaptor.capture());
+ assertThat(projectsCaptor.getValue()).hasSize(1_000);
+ }
+
+ @Test
+ public void projectLifeCycleListeners_onProjectsDeleted_called_even_if_delete_fails() {
+ userSession.logIn().addPermission(ADMINISTER, org1);
+ ComponentDto project1 = db.components().insertPrivateProject(org1);
+ ComponentDto project2 = db.components().insertPrivateProject(org1);
+ ComponentDto project3 = db.components().insertPrivateProject(org1);
+ ComponentCleanerService componentCleanerService = mock(ComponentCleanerService.class);
+ RuntimeException expectedException = new RuntimeException("Faking delete failing on 2nd project");
+ doNothing()
+ .doThrow(expectedException)
+ .when(componentCleanerService)
+ .delete(any(), any(ComponentDto.class));
+
+ try {
+ ws.newRequest()
+ .setParam("organization", org1.getKey())
+ .setParam("projects", project1.getDbKey() + "," + project2.getDbKey() + "," + project3.getDbKey())
+ .execute();
+ } catch (RuntimeException e) {
+ assertThat(e).isSameAs(expectedException);
+ verifyListenersOnProjectsDeleted(project1, project2, project3);
+ }
}
@Test
.execute();
verifyDeleted(toDelete);
+ verifyListenersOnProjectsDeleted(toDelete);
}
@Test
.setParam("ids", "whatever-the-uuid").execute();
verifyNoDeletions();
+ verifyZeroInteractions(projectLifeCycleListeners);
}
@Test
.setParam("ids", "whatever-the-uuid").execute();
verifyNoDeletions();
+ verifyZeroInteractions(projectLifeCycleListeners);
}
@Test
.execute();
verifyNoDeletions();
+ verifyZeroInteractions(projectLifeCycleListeners);
}
private void verifyDeleted(ComponentDto... projects) {
private void verifyNoDeletions() {
verifyZeroInteractions(componentCleanerService);
}
+
+ private void verifyListenersOnProjectsDeleted(ComponentDto... components) {
+ verify(projectLifeCycleListeners)
+ .onProjectsDeleted(Arrays.stream(components).map(Project::from).collect(Collectors.toSet()));
+ }
}
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.WsTester;
+import static java.util.Collections.singleton;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
call(request);
assertThat(verifyDeletedKey()).isEqualTo(project.getDbKey());
- verify(projectLifeCycleListeners).onProjectDeleted(Project.from(project));
+ verify(projectLifeCycleListeners).onProjectsDeleted(singleton(Project.from(project)));
}
@Test
call(newRequest().setParam(PARAM_PROJECT, project.getDbKey()));
assertThat(verifyDeletedKey()).isEqualTo(project.getDbKey());
- verify(projectLifeCycleListeners).onProjectDeleted(Project.from(project));
+ verify(projectLifeCycleListeners).onProjectsDeleted(singleton(Project.from(project)));
}
@Test
call(newRequest().setParam(PARAM_PROJECT_ID, project.uuid()));
assertThat(verifyDeletedKey()).isEqualTo(project.getDbKey());
- verify(projectLifeCycleListeners).onProjectDeleted(Project.from(project));
+ verify(projectLifeCycleListeners).onProjectsDeleted(singleton(Project.from(project)));
}
@Test
call(newRequest().setParam(PARAM_PROJECT, project.getDbKey()));
assertThat(verifyDeletedKey()).isEqualTo(project.getDbKey());
- verify(projectLifeCycleListeners).onProjectDeleted(Project.from(project));
+ verify(projectLifeCycleListeners).onProjectsDeleted(singleton(Project.from(project)));
}
@Test