]> source.dussan.org Git - sonarqube.git/blob
6bdc3b7b58099f6e04472c652e73b58b4979b6e3
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2022 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.pushapi.sonarlint;
21
22 import java.io.IOException;
23 import java.util.Collections;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Set;
27 import java.util.concurrent.CopyOnWriteArrayList;
28 import java.util.function.Predicate;
29 import javax.servlet.AsyncEvent;
30 import javax.servlet.AsyncListener;
31 import org.json.JSONArray;
32 import org.json.JSONObject;
33 import org.sonar.api.server.ServerSide;
34 import org.sonar.api.utils.log.Logger;
35 import org.sonar.api.utils.log.Loggers;
36 import org.sonar.core.util.ParamChange;
37 import org.sonar.core.util.RuleActivationListener;
38 import org.sonar.core.util.RuleChange;
39 import org.sonar.core.util.RuleSetChangeEvent;
40 import org.sonar.server.exceptions.ForbiddenException;
41 import org.sonar.server.pushapi.qualityprofile.RuleActivatorEventsDistributor;
42
43 import static java.util.Arrays.asList;
44
45 @ServerSide
46 public class SonarLintClientsRegistry implements RuleActivationListener {
47
48   private static final Logger LOG = Loggers.get(SonarLintClientsRegistry.class);
49
50   private final SonarLintClientPermissionsValidator sonarLintClientPermissionsValidator;
51   private final List<SonarLintClient> clients = new CopyOnWriteArrayList<>();
52
53   public SonarLintClientsRegistry(RuleActivatorEventsDistributor ruleActivatorEventsDistributor, SonarLintClientPermissionsValidator permissionsValidator) {
54     this.sonarLintClientPermissionsValidator = permissionsValidator;
55
56     ruleActivatorEventsDistributor.subscribe(this);
57   }
58
59   public void registerClient(SonarLintClient sonarLintClient) {
60     clients.add(sonarLintClient);
61     sonarLintClient.scheduleHeartbeat();
62     sonarLintClient.addListener(new SonarLintClientEventsListener(sonarLintClient));
63     LOG.debug("Registering new SonarLint client");
64   }
65
66   public void unregisterClient(SonarLintClient client) {
67     client.close();
68     clients.remove(client);
69     LOG.debug("Removing SonarLint client");
70   }
71
72   public long countConnectedClients() {
73     return clients.size();
74   }
75
76   @Override
77   public void listen(RuleSetChangeEvent ruleChangeEvent) {
78     broadcastMessage(ruleChangeEvent, getFilterForEvent(ruleChangeEvent));
79   }
80
81   private static Predicate<SonarLintClient> getFilterForEvent(RuleSetChangeEvent ruleChangeEvent) {
82     List<String> affectedProjects = asList(ruleChangeEvent.getProjects());
83     return client -> {
84       Set<String> clientProjectKeys = client.getClientProjectKeys();
85       Set<String> languages = client.getLanguages();
86       return !Collections.disjoint(clientProjectKeys, affectedProjects) && languages.contains(ruleChangeEvent.getLanguage());
87     };
88   }
89
90   public void broadcastMessage(RuleSetChangeEvent message, Predicate<SonarLintClient> filter) {
91     clients.stream().filter(filter).forEach(c -> {
92       Set<String> projectKeysInterestingForClient = new HashSet<>(c.getClientProjectKeys());
93       projectKeysInterestingForClient.retainAll(Set.of(message.getProjects()));
94       try {
95         sonarLintClientPermissionsValidator.validateUserCanReceivePushEventForProjects(c.getUserUuid(), projectKeysInterestingForClient);
96         RuleSetChangeEvent personalizedEvent = new RuleSetChangeEvent(projectKeysInterestingForClient.toArray(String[]::new), message.getActivatedRules(),
97           message.getDeactivatedRules());
98         String jsonString = getJSONString(personalizedEvent);
99         c.writeAndFlush(jsonString);
100       } catch (ForbiddenException forbiddenException) {
101         LOG.debug("Client is no longer authenticated: " + forbiddenException.getMessage());
102         unregisterClient(c);
103       } catch (IllegalStateException | IOException e) {
104         LOG.error("Unable to send message to a client: " + e.getMessage());
105         unregisterClient(c);
106       }
107     });
108   }
109
110   public String getJSONString(RuleSetChangeEvent ruleSetChangeEvent) {
111     JSONObject result = new JSONObject();
112     result.put("event", ruleSetChangeEvent.getEvent());
113
114     JSONObject data = new JSONObject();
115     data.put("projects", ruleSetChangeEvent.getProjects());
116
117     JSONArray activatedRulesJson = new JSONArray();
118     for (RuleChange rule : ruleSetChangeEvent.getActivatedRules()) {
119       activatedRulesJson.put(toJson(rule));
120     }
121     data.put("activatedRules", activatedRulesJson);
122
123     JSONArray deactivatedRulesJson = new JSONArray();
124     for (RuleChange rule : ruleSetChangeEvent.getDeactivatedRules()) {
125       deactivatedRulesJson.put(toJson(rule));
126     }
127     data.put("deactivatedRules", deactivatedRulesJson);
128
129     result.put("data", data);
130     return result.toString();
131   }
132
133   private static JSONObject toJson(RuleChange rule) {
134     JSONObject ruleJson = new JSONObject();
135     ruleJson.put("key", rule.getKey());
136     ruleJson.put("language", rule.getLanguage());
137     ruleJson.put("severity", rule.getSeverity());
138     ruleJson.put("templateKey", rule.getTemplateKey());
139
140     JSONArray params = new JSONArray();
141     for (ParamChange paramChange : rule.getParams()) {
142       params.put(toJson(paramChange));
143     }
144     ruleJson.put("params", params);
145     return ruleJson;
146   }
147
148   private static JSONObject toJson(ParamChange paramChange) {
149     JSONObject param = new JSONObject();
150     param.put("key", paramChange.getKey());
151     param.put("value", paramChange.getValue());
152     return param;
153   }
154
155   class SonarLintClientEventsListener implements AsyncListener {
156     private final SonarLintClient client;
157
158     public SonarLintClientEventsListener(SonarLintClient sonarLintClient) {
159       this.client = sonarLintClient;
160     }
161
162     @Override
163     public void onComplete(AsyncEvent event) {
164       unregisterClient(client);
165     }
166
167     @Override
168     public void onError(AsyncEvent event) {
169       unregisterClient(client);
170     }
171
172     @Override
173     public void onStartAsync(AsyncEvent event) {
174       // nothing to do on start
175     }
176
177     @Override
178     public void onTimeout(AsyncEvent event) {
179       unregisterClient(client);
180     }
181   }
182
183 }