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.

UpdateEventAction.java 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  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 java.util.List;
  22. import java.util.function.Consumer;
  23. import java.util.function.Function;
  24. import java.util.function.Predicate;
  25. import java.util.stream.Stream;
  26. import org.sonar.api.server.ws.Request;
  27. import org.sonar.api.server.ws.Response;
  28. import org.sonar.api.server.ws.WebService;
  29. import org.sonar.api.web.UserRole;
  30. import org.sonar.core.util.Uuids;
  31. import org.sonar.db.DbClient;
  32. import org.sonar.db.DbSession;
  33. import org.sonar.db.component.SnapshotDto;
  34. import org.sonar.db.event.EventDto;
  35. import org.sonar.server.exceptions.NotFoundException;
  36. import org.sonar.server.user.UserSession;
  37. import org.sonarqube.ws.ProjectAnalyses.Event;
  38. import org.sonarqube.ws.ProjectAnalyses.UpdateEventResponse;
  39. import javax.annotation.CheckForNull;
  40. import static com.google.common.base.Preconditions.checkArgument;
  41. import static java.lang.String.format;
  42. import static java.util.Objects.requireNonNull;
  43. import static java.util.Optional.ofNullable;
  44. import static org.apache.commons.lang.StringUtils.isNotBlank;
  45. import static org.sonar.server.projectanalysis.ws.EventValidator.checkModifiable;
  46. import static org.sonar.server.ws.WsUtils.writeProtobuf;
  47. import static org.sonar.server.projectanalysis.ws.EventCategory.VERSION;
  48. import static org.sonar.server.projectanalysis.ws.EventCategory.fromLabel;
  49. import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_EVENT;
  50. import static org.sonar.server.projectanalysis.ws.ProjectAnalysesWsParameters.PARAM_NAME;
  51. public class UpdateEventAction implements ProjectAnalysesWsAction {
  52. private static final int MAX_NAME_LENGTH = 100;
  53. private final DbClient dbClient;
  54. private final UserSession userSession;
  55. public UpdateEventAction(DbClient dbClient, UserSession userSession) {
  56. this.dbClient = dbClient;
  57. this.userSession = userSession;
  58. }
  59. @Override
  60. public void define(WebService.NewController context) {
  61. WebService.NewAction action = context.createAction("update_event")
  62. .setDescription("Update a project analysis event.<br>" +
  63. "Only events of category '%s' and '%s' can be updated.<br>" +
  64. "Requires one of the following permissions:" +
  65. "<ul>" +
  66. " <li>'Administer System'</li>" +
  67. " <li>'Administer' rights on the specified project</li>" +
  68. "</ul>",
  69. EventCategory.VERSION.name(), EventCategory.OTHER.name())
  70. .setSince("6.3")
  71. .setPost(true)
  72. .setResponseExample(getClass().getResource("update_event-example.json"))
  73. .setHandler(this);
  74. action.createParam(PARAM_EVENT)
  75. .setDescription("Event key")
  76. .setExampleValue(Uuids.UUID_EXAMPLE_08)
  77. .setRequired(true);
  78. action.createParam(PARAM_NAME)
  79. .setMaximumLength(org.sonar.db.event.EventValidator.MAX_NAME_LENGTH)
  80. .setDescription("New name")
  81. .setExampleValue("5.6")
  82. .setRequired(true);
  83. }
  84. @Override
  85. public void handle(Request httpRequest, Response httpResponse) throws Exception {
  86. Stream.of(httpRequest)
  87. .map(toUpdateEventRequest())
  88. .map(this::doHandle)
  89. .forEach(wsResponse -> writeProtobuf(wsResponse, httpRequest, httpResponse));
  90. }
  91. private UpdateEventResponse doHandle(UpdateEventRequest request) {
  92. try (DbSession dbSession = dbClient.openSession(false)) {
  93. return Stream
  94. .of(getDbEvent(dbSession, request))
  95. .peek(checkPermissions())
  96. .peek(checkModifiable())
  97. .peek(checkVersionNameLength(request))
  98. .map(updateNameAndDescription(request))
  99. .peek(checkNonConflictingOtherEvents(dbSession))
  100. .peek(updateInDb(dbSession))
  101. .map(toWsResponse())
  102. .findAny()
  103. .orElseThrow(() -> new IllegalStateException("Event not found"));
  104. }
  105. }
  106. private Consumer<EventDto> updateInDb(DbSession dbSession) {
  107. return event -> {
  108. dbClient.eventDao().update(dbSession, event.getUuid(), event.getName(), event.getDescription());
  109. if (VERSION.getLabel().equals(event.getCategory())) {
  110. SnapshotDto analysis = getAnalysis(dbSession, event);
  111. analysis.setVersion(event.getName());
  112. dbClient.snapshotDao().update(dbSession, analysis);
  113. }
  114. dbSession.commit();
  115. };
  116. }
  117. private EventDto getDbEvent(DbSession dbSession, UpdateEventRequest request) {
  118. checkArgument(isNotBlank(request.getName()), "A non empty name is required");
  119. return dbClient.eventDao().selectByUuid(dbSession, request.getEvent())
  120. .orElseThrow(() -> new NotFoundException(format("Event '%s' not found", request.getEvent())));
  121. }
  122. private Consumer<EventDto> checkPermissions() {
  123. return event -> userSession.checkComponentUuidPermission(UserRole.ADMIN, event.getComponentUuid());
  124. }
  125. private Consumer<EventDto> checkNonConflictingOtherEvents(DbSession dbSession) {
  126. return candidateEvent -> {
  127. List<EventDto> dbEvents = dbClient.eventDao().selectByAnalysisUuid(dbSession, candidateEvent.getAnalysisUuid());
  128. Predicate<EventDto> otherEventWithSameName = otherEvent -> !candidateEvent.getUuid().equals(otherEvent.getUuid()) && otherEvent.getName().equals(candidateEvent.getName());
  129. dbEvents.stream()
  130. .filter(otherEventWithSameName)
  131. .findAny()
  132. .ifPresent(event -> {
  133. throw new IllegalArgumentException(format("An '%s' event with the same name already exists on analysis '%s'",
  134. candidateEvent.getCategory(),
  135. candidateEvent.getAnalysisUuid()));
  136. });
  137. };
  138. }
  139. private static Consumer<EventDto> checkVersionNameLength(UpdateEventRequest request) {
  140. String name = request.getName();
  141. return candidateEvent -> {
  142. if (name != null && VERSION.getLabel().equals(candidateEvent.getCategory())) {
  143. checkArgument(name.length() <= MAX_NAME_LENGTH,
  144. "Version length (%s) is longer than the maximum authorized (%s). '%s' was provided.", name.length(), MAX_NAME_LENGTH, name);
  145. }
  146. };
  147. }
  148. private SnapshotDto getAnalysis(DbSession dbSession, EventDto event) {
  149. return dbClient.snapshotDao().selectByUuid(dbSession, event.getAnalysisUuid())
  150. .orElseThrow(() -> new IllegalStateException(format("Analysis '%s' is not found", event.getAnalysisUuid())));
  151. }
  152. private static Function<EventDto, EventDto> updateNameAndDescription(UpdateEventRequest request) {
  153. return event -> {
  154. ofNullable(request.getName()).ifPresent(event::setName);
  155. return event;
  156. };
  157. }
  158. private static Function<EventDto, UpdateEventResponse> toWsResponse() {
  159. return dbEvent -> {
  160. Event.Builder wsEvent = Event.newBuilder()
  161. .setKey(dbEvent.getUuid())
  162. .setCategory(fromLabel(dbEvent.getCategory()).name())
  163. .setAnalysis(dbEvent.getAnalysisUuid());
  164. ofNullable(dbEvent.getName()).ifPresent(wsEvent::setName);
  165. ofNullable(dbEvent.getDescription()).ifPresent(wsEvent::setDescription);
  166. return UpdateEventResponse.newBuilder().setEvent(wsEvent).build();
  167. };
  168. }
  169. private static Function<Request, UpdateEventRequest> toUpdateEventRequest() {
  170. return request -> new UpdateEventRequest(
  171. request.mandatoryParam(PARAM_EVENT),
  172. request.param(PARAM_NAME));
  173. }
  174. private static class UpdateEventRequest {
  175. private final String event;
  176. private final String name;
  177. public UpdateEventRequest(String event, String name) {
  178. this.event = requireNonNull(event, "Event key is required");
  179. this.name = requireNonNull(name, "Name is required");
  180. }
  181. public String getEvent() {
  182. return event;
  183. }
  184. @CheckForNull
  185. public String getName() {
  186. return name;
  187. }
  188. }
  189. }