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 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  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 com.google.common.annotations.VisibleForTesting;
  22. import com.google.common.collect.ImmutableSet;
  23. import com.google.common.collect.Lists;
  24. import java.util.List;
  25. import java.util.function.Function;
  26. import javax.annotation.CheckForNull;
  27. import javax.annotation.Nullable;
  28. import org.sonar.api.measures.CoreMetrics;
  29. import org.sonar.api.resources.Qualifiers;
  30. import org.sonar.api.server.ws.Change;
  31. import org.sonar.api.server.ws.Request;
  32. import org.sonar.api.server.ws.Response;
  33. import org.sonar.api.server.ws.WebService;
  34. import org.sonar.api.server.ws.WebService.Param;
  35. import org.sonar.api.web.UserRole;
  36. import org.sonar.db.DatabaseUtils;
  37. import org.sonar.db.DbClient;
  38. import org.sonar.db.DbSession;
  39. import org.sonar.db.component.ComponentDto;
  40. import org.sonar.db.component.ComponentQuery;
  41. import org.sonar.db.component.ProjectLinkDto;
  42. import org.sonar.db.component.SnapshotDto;
  43. import org.sonar.db.measure.LiveMeasureDto;
  44. import org.sonar.server.user.UserSession;
  45. import org.sonarqube.ws.Projects.SearchMyProjectsWsResponse;
  46. import org.sonarqube.ws.Projects.SearchMyProjectsWsResponse.Link;
  47. import org.sonarqube.ws.Projects.SearchMyProjectsWsResponse.Project;
  48. import static com.google.common.base.Strings.emptyToNull;
  49. import static com.google.common.base.Strings.isNullOrEmpty;
  50. import static java.util.Collections.singletonList;
  51. import static java.util.Objects.requireNonNull;
  52. import static org.sonar.api.utils.Paging.offset;
  53. import static org.sonar.core.util.Protobuf.setNullable;
  54. import static org.sonar.server.project.ws.SearchMyProjectsData.builder;
  55. import static org.sonar.server.ws.WsUtils.writeProtobuf;
  56. public class SearchMyProjectsAction implements ProjectsWsAction {
  57. private static final int MAX_SIZE = 500;
  58. private final DbClient dbClient;
  59. private final UserSession userSession;
  60. public SearchMyProjectsAction(DbClient dbClient, UserSession userSession) {
  61. this.dbClient = dbClient;
  62. this.userSession = userSession;
  63. }
  64. @Override
  65. public void define(WebService.NewController context) {
  66. WebService.NewAction action = context.createAction("search_my_projects")
  67. .setDescription("Return list of projects for which the current user has 'Administer' permission. Maximum 1'000 projects are returned.")
  68. .setResponseExample(getClass().getResource("search_my_projects-example.json"))
  69. .addPagingParams(100, MAX_SIZE)
  70. .setSince("6.0")
  71. .setInternal(true)
  72. .setHandler(this);
  73. action.setChangelog(new Change("6.4", "The 'id' field is deprecated in the response"));
  74. }
  75. @Override
  76. public void handle(Request request, Response response) throws Exception {
  77. SearchMyProjectsWsResponse searchMyProjectsWsResponse = doHandle(toRequest(request));
  78. writeProtobuf(searchMyProjectsWsResponse, request, response);
  79. }
  80. private SearchMyProjectsWsResponse doHandle(SearchMyProjectsRequest request) {
  81. checkAuthenticated();
  82. try (DbSession dbSession = dbClient.openSession(false)) {
  83. SearchMyProjectsData data = load(dbSession, request);
  84. return buildResponse(request, data);
  85. }
  86. }
  87. private static SearchMyProjectsWsResponse buildResponse(SearchMyProjectsRequest request, SearchMyProjectsData data) {
  88. SearchMyProjectsWsResponse.Builder response = SearchMyProjectsWsResponse.newBuilder();
  89. ProjectDtoToWs projectDtoToWs = new ProjectDtoToWs(data);
  90. data.projects().stream()
  91. .map(projectDtoToWs)
  92. .forEach(response::addProjects);
  93. response.getPagingBuilder()
  94. .setPageIndex(request.getPage())
  95. .setPageSize(request.getPageSize())
  96. .setTotal(data.totalNbOfProjects())
  97. .build();
  98. return response.build();
  99. }
  100. private void checkAuthenticated() {
  101. userSession.checkLoggedIn();
  102. }
  103. private static SearchMyProjectsRequest toRequest(Request request) {
  104. return SearchMyProjectsRequest.builder()
  105. .setPage(request.mandatoryParamAsInt(Param.PAGE))
  106. .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE))
  107. .build();
  108. }
  109. private static class ProjectDtoToWs implements Function<ComponentDto, Project> {
  110. private final SearchMyProjectsData data;
  111. private ProjectDtoToWs(SearchMyProjectsData data) {
  112. this.data = data;
  113. }
  114. @Override
  115. public Project apply(ComponentDto dto) {
  116. Project.Builder project = Project.newBuilder();
  117. project
  118. .setId(dto.uuid())
  119. .setKey(dto.getDbKey())
  120. .setName(dto.name());
  121. data.lastAnalysisDateFor(dto.uuid()).ifPresent(project::setLastAnalysisDate);
  122. data.qualityGateStatusFor(dto.uuid()).ifPresent(project::setQualityGate);
  123. setNullable(emptyToNull(dto.description()), 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. 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. @VisibleForTesting
  162. ProjectsResult searchProjects(DbSession dbSession, SearchMyProjectsRequest request) {
  163. int userId = requireNonNull(userSession.getUserId(), "Current user must be authenticated");
  164. List<Long> componentIds = dbClient.roleDao().selectComponentIdsByPermissionAndUserId(dbSession, UserRole.ADMIN, userId);
  165. ComponentQuery dbQuery = ComponentQuery.builder()
  166. .setQualifiers(Qualifiers.PROJECT)
  167. .setComponentIds(ImmutableSet.copyOf(componentIds.subList(0, Math.min(componentIds.size(), DatabaseUtils.PARTITION_SIZE_FOR_ORACLE))))
  168. .build();
  169. return new ProjectsResult(
  170. dbClient.componentDao().selectByQuery(dbSession, dbQuery, offset(request.getPage(), request.getPageSize()), request.getPageSize()),
  171. dbClient.componentDao().countByQuery(dbSession, dbQuery));
  172. }
  173. private static class ProjectsResult {
  174. private final List<ComponentDto> projects;
  175. private final int total;
  176. private ProjectsResult(List<ComponentDto> projects, int total) {
  177. this.projects = projects;
  178. this.total = total;
  179. }
  180. }
  181. private static class SearchMyProjectsRequest {
  182. private final Integer page;
  183. private final Integer pageSize;
  184. private SearchMyProjectsRequest(Builder builder) {
  185. this.page = builder.page;
  186. this.pageSize = builder.pageSize;
  187. }
  188. @CheckForNull
  189. public Integer getPage() {
  190. return page;
  191. }
  192. @CheckForNull
  193. public Integer getPageSize() {
  194. return pageSize;
  195. }
  196. public static Builder builder() {
  197. return new Builder();
  198. }
  199. }
  200. private static class Builder {
  201. private Integer page;
  202. private Integer pageSize;
  203. private Builder() {
  204. // enforce method constructor
  205. }
  206. public Builder setPage(@Nullable Integer page) {
  207. this.page = page;
  208. return this;
  209. }
  210. public Builder setPageSize(@Nullable Integer pageSize) {
  211. this.pageSize = pageSize;
  212. return this;
  213. }
  214. public SearchMyProjectsRequest build() {
  215. return new SearchMyProjectsRequest(this);
  216. }
  217. }
  218. }