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

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