3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.server.qualityprofile.ws;
22 import java.util.ArrayList;
23 import java.util.List;
25 import java.util.stream.Collectors;
26 import org.sonar.api.resources.Languages;
27 import org.sonar.api.server.ws.Request;
28 import org.sonar.api.server.ws.Response;
29 import org.sonar.api.server.ws.WebService.NewAction;
30 import org.sonar.api.server.ws.WebService.NewController;
31 import org.sonar.db.DbClient;
32 import org.sonar.db.DbSession;
33 import org.sonar.db.qualityprofile.ActiveRuleCountQuery;
34 import org.sonar.db.qualityprofile.ActiveRuleDao;
35 import org.sonar.db.qualityprofile.QProfileDto;
36 import org.sonarqube.ws.Qualityprofiles.InheritanceWsResponse;
37 import org.sonarqube.ws.Qualityprofiles.InheritanceWsResponse.QualityProfile;
39 import static java.util.Collections.singleton;
40 import static java.util.Optional.ofNullable;
41 import static org.sonar.db.qualityprofile.ActiveRuleDto.OVERRIDES;
42 import static org.sonar.server.ws.WsUtils.writeProtobuf;
44 public class InheritanceAction implements QProfileWsAction {
46 private final DbClient dbClient;
47 private final QProfileWsSupport wsSupport;
48 private final Languages languages;
50 public InheritanceAction(DbClient dbClient, QProfileWsSupport wsSupport, Languages languages) {
51 this.dbClient = dbClient;
52 this.wsSupport = wsSupport;
53 this.languages = languages;
57 public void define(NewController context) {
58 NewAction inheritance = context.createAction("inheritance")
60 .setDescription("Show a quality profile's ancestors and children.")
62 .setResponseExample(getClass().getResource("inheritance-example.json"));
64 QProfileReference.defineParams(inheritance, languages);
68 public void handle(Request request, Response response) throws Exception {
69 QProfileReference reference = QProfileReference.fromName(request);
70 try (DbSession dbSession = dbClient.openSession(false)) {
71 QProfileDto profile = wsSupport.getProfile(dbSession, reference);
72 List<QProfileDto> ancestors = ancestors(profile, dbSession);
73 List<QProfileDto> children = dbClient.qualityProfileDao().selectChildren(dbSession, singleton(profile));
74 List<QProfileDto> allProfiles = new ArrayList<>();
75 allProfiles.add(profile);
76 allProfiles.addAll(ancestors);
77 allProfiles.addAll(children);
78 Statistics statistics = new Statistics(dbSession, allProfiles);
80 writeProtobuf(buildResponse(profile, ancestors, children, statistics), request, response);
84 private List<QProfileDto> ancestors(QProfileDto profile, DbSession dbSession) {
85 List<QProfileDto> ancestors = new ArrayList<>();
86 collectAncestors(profile, ancestors, dbSession);
90 private void collectAncestors(QProfileDto profile, List<QProfileDto> ancestors, DbSession session) {
91 if (profile.getParentKee() == null) {
95 QProfileDto parent = getParent(session, profile);
96 ancestors.add(parent);
97 collectAncestors(parent, ancestors, session);
100 private QProfileDto getParent(DbSession dbSession, QProfileDto profile) {
101 QProfileDto parent = dbClient.qualityProfileDao().selectByUuid(dbSession, profile.getParentKee());
102 if (parent == null) {
103 throw new IllegalStateException("Cannot find parent of profile: " + profile.getKee());
108 private static InheritanceWsResponse buildResponse(QProfileDto profile, List<QProfileDto> ancestors, List<QProfileDto> children, Statistics statistics) {
109 return InheritanceWsResponse.newBuilder()
110 .setProfile(buildProfile(profile, statistics))
111 .addAllAncestors(buildAncestors(ancestors, statistics))
112 .addAllChildren(buildChildren(children, statistics))
116 private static Iterable<QualityProfile> buildAncestors(List<QProfileDto> ancestors, Statistics statistics) {
117 return ancestors.stream()
118 .map(ancestor -> buildProfile(ancestor, statistics))
119 .collect(Collectors.toList());
122 private static Iterable<QualityProfile> buildChildren(List<QProfileDto> children, Statistics statistics) {
123 return children.stream()
124 .map(child -> buildProfile(child, statistics))
125 .collect(Collectors.toList());
128 private static QualityProfile buildProfile(QProfileDto qualityProfile, Statistics statistics) {
129 String key = qualityProfile.getKee();
130 QualityProfile.Builder builder = QualityProfile.newBuilder()
132 .setName(qualityProfile.getName())
133 .setActiveRuleCount(statistics.countRulesByProfileKey.getOrDefault(key, 0L))
134 .setOverridingRuleCount(statistics.countOverridingRulesByProfileKey.getOrDefault(key, 0L))
135 .setIsBuiltIn(qualityProfile.isBuiltIn());
136 ofNullable(qualityProfile.getParentKee()).ifPresent(builder::setParent);
137 return builder.build();
140 private class Statistics {
141 private final Map<String, Long> countRulesByProfileKey;
142 private final Map<String, Long> countOverridingRulesByProfileKey;
144 private Statistics(DbSession dbSession, List<QProfileDto> profiles) {
145 ActiveRuleDao dao = dbClient.activeRuleDao();
146 ActiveRuleCountQuery.Builder builder = ActiveRuleCountQuery.builder();
147 countRulesByProfileKey = dao.countActiveRulesByQuery(dbSession, builder.setProfiles(profiles).build());
148 countOverridingRulesByProfileKey = dao.countActiveRulesByQuery(dbSession, builder.setProfiles(profiles).setInheritance(OVERRIDES).build());