Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

ComponentAction.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2018 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.measure.ws;
  21. import com.google.common.base.Joiner;
  22. import com.google.common.base.Optional;
  23. import com.google.common.collect.ImmutableSortedSet;
  24. import com.google.common.collect.Lists;
  25. import com.google.common.collect.Maps;
  26. import com.google.common.collect.Sets;
  27. import java.util.HashMap;
  28. import java.util.LinkedHashSet;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.Set;
  32. import javax.annotation.CheckForNull;
  33. import javax.annotation.Nullable;
  34. import org.sonar.api.resources.Qualifiers;
  35. import org.sonar.api.server.ws.Change;
  36. import org.sonar.api.server.ws.Request;
  37. import org.sonar.api.server.ws.Response;
  38. import org.sonar.api.server.ws.WebService;
  39. import org.sonar.api.web.UserRole;
  40. import org.sonar.core.util.stream.MoreCollectors;
  41. import org.sonar.db.DbClient;
  42. import org.sonar.db.DbSession;
  43. import org.sonar.db.component.ComponentDto;
  44. import org.sonar.db.component.SnapshotDto;
  45. import org.sonar.db.measure.LiveMeasureDto;
  46. import org.sonar.db.metric.MetricDto;
  47. import org.sonar.db.metric.MetricDtoFunctions;
  48. import org.sonar.server.component.ComponentFinder;
  49. import org.sonar.server.exceptions.NotFoundException;
  50. import org.sonar.server.user.UserSession;
  51. import org.sonarqube.ws.Measures;
  52. import org.sonarqube.ws.Measures.ComponentWsResponse;
  53. import static com.google.common.base.Preconditions.checkArgument;
  54. import static java.lang.String.format;
  55. import static java.util.Collections.emptyMap;
  56. import static java.util.Collections.singletonList;
  57. import static java.util.Collections.singletonMap;
  58. import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
  59. import static org.sonar.server.component.ComponentFinder.ParamNames.COMPONENT_ID_AND_KEY;
  60. import static org.sonar.server.component.ws.MeasuresWsParameters.ACTION_COMPONENT;
  61. import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_METRICS;
  62. import static org.sonar.server.component.ws.MeasuresWsParameters.ADDITIONAL_PERIODS;
  63. import static org.sonar.server.component.ws.MeasuresWsParameters.DEPRECATED_PARAM_COMPONENT_ID;
  64. import static org.sonar.server.component.ws.MeasuresWsParameters.DEPRECATED_PARAM_COMPONENT_KEY;
  65. import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_ADDITIONAL_FIELDS;
  66. import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH;
  67. import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_COMPONENT;
  68. import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_DEVELOPER_ID;
  69. import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_DEVELOPER_KEY;
  70. import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRIC_KEYS;
  71. import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST;
  72. import static org.sonar.server.measure.ws.ComponentDtoToWsComponent.componentDtoToWsComponent;
  73. import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createAdditionalFieldsParameter;
  74. import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createDeveloperParameters;
  75. import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createMetricKeysParameter;
  76. import static org.sonar.server.measure.ws.MetricDtoToWsMetric.metricDtoToWsMetric;
  77. import static org.sonar.server.measure.ws.SnapshotDtoToWsPeriods.snapshotToWsPeriods;
  78. import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
  79. import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
  80. import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
  81. import static org.sonar.server.ws.WsUtils.checkRequest;
  82. import static org.sonar.server.ws.WsUtils.writeProtobuf;
  83. public class ComponentAction implements MeasuresWsAction {
  84. private static final Set<String> QUALIFIERS_ELIGIBLE_FOR_BEST_VALUE = ImmutableSortedSet.of(Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE);
  85. private final DbClient dbClient;
  86. private final ComponentFinder componentFinder;
  87. private final UserSession userSession;
  88. public ComponentAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) {
  89. this.dbClient = dbClient;
  90. this.componentFinder = componentFinder;
  91. this.userSession = userSession;
  92. }
  93. @Override
  94. public void define(WebService.NewController context) {
  95. WebService.NewAction action = context.createAction(ACTION_COMPONENT)
  96. .setDescription(format("Return component with specified measures. The %s or the %s parameter must be provided.<br>" +
  97. "Requires the following permission: 'Browse' on the project of specified component.",
  98. DEPRECATED_PARAM_COMPONENT_ID, PARAM_COMPONENT))
  99. .setResponseExample(getClass().getResource("component-example.json"))
  100. .setSince("5.4")
  101. .setChangelog(
  102. new Change("6.6", "the response field id is deprecated. Use key instead."),
  103. new Change("6.6", "the response field refId is deprecated. Use refKey instead."))
  104. .setHandler(this);
  105. action.createParam(PARAM_COMPONENT)
  106. .setDescription("Component key")
  107. .setExampleValue(KEY_PROJECT_EXAMPLE_001)
  108. .setDeprecatedKey(DEPRECATED_PARAM_COMPONENT_KEY, "6.6");
  109. action.createParam(DEPRECATED_PARAM_COMPONENT_ID)
  110. .setDescription("Component id")
  111. .setExampleValue(UUID_EXAMPLE_01)
  112. .setDeprecatedSince("6.6");
  113. action.createParam(PARAM_BRANCH)
  114. .setDescription("Branch key")
  115. .setExampleValue(KEY_BRANCH_EXAMPLE_001)
  116. .setInternal(true)
  117. .setSince("6.6");
  118. action.createParam(PARAM_PULL_REQUEST)
  119. .setDescription("Pull request id")
  120. .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001)
  121. .setInternal(true)
  122. .setSince("7.1");
  123. createMetricKeysParameter(action);
  124. createAdditionalFieldsParameter(action);
  125. createDeveloperParameters(action);
  126. }
  127. @Override
  128. public void handle(Request request, Response response) throws Exception {
  129. if (request.param(PARAM_DEVELOPER_ID) != null || request.param(PARAM_DEVELOPER_KEY) != null) {
  130. throw new NotFoundException("The Developer Cockpit feature has been dropped. The specified developer cannot be found.");
  131. }
  132. ComponentWsResponse componentWsResponse = doHandle(toComponentWsRequest(request));
  133. writeProtobuf(componentWsResponse, request, response);
  134. }
  135. private ComponentWsResponse doHandle(ComponentRequest request) {
  136. try (DbSession dbSession = dbClient.openSession(false)) {
  137. ComponentDto component = loadComponent(dbSession, request);
  138. Optional<ComponentDto> refComponent = getReferenceComponent(dbSession, component);
  139. checkPermissions(component);
  140. SnapshotDto analysis = dbClient.snapshotDao().selectLastAnalysisByRootComponentUuid(dbSession, component.projectUuid()).orElse(null);
  141. List<MetricDto> metrics = searchMetrics(dbSession, request);
  142. List<Measures.Period> periods = snapshotToWsPeriods(analysis);
  143. List<LiveMeasureDto> measures = searchMeasures(dbSession, component, metrics);
  144. return buildResponse(request, component, refComponent, measures, metrics, periods);
  145. }
  146. }
  147. private ComponentDto loadComponent(DbSession dbSession, ComponentRequest request) {
  148. String componentKey = request.getComponent();
  149. String componentId = request.getComponentId();
  150. String branch = request.getBranch();
  151. String pullRequest = request.getPullRequest();
  152. checkArgument(componentId == null || (branch == null && pullRequest == null), "Parameter '%s' cannot be used at the same time as '%s' or '%s'",
  153. DEPRECATED_PARAM_COMPONENT_ID, PARAM_BRANCH, PARAM_PULL_REQUEST);
  154. if (branch == null && pullRequest == null) {
  155. return componentFinder.getByUuidOrKey(dbSession, componentId, componentKey, COMPONENT_ID_AND_KEY);
  156. }
  157. checkRequest(componentKey != null, "The '%s' parameter is missing", PARAM_COMPONENT);
  158. return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest);
  159. }
  160. private Optional<ComponentDto> getReferenceComponent(DbSession dbSession, ComponentDto component) {
  161. if (component.getCopyResourceUuid() == null) {
  162. return Optional.absent();
  163. }
  164. return dbClient.componentDao().selectByUuid(dbSession, component.getCopyResourceUuid());
  165. }
  166. private static ComponentWsResponse buildResponse(ComponentRequest request, ComponentDto component, Optional<ComponentDto> refComponent, List<LiveMeasureDto> measures,
  167. List<MetricDto> metrics, List<Measures.Period> periods) {
  168. ComponentWsResponse.Builder response = ComponentWsResponse.newBuilder();
  169. Map<Integer, MetricDto> metricsById = Maps.uniqueIndex(metrics, MetricDto::getId);
  170. Map<MetricDto, LiveMeasureDto> measuresByMetric = new HashMap<>();
  171. for (LiveMeasureDto measure : measures) {
  172. MetricDto metric = metricsById.get(measure.getMetricId());
  173. measuresByMetric.put(metric, measure);
  174. }
  175. if (refComponent.isPresent()) {
  176. response.setComponent(componentDtoToWsComponent(component, measuresByMetric, singletonMap(refComponent.get().uuid(), refComponent.get())));
  177. } else {
  178. response.setComponent(componentDtoToWsComponent(component, measuresByMetric, emptyMap()));
  179. }
  180. List<String> additionalFields = request.getAdditionalFields();
  181. if (additionalFields != null) {
  182. if (additionalFields.contains(ADDITIONAL_METRICS)) {
  183. for (MetricDto metric : metrics) {
  184. response.getMetricsBuilder().addMetrics(metricDtoToWsMetric(metric));
  185. }
  186. }
  187. if (additionalFields.contains(ADDITIONAL_PERIODS)) {
  188. response.getPeriodsBuilder().addAllPeriods(periods);
  189. }
  190. }
  191. return response.build();
  192. }
  193. private List<MetricDto> searchMetrics(DbSession dbSession, ComponentRequest request) {
  194. List<MetricDto> metrics = dbClient.metricDao().selectByKeys(dbSession, request.getMetricKeys());
  195. if (metrics.size() < request.getMetricKeys().size()) {
  196. List<String> foundMetricKeys = Lists.transform(metrics, MetricDto::getKey);
  197. Set<String> missingMetricKeys = Sets.difference(
  198. new LinkedHashSet<>(request.getMetricKeys()),
  199. new LinkedHashSet<>(foundMetricKeys));
  200. throw new NotFoundException(format("The following metric keys are not found: %s", Joiner.on(", ").join(missingMetricKeys)));
  201. }
  202. return metrics;
  203. }
  204. private List<LiveMeasureDto> searchMeasures(DbSession dbSession, ComponentDto component, List<MetricDto> metrics) {
  205. List<Integer> metricIds = Lists.transform(metrics, MetricDto::getId);
  206. List<LiveMeasureDto> measures = dbClient.liveMeasureDao().selectByComponentUuidsAndMetricIds(dbSession, singletonList(component.uuid()), metricIds);
  207. addBestValuesToMeasures(measures, component, metrics);
  208. return measures;
  209. }
  210. /**
  211. * Conditions for best value measure:
  212. * <ul>
  213. * <li>component is a production file or test file</li>
  214. * <li>metric is optimized for best value</li>
  215. * </ul>
  216. */
  217. private static void addBestValuesToMeasures(List<LiveMeasureDto> measures, ComponentDto component, List<MetricDto> metrics) {
  218. if (!QUALIFIERS_ELIGIBLE_FOR_BEST_VALUE.contains(component.qualifier())) {
  219. return;
  220. }
  221. List<MetricDtoWithBestValue> metricWithBestValueList = metrics.stream()
  222. .filter(MetricDtoFunctions.isOptimizedForBestValue())
  223. .map(MetricDtoWithBestValue::new)
  224. .collect(MoreCollectors.toList(metrics.size()));
  225. Map<Integer, LiveMeasureDto> measuresByMetricId = Maps.uniqueIndex(measures, LiveMeasureDto::getMetricId);
  226. for (MetricDtoWithBestValue metricWithBestValue : metricWithBestValueList) {
  227. if (measuresByMetricId.get(metricWithBestValue.getMetric().getId()) == null) {
  228. measures.add(metricWithBestValue.getBestValue());
  229. }
  230. }
  231. }
  232. private static ComponentRequest toComponentWsRequest(Request request) {
  233. ComponentRequest componentRequest = new ComponentRequest()
  234. .setComponentId(request.param(DEPRECATED_PARAM_COMPONENT_ID))
  235. .setComponent(request.param(PARAM_COMPONENT))
  236. .setBranch(request.param(PARAM_BRANCH))
  237. .setPullRequest(request.param(PARAM_PULL_REQUEST))
  238. .setAdditionalFields(request.paramAsStrings(PARAM_ADDITIONAL_FIELDS))
  239. .setMetricKeys(request.mandatoryParamAsStrings(PARAM_METRIC_KEYS));
  240. checkRequest(!componentRequest.getMetricKeys().isEmpty(), "At least one metric key must be provided");
  241. return componentRequest;
  242. }
  243. private void checkPermissions(ComponentDto baseComponent) {
  244. userSession.checkComponentPermission(UserRole.USER, baseComponent);
  245. }
  246. private static class ComponentRequest {
  247. private String componentId;
  248. private String component;
  249. private String branch;
  250. private String pullRequest;
  251. private List<String> metricKeys;
  252. private List<String> additionalFields;
  253. private String developerId;
  254. private String developerKey;
  255. /**
  256. * @deprecated since 6.6, please use {@link #getComponent()} instead
  257. */
  258. @Deprecated
  259. @CheckForNull
  260. private String getComponentId() {
  261. return componentId;
  262. }
  263. /**
  264. * @deprecated since 6.6, please use {@link #setComponent(String)} instead
  265. */
  266. @Deprecated
  267. private ComponentRequest setComponentId(@Nullable String componentId) {
  268. this.componentId = componentId;
  269. return this;
  270. }
  271. @CheckForNull
  272. private String getComponent() {
  273. return component;
  274. }
  275. private ComponentRequest setComponent(@Nullable String component) {
  276. this.component = component;
  277. return this;
  278. }
  279. @CheckForNull
  280. private String getBranch() {
  281. return branch;
  282. }
  283. private ComponentRequest setBranch(@Nullable String branch) {
  284. this.branch = branch;
  285. return this;
  286. }
  287. @CheckForNull
  288. public String getPullRequest() {
  289. return pullRequest;
  290. }
  291. public ComponentRequest setPullRequest(@Nullable String pullRequest) {
  292. this.pullRequest = pullRequest;
  293. return this;
  294. }
  295. private List<String> getMetricKeys() {
  296. return metricKeys;
  297. }
  298. private ComponentRequest setMetricKeys(@Nullable List<String> metricKeys) {
  299. this.metricKeys = metricKeys;
  300. return this;
  301. }
  302. @CheckForNull
  303. private List<String> getAdditionalFields() {
  304. return additionalFields;
  305. }
  306. private ComponentRequest setAdditionalFields(@Nullable List<String> additionalFields) {
  307. this.additionalFields = additionalFields;
  308. return this;
  309. }
  310. @CheckForNull
  311. private String getDeveloperId() {
  312. return developerId;
  313. }
  314. private ComponentRequest setDeveloperId(@Nullable String developerId) {
  315. this.developerId = developerId;
  316. return this;
  317. }
  318. @CheckForNull
  319. private String getDeveloperKey() {
  320. return developerKey;
  321. }
  322. private ComponentRequest setDeveloperKey(@Nullable String developerKey) {
  323. this.developerKey = developerKey;
  324. return this;
  325. }
  326. }
  327. }