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.

SearchMyProjectsAction.java 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 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 com.google.common.collect.ImmutableSet;
  22. import com.google.common.collect.Lists;
  23. import java.util.List;
  24. import java.util.function.Function;
  25. import javax.annotation.CheckForNull;
  26. import javax.annotation.Nullable;
  27. import org.sonar.api.measures.CoreMetrics;
  28. import org.sonar.api.resources.Qualifiers;
  29. import org.sonar.api.server.ws.Request;
  30. import org.sonar.api.server.ws.Response;
  31. import org.sonar.api.server.ws.WebService;
  32. import org.sonar.api.server.ws.WebService.Param;
  33. import org.sonar.api.web.UserRole;
  34. import org.sonar.db.DatabaseUtils;
  35. import org.sonar.db.DbClient;
  36. import org.sonar.db.DbSession;
  37. import org.sonar.db.component.ComponentDto;
  38. import org.sonar.db.component.ComponentQuery;
  39. import org.sonar.db.component.ProjectLinkDto;
  40. import org.sonar.db.component.SnapshotDto;
  41. import org.sonar.db.measure.LiveMeasureDto;
  42. import org.sonar.server.user.UserSession;
  43. import org.sonarqube.ws.Projects.SearchMyProjectsWsResponse;
  44. import org.sonarqube.ws.Projects.SearchMyProjectsWsResponse.Link;
  45. import org.sonarqube.ws.Projects.SearchMyProjectsWsResponse.Project;
  46. import static com.google.common.base.Strings.emptyToNull;
  47. import static com.google.common.base.Strings.isNullOrEmpty;
  48. import static java.util.Collections.singletonList;
  49. import static java.util.Objects.requireNonNull;
  50. import static java.util.Optional.ofNullable;
  51. import static org.sonar.api.utils.DateUtils.formatDateTime;
  52. import static org.sonar.api.utils.Paging.offset;
  53. import static org.sonar.server.project.ws.SearchMyProjectsData.builder;
  54. import static org.sonar.server.ws.WsUtils.writeProtobuf;
  55. public class SearchMyProjectsAction implements ProjectsWsAction {
  56. private static final int MAX_SIZE = 500;
  57. private final DbClient dbClient;
  58. private final UserSession userSession;
  59. public SearchMyProjectsAction(DbClient dbClient, UserSession userSession) {
  60. this.dbClient = dbClient;
  61. this.userSession = userSession;
  62. }
  63. @Override
  64. public void define(WebService.NewController context) {
  65. context.createAction("search_my_projects")
  66. .setDescription("Return list of projects for which the current user has 'Administer' permission. Maximum 1'000 projects are returned.")
  67. .setResponseExample(getClass().getResource("search_my_projects-example.json"))
  68. .addPagingParams(100, MAX_SIZE)
  69. .setSince("6.0")
  70. .setInternal(true)
  71. .setHandler(this);
  72. }
  73. @Override
  74. public void handle(Request request, Response response) throws Exception {
  75. SearchMyProjectsWsResponse searchMyProjectsWsResponse = doHandle(toRequest(request));
  76. writeProtobuf(searchMyProjectsWsResponse, request, response);
  77. }
  78. private SearchMyProjectsWsResponse doHandle(SearchMyProjectsRequest request) {
  79. checkAuthenticated();
  80. try (DbSession dbSession = dbClient.openSession(false)) {
  81. SearchMyProjectsData data = load(dbSession, request);
  82. return buildResponse(request, data);
  83. }
  84. }
  85. private static SearchMyProjectsWsResponse buildResponse(SearchMyProjectsRequest request, SearchMyProjectsData data) {
  86. SearchMyProjectsWsResponse.Builder response = SearchMyProjectsWsResponse.newBuilder();
  87. ProjectDtoToWs projectDtoToWs = new ProjectDtoToWs(data);
  88. data.projects().stream()
  89. .map(projectDtoToWs)
  90. .forEach(response::addProjects);
  91. response.getPagingBuilder()
  92. .setPageIndex(request.getPage())
  93. .setPageSize(request.getPageSize())
  94. .setTotal(data.totalNbOfProjects())
  95. .build();
  96. return response.build();
  97. }
  98. private void checkAuthenticated() {
  99. userSession.checkLoggedIn();
  100. }
  101. private static SearchMyProjectsRequest toRequest(Request request) {
  102. return SearchMyProjectsRequest.builder()
  103. .setPage(request.mandatoryParamAsInt(Param.PAGE))
  104. .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))
  105. .build();
  106. }
  107. private static class ProjectDtoToWs implements Function<ComponentDto, Project> {
  108. private final SearchMyProjectsData data;
  109. private ProjectDtoToWs(SearchMyProjectsData data) {
  110. this.data = data;
  111. }
  112. @Override
  113. public Project apply(ComponentDto dto) {
  114. Project.Builder project = Project.newBuilder();
  115. project
  116. .setKey(dto.getDbKey())
  117. .setName(dto.name());
  118. data.lastSnapshot(dto.uuid()).ifPresent(s -> {
  119. project.setLastAnalysisDate(formatDateTime(s.getCreatedAt()));
  120. ofNullable(s.getRevision()).ifPresent(project::setRevision);
  121. });
  122. data.qualityGateStatusFor(dto.uuid()).ifPresent(project::setQualityGate);
  123. ofNullable(emptyToNull(dto.description())).ifPresent(project::setDescription);
  124. data.projectLinksFor(dto.uuid()).stream()
  125. .map(ProjectLinkDtoToWs.INSTANCE)
  126. .forEach(project::addLinks);
  127. return project.build();
  128. }
  129. }
  130. private enum ProjectLinkDtoToWs implements Function<ProjectLinkDto, Link> {
  131. INSTANCE;
  132. @Override
  133. public Link apply(ProjectLinkDto dto) {
  134. Link.Builder link = Link.newBuilder();
  135. link.setHref(dto.getHref());
  136. if (!isNullOrEmpty(dto.getName())) {
  137. link.setName(dto.getName());
  138. }
  139. if (!isNullOrEmpty(dto.getType())) {
  140. link.setType(dto.getType());
  141. }
  142. return link.build();
  143. }
  144. }
  145. private SearchMyProjectsData load(DbSession dbSession, SearchMyProjectsRequest request) {
  146. SearchMyProjectsData.Builder data = builder();
  147. ProjectsResult searchResult = searchProjects(dbSession, request);
  148. List<ComponentDto> projects = searchResult.projects;
  149. List<String> projectUuids = Lists.transform(projects, ComponentDto::projectUuid);
  150. List<ProjectLinkDto> projectLinks = dbClient.projectLinkDao().selectByProjectUuids(dbSession, projectUuids);
  151. List<SnapshotDto> snapshots = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(dbSession, projectUuids);
  152. List<LiveMeasureDto> qualityGates = dbClient.liveMeasureDao()
  153. .selectByComponentUuidsAndMetricKeys(dbSession, projectUuids, singletonList(CoreMetrics.ALERT_STATUS_KEY));
  154. data.setProjects(projects)
  155. .setProjectLinks(projectLinks)
  156. .setSnapshots(snapshots)
  157. .setQualityGates(qualityGates)
  158. .setTotalNbOfProjects(searchResult.total);
  159. return data.build();
  160. }
  161. private ProjectsResult searchProjects(DbSession dbSession, SearchMyProjectsRequest request) {
  162. String userUuid = requireNonNull(userSession.getUuid(), "Current user must be authenticated");
  163. List<String> componentUuids = dbClient.roleDao().selectComponentUuidsByPermissionAndUserUuid(dbSession, UserRole.ADMIN, userUuid);
  164. ComponentQuery dbQuery = ComponentQuery.builder()
  165. .setQualifiers(Qualifiers.PROJECT)
  166. .setComponentUuids(ImmutableSet.copyOf(componentUuids.subList(0, Math.min(componentUuids.size(), DatabaseUtils.PARTITION_SIZE_FOR_ORACLE))))
  167. .build();
  168. return new ProjectsResult(
  169. dbClient.componentDao().selectByQuery(dbSession, dbQuery, offset(request.getPage(), request.getPageSize()), request.getPageSize()),
  170. dbClient.componentDao().countByQuery(dbSession, dbQuery));
  171. }
  172. private static class ProjectsResult {
  173. private final List<ComponentDto> projects;
  174. private final int total;
  175. private ProjectsResult(List<ComponentDto> projects, int total) {
  176. this.projects = projects;
  177. this.total = total;
  178. }
  179. }
  180. private static class SearchMyProjectsRequest {
  181. private final Integer page;
  182. private final Integer pageSize;
  183. private SearchMyProjectsRequest(Builder builder) {
  184. this.page = builder.page;
  185. this.pageSize = builder.pageSize;
  186. }
  187. @CheckForNull
  188. public Integer getPage() {
  189. return page;
  190. }
  191. @CheckForNull
  192. public Integer getPageSize() {
  193. return pageSize;
  194. }
  195. public static Builder builder() {
  196. return new Builder();
  197. }
  198. }
  199. private static class Builder {
  200. private Integer page;
  201. private Integer pageSize;
  202. private Builder() {
  203. // enforce method constructor
  204. }
  205. public Builder setPage(@Nullable Integer page) {
  206. this.page = page;
  207. return this;
  208. }
  209. public Builder setPageSize(@Nullable Integer pageSize) {
  210. this.pageSize = pageSize;
  211. return this;
  212. }
  213. public SearchMyProjectsRequest build() {
  214. return new SearchMyProjectsRequest(this);
  215. }
  216. }
  217. }