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.

SearchResponseBuilder.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 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.projectanalysis.ws;
  21. import com.google.common.collect.ListMultimap;
  22. import com.google.gson.Gson;
  23. import com.google.gson.JsonSyntaxException;
  24. import java.util.Collection;
  25. import java.util.Iterator;
  26. import java.util.List;
  27. import java.util.Optional;
  28. import java.util.stream.Collectors;
  29. import javax.annotation.Nullable;
  30. import org.sonar.api.utils.log.Logger;
  31. import org.sonar.api.utils.log.Loggers;
  32. import org.sonar.db.component.SnapshotDto;
  33. import org.sonar.db.event.EventComponentChangeDto;
  34. import org.sonar.db.event.EventDto;
  35. import org.sonarqube.ws.ProjectAnalyses;
  36. import org.sonarqube.ws.ProjectAnalyses.Analysis;
  37. import org.sonarqube.ws.ProjectAnalyses.Event;
  38. import org.sonarqube.ws.ProjectAnalyses.QualityGate;
  39. import org.sonarqube.ws.ProjectAnalyses.SearchResponse;
  40. import static java.lang.String.format;
  41. import static java.util.Optional.ofNullable;
  42. import static java.util.stream.Collectors.toList;
  43. import static org.sonar.api.utils.DateUtils.formatDateTime;
  44. import static org.sonar.core.util.stream.MoreCollectors.index;
  45. import static org.sonar.server.projectanalysis.ws.EventCategory.fromLabel;
  46. class SearchResponseBuilder {
  47. private static final Logger LOGGER = Loggers.get(SearchResponseBuilder.class);
  48. private final Analysis.Builder wsAnalysis;
  49. private final Event.Builder wsEvent;
  50. private final SearchData searchData;
  51. private final QualityGate.Builder wsQualityGate;
  52. private final ProjectAnalyses.DefinitionChange.Builder wsDefinitionChange;
  53. SearchResponseBuilder(SearchData searchData) {
  54. this.wsAnalysis = Analysis.newBuilder();
  55. this.wsEvent = Event.newBuilder();
  56. this.wsQualityGate = QualityGate.newBuilder();
  57. this.wsDefinitionChange = ProjectAnalyses.DefinitionChange.newBuilder();
  58. this.searchData = searchData;
  59. }
  60. SearchResponse build() {
  61. SearchResponse.Builder wsResponse = SearchResponse.newBuilder();
  62. addAnalyses(wsResponse);
  63. addPagination(wsResponse);
  64. return wsResponse.build();
  65. }
  66. private void addAnalyses(SearchResponse.Builder wsResponse) {
  67. searchData.analyses.stream()
  68. .map(this::dbToWsAnalysis)
  69. .map(this::attachEvents)
  70. .forEach(wsResponse::addAnalyses);
  71. }
  72. private Analysis.Builder dbToWsAnalysis(SnapshotDto dbAnalysis) {
  73. return wsAnalysis.clear()
  74. .setKey(dbAnalysis.getUuid())
  75. .setDate(formatDateTime(dbAnalysis.getCreatedAt()));
  76. }
  77. private Analysis.Builder attachEvents(Analysis.Builder analysis) {
  78. searchData.eventsByAnalysis.get(analysis.getKey())
  79. .stream()
  80. .map(this::dbToWsEvent)
  81. .forEach(analysis::addEvents);
  82. return analysis;
  83. }
  84. private Event.Builder dbToWsEvent(EventDto dbEvent) {
  85. wsEvent.clear().setKey(dbEvent.getUuid());
  86. ofNullable(dbEvent.getName()).ifPresent(wsEvent::setName);
  87. ofNullable(dbEvent.getDescription()).ifPresent(wsEvent::setDescription);
  88. ofNullable(dbEvent.getCategory()).ifPresent(cat -> wsEvent.setCategory(fromLabel(cat).name()));
  89. if (dbEvent.getCategory() != null) {
  90. switch (EventCategory.fromLabel(dbEvent.getCategory())) {
  91. case DEFINITION_CHANGE:
  92. addDefinitionChange(dbEvent);
  93. break;
  94. case QUALITY_GATE:
  95. addQualityGateInformation(dbEvent);
  96. break;
  97. case VERSION:
  98. case OTHER:
  99. case QUALITY_PROFILE:
  100. default:
  101. break;
  102. }
  103. }
  104. return wsEvent;
  105. }
  106. private void addQualityGateInformation(EventDto event) {
  107. wsQualityGate.clear();
  108. List<EventComponentChangeDto> eventComponentChangeDtos = searchData.componentChangesByEventUuid.get(event.getUuid());
  109. if (eventComponentChangeDtos.isEmpty()) {
  110. return;
  111. }
  112. if (event.getData() != null) {
  113. try {
  114. Gson gson = new Gson();
  115. Data data = gson.fromJson(event.getData(), Data.class);
  116. wsQualityGate.setStillFailing(data.isStillFailing());
  117. wsQualityGate.setStatus(data.getStatus());
  118. } catch (JsonSyntaxException ex) {
  119. LOGGER.error("Unable to retrieve data from event uuid=" + event.getUuid(), ex);
  120. return;
  121. }
  122. }
  123. wsQualityGate.addAllFailing(eventComponentChangeDtos.stream()
  124. .map(SearchResponseBuilder::toFailing)
  125. .collect(toList()));
  126. wsEvent.setQualityGate(wsQualityGate.build());
  127. }
  128. private void addDefinitionChange(EventDto event) {
  129. wsDefinitionChange.clear();
  130. List<EventComponentChangeDto> eventComponentChangeDtos = searchData.componentChangesByEventUuid.get(event.getUuid());
  131. if (eventComponentChangeDtos.isEmpty()) {
  132. return;
  133. }
  134. ListMultimap<String, EventComponentChangeDto> componentChangeByKey = eventComponentChangeDtos.stream()
  135. .collect(index(EventComponentChangeDto::getComponentKey));
  136. try {
  137. wsDefinitionChange.addAllProjects(
  138. componentChangeByKey.asMap().values().stream()
  139. .map(SearchResponseBuilder::addChange)
  140. .map(Project::toProject)
  141. .collect(toList())
  142. );
  143. wsEvent.setDefinitionChange(wsDefinitionChange.build());
  144. } catch (IllegalStateException e) {
  145. LOGGER.error(e.getMessage(), e);
  146. }
  147. }
  148. private static Project addChange(Collection<EventComponentChangeDto> changes) {
  149. if (changes.size() == 1) {
  150. return addSingleChange(changes.iterator().next());
  151. } else {
  152. return addBranchChange(changes);
  153. }
  154. }
  155. private static Project addSingleChange(EventComponentChangeDto componentChange) {
  156. Project project = new Project()
  157. .setKey(componentChange.getComponentKey())
  158. .setName(componentChange.getComponentName())
  159. .setBranch(componentChange.getComponentBranchKey());
  160. switch (componentChange.getCategory()) {
  161. case ADDED:
  162. project.setChangeType("ADDED");
  163. break;
  164. case REMOVED:
  165. project.setChangeType("REMOVED");
  166. break;
  167. default:
  168. throw new IllegalStateException(format("Unknown change %s for eventComponentChange uuid: %s", componentChange.getCategory(), componentChange.getUuid()));
  169. }
  170. return project;
  171. }
  172. private static Project addBranchChange(Collection<EventComponentChangeDto> changes) {
  173. if (changes.size() != 2) {
  174. throw new IllegalStateException(format("Too many changes on same project (%d) for eventComponentChange uuids : %s",
  175. changes.size(),
  176. changes.stream().map(EventComponentChangeDto::getUuid).collect(Collectors.joining(","))));
  177. }
  178. Optional<EventComponentChangeDto> addedChange = changes.stream().filter(c -> c.getCategory().equals(EventComponentChangeDto.ChangeCategory.ADDED)).findFirst();
  179. Optional<EventComponentChangeDto> removedChange = changes.stream().filter(c -> c.getCategory().equals(EventComponentChangeDto.ChangeCategory.REMOVED)).findFirst();
  180. if (!addedChange.isPresent() || !removedChange.isPresent() || addedChange.equals(removedChange)) {
  181. Iterator<EventComponentChangeDto> iterator = changes.iterator();
  182. // We are missing two different ADDED and REMOVED changes
  183. EventComponentChangeDto firstChange = iterator.next();
  184. EventComponentChangeDto secondChange = iterator.next();
  185. throw new IllegalStateException(format("Incorrect changes : [uuid=%s change=%s, branch=%s] and [uuid=%s, change=%s, branch=%s]",
  186. firstChange.getUuid(), firstChange.getCategory().name(), firstChange.getComponentBranchKey(),
  187. secondChange.getUuid(), secondChange.getCategory().name(), secondChange.getComponentBranchKey()));
  188. }
  189. return new Project()
  190. .setName(addedChange.get().getComponentName())
  191. .setKey(addedChange.get().getComponentKey())
  192. .setChangeType("BRANCH_CHANGED")
  193. .setNewBranch(addedChange.get().getComponentBranchKey())
  194. .setOldBranch(removedChange.get().getComponentBranchKey());
  195. }
  196. private void addPagination(SearchResponse.Builder wsResponse) {
  197. wsResponse.getPagingBuilder()
  198. .setPageIndex(searchData.paging.pageIndex())
  199. .setPageSize(searchData.paging.pageSize())
  200. .setTotal(searchData.paging.total())
  201. .build();
  202. }
  203. private static ProjectAnalyses.Failing toFailing(EventComponentChangeDto change) {
  204. ProjectAnalyses.Failing.Builder builder = ProjectAnalyses.Failing.newBuilder()
  205. .setKey(change.getComponentKey())
  206. .setName(change.getComponentName());
  207. if (change.getComponentBranchKey() != null) {
  208. builder.setBranch(change.getComponentBranchKey());
  209. }
  210. return builder.build();
  211. }
  212. private static class Data {
  213. private boolean stillFailing;
  214. private String status;
  215. public Data() {
  216. // Empty constructor because it's used by GSon
  217. }
  218. boolean isStillFailing() {
  219. return stillFailing;
  220. }
  221. public Data setStillFailing(boolean stillFailing) {
  222. this.stillFailing = stillFailing;
  223. return this;
  224. }
  225. String getStatus() {
  226. return status;
  227. }
  228. public Data setStatus(String status) {
  229. this.status = status;
  230. return this;
  231. }
  232. }
  233. private static class Project {
  234. private String key;
  235. private String name;
  236. private String changeType;
  237. private String branch;
  238. private String oldBranch;
  239. private String newBranch;
  240. public Project setKey(String key) {
  241. this.key = key;
  242. return this;
  243. }
  244. public Project setName(String name) {
  245. this.name = name;
  246. return this;
  247. }
  248. public Project setChangeType(String changeType) {
  249. this.changeType = changeType;
  250. return this;
  251. }
  252. public Project setBranch(@Nullable String branch) {
  253. this.branch = branch;
  254. return this;
  255. }
  256. public Project setOldBranch(@Nullable String oldBranch) {
  257. this.oldBranch = oldBranch;
  258. return this;
  259. }
  260. public Project setNewBranch(@Nullable String newBranch) {
  261. this.newBranch = newBranch;
  262. return this;
  263. }
  264. private ProjectAnalyses.Project toProject() {
  265. ProjectAnalyses.Project.Builder builder = ProjectAnalyses.Project.newBuilder();
  266. builder
  267. .setKey(key)
  268. .setName(name)
  269. .setChangeType(changeType);
  270. ofNullable(branch).ifPresent(builder::setBranch);
  271. ofNullable(oldBranch).ifPresent(builder::setOldBranch);
  272. ofNullable(newBranch).ifPresent(builder::setNewBranch);
  273. return builder.build();
  274. }
  275. }
  276. }