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