You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BulkDeleteAction.java 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2018 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.server.project.ws;
  21. import java.util.List;
  22. import javax.annotation.CheckForNull;
  23. import javax.annotation.Nullable;
  24. import org.sonar.api.server.ws.Change;
  25. import org.sonar.api.server.ws.Request;
  26. import org.sonar.api.server.ws.Response;
  27. import org.sonar.api.server.ws.WebService;
  28. import org.sonar.api.server.ws.WebService.Param;
  29. import org.sonar.core.util.stream.MoreCollectors;
  30. import org.sonar.db.DbClient;
  31. import org.sonar.db.DbSession;
  32. import org.sonar.db.component.ComponentDto;
  33. import org.sonar.db.component.ComponentQuery;
  34. import org.sonar.db.organization.OrganizationDto;
  35. import org.sonar.db.permission.OrganizationPermission;
  36. import org.sonar.server.component.ComponentCleanerService;
  37. import org.sonar.server.project.Project;
  38. import org.sonar.server.project.ProjectLifeCycleListeners;
  39. import org.sonar.server.project.Visibility;
  40. import org.sonar.server.user.UserSession;
  41. import static java.lang.Math.min;
  42. import static org.sonar.api.resources.Qualifiers.APP;
  43. import static org.sonar.api.resources.Qualifiers.PROJECT;
  44. import static org.sonar.api.resources.Qualifiers.VIEW;
  45. import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
  46. import static org.sonar.core.util.Uuids.UUID_EXAMPLE_02;
  47. import static org.sonar.server.project.ws.SearchAction.buildDbQuery;
  48. import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
  49. import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_002;
  50. import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ANALYZED_BEFORE;
  51. import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ON_PROVISIONED_ONLY;
  52. import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ORGANIZATION;
  53. import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECTS;
  54. import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT_IDS;
  55. import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_QUALIFIERS;
  56. import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_VISIBILITY;
  57. public class BulkDeleteAction implements ProjectsWsAction {
  58. private static final String ACTION = "bulk_delete";
  59. private final ComponentCleanerService componentCleanerService;
  60. private final DbClient dbClient;
  61. private final UserSession userSession;
  62. private final ProjectsWsSupport support;
  63. private final ProjectLifeCycleListeners projectLifeCycleListeners;
  64. public BulkDeleteAction(ComponentCleanerService componentCleanerService, DbClient dbClient, UserSession userSession,
  65. ProjectsWsSupport support, ProjectLifeCycleListeners projectLifeCycleListeners) {
  66. this.componentCleanerService = componentCleanerService;
  67. this.dbClient = dbClient;
  68. this.userSession = userSession;
  69. this.support = support;
  70. this.projectLifeCycleListeners = projectLifeCycleListeners;
  71. }
  72. @Override
  73. public void define(WebService.NewController context) {
  74. WebService.NewAction action = context
  75. .createAction(ACTION)
  76. .setPost(true)
  77. .setDescription("Delete one or several projects.<br />" +
  78. "Requires 'Administer System' permission.")
  79. .setSince("5.2")
  80. .setHandler(this)
  81. .setChangelog(new Change("6.7.2", "Only the 1'000 first items in project filters are taken into account"));
  82. support.addOrganizationParam(action);
  83. action
  84. .createParam(PARAM_PROJECTS)
  85. .setDescription("Comma-separated list of project keys")
  86. .setDeprecatedKey("keys", "6.4")
  87. .setExampleValue(String.join(",", KEY_PROJECT_EXAMPLE_001, KEY_PROJECT_EXAMPLE_002));
  88. action
  89. .createParam(PARAM_PROJECT_IDS)
  90. .setDescription("Comma-separated list of project ids. Only the 1'000 first ids are used. Others are silently ignored.")
  91. .setDeprecatedKey("ids", "6.4")
  92. .setDeprecatedSince("6.4")
  93. .setExampleValue(String.join(",", UUID_EXAMPLE_01, UUID_EXAMPLE_02));
  94. action.createParam(Param.TEXT_QUERY)
  95. .setDescription("Limit to: <ul>" +
  96. "<li>component names that contain the supplied string</li>" +
  97. "<li>component keys that contain the supplied string</li>" +
  98. "</ul>")
  99. .setExampleValue("sonar");
  100. action.createParam(PARAM_QUALIFIERS)
  101. .setDescription("Comma-separated list of component qualifiers. Filter the results with the specified qualifiers")
  102. .setPossibleValues(PROJECT, VIEW, APP)
  103. .setDefaultValue(PROJECT);
  104. action.createParam(PARAM_VISIBILITY)
  105. .setDescription("Filter the projects that should be visible to everyone (%s), or only specific user/groups (%s).<br/>" +
  106. "If no visibility is specified, the default project visibility of the organization will be used.",
  107. Visibility.PUBLIC.getLabel(), Visibility.PRIVATE.getLabel())
  108. .setRequired(false)
  109. .setInternal(true)
  110. .setSince("6.4")
  111. .setPossibleValues(Visibility.getLabels());
  112. action.createParam(PARAM_ANALYZED_BEFORE)
  113. .setDescription("Filter the projects for which last analysis is older than the given date (exclusive).<br> " +
  114. "Either a date (server timezone) or datetime can be provided.")
  115. .setSince("6.6")
  116. .setExampleValue("2017-10-19 or 2017-10-19T13:00:00+0200");
  117. action.createParam(PARAM_ON_PROVISIONED_ONLY)
  118. .setDescription("Filter the projects that are provisioned")
  119. .setBooleanPossibleValues()
  120. .setDefaultValue("false")
  121. .setSince("6.6");
  122. }
  123. @Override
  124. public void handle(Request request, Response response) throws Exception {
  125. SearchRequest searchRequest = toSearchWsRequest(request);
  126. userSession.checkLoggedIn();
  127. try (DbSession dbSession = dbClient.openSession(false)) {
  128. OrganizationDto organization = support.getOrganization(dbSession, searchRequest.getOrganization());
  129. userSession.checkPermission(OrganizationPermission.ADMINISTER, organization);
  130. ComponentQuery query = buildDbQuery(searchRequest);
  131. List<ComponentDto> componentDtos = dbClient.componentDao().selectByQuery(dbSession, organization.getUuid(), query, 0, Integer.MAX_VALUE);
  132. try {
  133. componentDtos.forEach(p -> componentCleanerService.delete(dbSession, p));
  134. } finally {
  135. projectLifeCycleListeners.onProjectsDeleted(componentDtos.stream().map(Project::from).collect(MoreCollectors.toSet(componentDtos.size())));
  136. }
  137. }
  138. response.noContent();
  139. }
  140. private static SearchRequest toSearchWsRequest(Request request) {
  141. return SearchRequest.builder()
  142. .setOrganization(request.param(PARAM_ORGANIZATION))
  143. .setQualifiers(request.mandatoryParamAsStrings(PARAM_QUALIFIERS))
  144. .setQuery(request.param(Param.TEXT_QUERY))
  145. .setVisibility(request.param(PARAM_VISIBILITY))
  146. .setAnalyzedBefore(request.param(PARAM_ANALYZED_BEFORE))
  147. .setOnProvisionedOnly(request.mandatoryParamAsBoolean(PARAM_ON_PROVISIONED_ONLY))
  148. .setProjects(restrictTo1000Values(request.paramAsStrings(PARAM_PROJECTS)))
  149. .setProjectIds(restrictTo1000Values(request.paramAsStrings(PARAM_PROJECT_IDS)))
  150. .build();
  151. }
  152. @CheckForNull
  153. private static List<String> restrictTo1000Values(@Nullable List<String> values) {
  154. if (values == null) {
  155. return null;
  156. }
  157. return values.subList(0, min(values.size(), 1_000));
  158. }
  159. }