]> source.dussan.org Git - sonarqube.git/blob
88d874337732e9ce1f54c120f9bd16c9dcf2e235
[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.RuleSetChangedEvent;
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   private final RuleActivatorEventsDistributor eventsDistributor;
53
54   private boolean registeredToEvents = false;
55
56   public SonarLintClientsRegistry(RuleActivatorEventsDistributor ruleActivatorEventsDistributor, SonarLintClientPermissionsValidator permissionsValidator) {
57     this.sonarLintClientPermissionsValidator = permissionsValidator;
58     this.eventsDistributor = ruleActivatorEventsDistributor;
59   }
60
61   public void registerClient(SonarLintClient sonarLintClient) {
62     ensureListeningToEvents();
63     clients.add(sonarLintClient);
64     sonarLintClient.scheduleHeartbeat();
65     sonarLintClient.addListener(new SonarLintClientEventsListener(sonarLintClient));
66     LOG.debug("Registering new SonarLint client");
67   }
68
69   private synchronized void ensureListeningToEvents() {
70     if (registeredToEvents) {
71       return;
72     }
73     try {
74       eventsDistributor.subscribe(this);
75       registeredToEvents = true;
76     } catch (RuntimeException e) {
77       LOG.warn("Can not listen to rule activation events for server push. Web Server might not have started fully yet.", e);
78     }
79   }
80
81   public void unregisterClient(SonarLintClient client) {
82     client.close();
83     clients.remove(client);
84     LOG.debug("Removing SonarLint client");
85   }
86
87   public long countConnectedClients() {
88     return clients.size();
89   }
90
91   @Override
92   public void listen(RuleSetChangedEvent ruleSetChangedEvent) {
93     broadcastMessage(ruleSetChangedEvent, getFilterForEvent(ruleSetChangedEvent));
94   }
95
96   private static Predicate<SonarLintClient> getFilterForEvent(RuleSetChangedEvent ruleSetChangedEvent) {
97     List<String> affectedProjects = asList(ruleSetChangedEvent.getProjects());
98     return client -> {
99       Set<String> clientProjectKeys = client.getClientProjectKeys();
100       Set<String> languages = client.getLanguages();
101       return !Collections.disjoint(clientProjectKeys, affectedProjects) && languages.contains(ruleSetChangedEvent.getLanguage());
102     };
103   }
104
105   public void broadcastMessage(RuleSetChangedEvent event, Predicate<SonarLintClient> filter) {
106     clients.stream().filter(filter).forEach(c -> {
107       Set<String> projectKeysInterestingForClient = new HashSet<>(c.getClientProjectKeys());
108       projectKeysInterestingForClient.retainAll(Set.of(event.getProjects()));
109       try {
110         sonarLintClientPermissionsValidator.validateUserCanReceivePushEventForProjects(c.getUserUuid(), projectKeysInterestingForClient);
111         RuleSetChangedEvent personalizedEvent = new RuleSetChangedEvent(projectKeysInterestingForClient.toArray(String[]::new), event.getActivatedRules(),
112           event.getDeactivatedRules(), event.getLanguage());
113         String message = getMessage(personalizedEvent);
114         c.writeAndFlush(message);
115       } catch (ForbiddenException forbiddenException) {
116         LOG.debug("Client is no longer authenticated: " + forbiddenException.getMessage());
117         unregisterClient(c);
118       } catch (IllegalStateException | IOException e) {
119         LOG.error("Unable to send message to a client: " + e.getMessage());
120         unregisterClient(c);
121       }
122     });
123   }
124   private static String getMessage(RuleSetChangedEvent ruleSetChangedEvent) {
125     return "event: " + ruleSetChangedEvent.getEvent() + "\n"
126       + "data: " + toJson(ruleSetChangedEvent);
127   }
128
129   private static String toJson(RuleSetChangedEvent ruleSetChangedEvent) {
130     JSONObject data = new JSONObject();
131     data.put("projects", ruleSetChangedEvent.getProjects());
132
133     JSONArray activatedRulesJson = new JSONArray();
134     for (RuleChange rule : ruleSetChangedEvent.getActivatedRules()) {
135       activatedRulesJson.put(toJson(rule));
136     }
137     data.put("activatedRules", activatedRulesJson);
138
139     JSONArray deactivatedRulesJson = new JSONArray();
140     for (String ruleKey : ruleSetChangedEvent.getDeactivatedRules()) {
141       deactivatedRulesJson.put(ruleKey);
142     }
143     data.put("deactivatedRules", deactivatedRulesJson);
144
145     return data.toString();
146   }
147
148   private static JSONObject toJson(RuleChange rule) {
149     JSONObject ruleJson = new JSONObject();
150     ruleJson.put("key", rule.getKey());
151     ruleJson.put("language", rule.getLanguage());
152     ruleJson.put("severity", rule.getSeverity());
153     ruleJson.put("templateKey", rule.getTemplateKey());
154
155     JSONArray params = new JSONArray();
156     for (ParamChange paramChange : rule.getParams()) {
157       params.put(toJson(paramChange));
158     }
159     ruleJson.put("params", params);
160     return ruleJson;
161   }
162
163   private static JSONObject toJson(ParamChange paramChange) {
164     JSONObject param = new JSONObject();
165     param.put("key", paramChange.getKey());
166     param.put("value", paramChange.getValue());
167     return param;
168   }
169
170   class SonarLintClientEventsListener implements AsyncListener {
171     private final SonarLintClient client;
172
173     public SonarLintClientEventsListener(SonarLintClient sonarLintClient) {
174       this.client = sonarLintClient;
175     }
176
177     @Override
178     public void onComplete(AsyncEvent event) {
179       unregisterClient(client);
180     }
181
182     @Override
183     public void onError(AsyncEvent event) {
184       unregisterClient(client);
185     }
186
187     @Override
188     public void onStartAsync(AsyncEvent event) {
189       // nothing to do on start
190     }
191
192     @Override
193     public void onTimeout(AsyncEvent event) {
194       unregisterClient(client);
195     }
196   }
197
198 }