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.

SetAction.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  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.newcodeperiod.ws;
  21. import com.google.common.base.Preconditions;
  22. import java.util.EnumSet;
  23. import java.util.Locale;
  24. import java.util.Set;
  25. import javax.annotation.Nullable;
  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.platform.EditionProvider;
  31. import org.sonar.core.platform.PlatformEditionProvider;
  32. import org.sonar.db.DbClient;
  33. import org.sonar.db.DbSession;
  34. import org.sonar.db.component.BranchDto;
  35. import org.sonar.db.component.BranchType;
  36. import org.sonar.db.component.ComponentDto;
  37. import org.sonar.db.component.SnapshotDto;
  38. import org.sonar.db.newcodeperiod.NewCodePeriodDao;
  39. import org.sonar.db.newcodeperiod.NewCodePeriodDto;
  40. import org.sonar.db.newcodeperiod.NewCodePeriodParser;
  41. import org.sonar.db.newcodeperiod.NewCodePeriodType;
  42. import org.sonar.server.component.ComponentFinder;
  43. import org.sonar.server.exceptions.NotFoundException;
  44. import org.sonar.server.user.UserSession;
  45. import static com.google.common.base.Preconditions.checkArgument;
  46. import static java.lang.String.format;
  47. import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS;
  48. import static org.sonar.db.newcodeperiod.NewCodePeriodType.PREVIOUS_VERSION;
  49. import static org.sonar.db.newcodeperiod.NewCodePeriodType.SPECIFIC_ANALYSIS;
  50. import static org.sonar.server.component.ComponentFinder.ParamNames.PROJECT_ID_AND_KEY;
  51. public class SetAction implements NewCodePeriodsWsAction {
  52. private static final String PARAM_BRANCH = "branch";
  53. private static final String PARAM_PROJECT = "project";
  54. private static final String PARAM_TYPE = "type";
  55. private static final String PARAM_VALUE = "value";
  56. private static final String BEGIN_LIST = "<ul>";
  57. private static final String END_LIST = "</ul>";
  58. private static final String BEGIN_ITEM_LIST = "<li>";
  59. private static final String END_ITEM_LIST = "</li>";
  60. private static final Set<NewCodePeriodType> OVERALL_TYPES = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS);
  61. private static final Set<NewCodePeriodType> PROJECT_TYPES = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS);
  62. private static final Set<NewCodePeriodType> BRANCH_TYPES = EnumSet.of(PREVIOUS_VERSION, NUMBER_OF_DAYS, SPECIFIC_ANALYSIS);
  63. private final DbClient dbClient;
  64. private final UserSession userSession;
  65. private final ComponentFinder componentFinder;
  66. private final PlatformEditionProvider editionProvider;
  67. private final NewCodePeriodDao newCodePeriodDao;
  68. public SetAction(DbClient dbClient, UserSession userSession, ComponentFinder componentFinder, PlatformEditionProvider editionProvider, NewCodePeriodDao newCodePeriodDao) {
  69. this.dbClient = dbClient;
  70. this.userSession = userSession;
  71. this.componentFinder = componentFinder;
  72. this.editionProvider = editionProvider;
  73. this.newCodePeriodDao = newCodePeriodDao;
  74. }
  75. @Override
  76. public void define(WebService.NewController context) {
  77. WebService.NewAction action = context.createAction("set")
  78. .setPost(true)
  79. .setDescription("Updates the setting for the New Code Period on different levels:<br>" +
  80. BEGIN_LIST +
  81. BEGIN_ITEM_LIST + "Project key must be provided to update the value for a project" + END_ITEM_LIST +
  82. BEGIN_ITEM_LIST + "Both project and branch keys must be provided to update the value for a branch" + END_ITEM_LIST +
  83. END_LIST +
  84. "Requires one of the following permissions: " +
  85. BEGIN_LIST +
  86. BEGIN_ITEM_LIST + "'Administer System' to change the global setting" + END_ITEM_LIST +
  87. BEGIN_ITEM_LIST + "'Administer' rights on the specified project to change the project setting" + END_ITEM_LIST +
  88. END_LIST)
  89. .setSince("8.0")
  90. .setHandler(this);
  91. action.createParam(PARAM_PROJECT)
  92. .setDescription("Project key");
  93. action.createParam(PARAM_BRANCH)
  94. .setDescription("Branch key");
  95. action.createParam(PARAM_TYPE)
  96. .setRequired(true)
  97. .setDescription("Type<br/>" +
  98. "New code periods of the following types are allowed:" +
  99. BEGIN_LIST +
  100. BEGIN_ITEM_LIST + SPECIFIC_ANALYSIS.name() + " - can be set at branch level only" + END_ITEM_LIST +
  101. BEGIN_ITEM_LIST + PREVIOUS_VERSION.name() + " - can be set at any level (global, project, branch)" + END_ITEM_LIST +
  102. BEGIN_ITEM_LIST + NUMBER_OF_DAYS.name() + " - can be set can be set at any level (global, project, branch)" + END_ITEM_LIST +
  103. END_LIST
  104. );
  105. action.createParam(PARAM_VALUE)
  106. .setDescription("Value<br/>" +
  107. "For each type, a different value is expected:" +
  108. BEGIN_LIST +
  109. BEGIN_ITEM_LIST + "the uuid of an analysis, when type is " + SPECIFIC_ANALYSIS.name() + END_ITEM_LIST +
  110. BEGIN_ITEM_LIST + "no value, when type is " + PREVIOUS_VERSION.name() + END_ITEM_LIST +
  111. BEGIN_ITEM_LIST + "a number, when type is " + NUMBER_OF_DAYS.name() + END_ITEM_LIST +
  112. END_LIST
  113. );
  114. }
  115. @Override
  116. public void handle(Request request, Response response) throws Exception {
  117. String projectStr = request.getParam(PARAM_PROJECT).emptyAsNull().or(() -> null);
  118. String branchStr = request.getParam(PARAM_BRANCH).emptyAsNull().or(() -> null);
  119. if (projectStr == null && branchStr != null) {
  120. throw new IllegalArgumentException("If branch key is specified, project key needs to be specified too");
  121. }
  122. try (DbSession dbSession = dbClient.openSession(false)) {
  123. String typeStr = request.mandatoryParam(PARAM_TYPE);
  124. String valueStr = request.getParam(PARAM_VALUE).emptyAsNull().or(() -> null);
  125. boolean isCommunityEdition = editionProvider.get().filter(t -> t == EditionProvider.Edition.COMMUNITY).isPresent();
  126. NewCodePeriodType type = validateType(typeStr, projectStr == null, branchStr != null || isCommunityEdition);
  127. NewCodePeriodDto dto = new NewCodePeriodDto();
  128. dto.setType(type);
  129. ComponentDto projectBranch = null;
  130. if (projectStr != null) {
  131. projectBranch = getProject(dbSession, projectStr, branchStr);
  132. userSession.checkComponentPermission(UserRole.ADMIN, projectBranch);
  133. // in CE set main branch value instead of project value
  134. if (branchStr != null || isCommunityEdition) {
  135. dto.setBranchUuid(projectBranch.uuid());
  136. }
  137. // depending whether it's the main branch or not
  138. dto.setProjectUuid(projectBranch.getMainBranchProjectUuid() != null ? projectBranch.getMainBranchProjectUuid() : projectBranch.uuid());
  139. } else {
  140. userSession.checkIsSystemAdministrator();
  141. }
  142. setValue(dbSession, dto, type, projectBranch, branchStr, valueStr);
  143. newCodePeriodDao.upsert(dbSession, dto);
  144. dbSession.commit();
  145. }
  146. }
  147. private void setValue(DbSession dbSession, NewCodePeriodDto dto, NewCodePeriodType type, @Nullable ComponentDto projectBranch,
  148. @Nullable String branch, @Nullable String value) {
  149. switch (type) {
  150. case PREVIOUS_VERSION:
  151. Preconditions.checkArgument(value == null, "Unexpected value for type '%s'", type);
  152. break;
  153. case NUMBER_OF_DAYS:
  154. requireValue(type, value);
  155. dto.setValue(parseDays(value));
  156. break;
  157. case SPECIFIC_ANALYSIS:
  158. requireValue(type, value);
  159. requireBranch(type, projectBranch);
  160. SnapshotDto analysis = getAnalysis(dbSession, value, projectBranch, branch);
  161. dto.setValue(analysis.getUuid());
  162. break;
  163. default:
  164. throw new IllegalStateException("Unexpected type: " + type);
  165. }
  166. }
  167. private static String parseDays(String value) {
  168. try {
  169. return Integer.toString(NewCodePeriodParser.parseDays(value));
  170. } catch (Exception e) {
  171. throw new IllegalArgumentException("Failed to parse number of days: " + value);
  172. }
  173. }
  174. private static void requireValue(NewCodePeriodType type, @Nullable String value) {
  175. Preconditions.checkArgument(value != null, "New Code Period type '%s' requires a value", type);
  176. }
  177. private static void requireBranch(NewCodePeriodType type, @Nullable ComponentDto projectBranch) {
  178. Preconditions.checkArgument(projectBranch != null, "New Code Period type '%s' requires a branch", type);
  179. }
  180. private ComponentDto getProject(DbSession dbSession, String projectKey, @Nullable String branchKey) {
  181. if (branchKey == null) {
  182. return componentFinder.getByUuidOrKey(dbSession, null, projectKey, PROJECT_ID_AND_KEY);
  183. }
  184. ComponentDto project = componentFinder.getByKeyAndBranch(dbSession, projectKey, branchKey);
  185. BranchDto branchDto = dbClient.branchDao().selectByUuid(dbSession, project.uuid())
  186. .orElseThrow(() -> new NotFoundException(format("Branch '%s' is not found", branchKey)));
  187. checkArgument(branchDto.getBranchType() == BranchType.LONG,
  188. "Not a long-living branch: '%s'", branchKey);
  189. return project;
  190. }
  191. private static NewCodePeriodType validateType(String typeStr, boolean isOverall, boolean isBranch) {
  192. NewCodePeriodType type;
  193. try {
  194. type = NewCodePeriodType.valueOf(typeStr.toUpperCase(Locale.US));
  195. } catch (IllegalArgumentException e) {
  196. throw new IllegalArgumentException("Invalid type: " + typeStr);
  197. }
  198. if (isOverall) {
  199. checkType("Overall setting", OVERALL_TYPES, type);
  200. } else if (isBranch) {
  201. checkType("Branches", BRANCH_TYPES, type);
  202. } else {
  203. checkType("Projects", PROJECT_TYPES, type);
  204. }
  205. return type;
  206. }
  207. private SnapshotDto getAnalysis(DbSession dbSession, String analysisUuid, ComponentDto projectBranch, @Nullable String branchKey) {
  208. SnapshotDto snapshotDto = dbClient.snapshotDao().selectByUuid(dbSession, analysisUuid)
  209. .orElseThrow(() -> new NotFoundException(format("Analysis '%s' is not found", analysisUuid)));
  210. checkAnalysis(dbSession, projectBranch, branchKey, snapshotDto);
  211. return snapshotDto;
  212. }
  213. private void checkAnalysis(DbSession dbSession, ComponentDto projectBranch, @Nullable String branchKey, SnapshotDto analysis) {
  214. ComponentDto project = dbClient.componentDao().selectByUuid(dbSession, analysis.getComponentUuid()).orElse(null);
  215. boolean analysisMatchesProjectBranch = project != null && projectBranch.uuid().equals(project.uuid());
  216. if (branchKey != null) {
  217. checkArgument(analysisMatchesProjectBranch,
  218. "Analysis '%s' does not belong to branch '%s' of project '%s'",
  219. analysis.getUuid(), branchKey, projectBranch.getKey());
  220. } else {
  221. checkArgument(analysisMatchesProjectBranch,
  222. "Analysis '%s' does not belong to project '%s'",
  223. analysis.getUuid(), projectBranch.getKey());
  224. }
  225. }
  226. private static void checkType(String name, Set<NewCodePeriodType> validTypes, NewCodePeriodType type) {
  227. if (!validTypes.contains(type)) {
  228. throw new IllegalArgumentException(String.format("Invalid type '%s'. %s can only be set with types: %s", type, name, validTypes));
  229. }
  230. }
  231. }