]> source.dussan.org Git - sonarqube.git/blob
eed10f265f262f1ed3208cc210d1e120ab8f5079
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 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.notification;
21
22 import com.google.common.annotations.VisibleForTesting;
23 import com.google.common.collect.ImmutableMultimap;
24 import com.google.common.collect.ImmutableSetMultimap;
25 import com.google.common.collect.Multimap;
26 import java.io.IOException;
27 import java.io.InvalidClassException;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.Objects;
32 import java.util.Set;
33 import java.util.stream.Collectors;
34 import java.util.stream.Stream;
35 import javax.annotation.Nullable;
36 import org.sonar.api.notifications.Notification;
37 import org.sonar.api.notifications.NotificationChannel;
38 import org.sonar.api.utils.SonarException;
39 import org.sonar.api.utils.log.Logger;
40 import org.sonar.api.utils.log.Loggers;
41 import org.sonar.core.util.stream.MoreCollectors;
42 import org.sonar.db.DbClient;
43 import org.sonar.db.DbSession;
44 import org.sonar.db.EmailSubscriberDto;
45 import org.sonar.db.notification.NotificationQueueDto;
46 import org.sonar.db.property.Subscriber;
47 import org.sonar.server.notification.email.EmailNotificationChannel;
48
49 import static java.util.Collections.emptySet;
50 import static java.util.Collections.singletonList;
51 import static java.util.Objects.requireNonNull;
52
53 public class DefaultNotificationManager implements NotificationManager {
54
55   private static final Logger LOG = Loggers.get(DefaultNotificationManager.class);
56
57   private static final String UNABLE_TO_READ_NOTIFICATION = "Unable to read notification";
58
59   private final NotificationChannel[] notificationChannels;
60   private final DbClient dbClient;
61   private boolean alreadyLoggedDeserializationIssue = false;
62
63   public DefaultNotificationManager(NotificationChannel[] channels, DbClient dbClient) {
64     this.notificationChannels = channels;
65     this.dbClient = dbClient;
66   }
67
68   /**
69    * {@inheritDoc}
70    */
71   @Override
72   public <T extends Notification> void scheduleForSending(T notification) {
73     NotificationQueueDto dto = NotificationQueueDto.toNotificationQueueDto(notification);
74     dbClient.notificationQueueDao().insert(singletonList(dto));
75   }
76
77   /**
78    * Give the notification queue so that it can be processed
79    */
80   public <T extends Notification> T getFromQueue() {
81     int batchSize = 1;
82     List<NotificationQueueDto> notificationDtos = dbClient.notificationQueueDao().selectOldest(batchSize);
83     if (notificationDtos.isEmpty()) {
84       return null;
85     }
86     dbClient.notificationQueueDao().delete(notificationDtos);
87
88     return convertToNotification(notificationDtos);
89   }
90
91   private <T extends Notification> T convertToNotification(List<NotificationQueueDto> notifications) {
92     try {
93       // If batchSize is increased then we should return a list instead of a single element
94       return notifications.get(0).toNotification();
95     } catch (InvalidClassException e) {
96       // SONAR-4739
97       if (!alreadyLoggedDeserializationIssue) {
98         logDeserializationIssue();
99         alreadyLoggedDeserializationIssue = true;
100       }
101       return null;
102     } catch (IOException | ClassNotFoundException e) {
103       throw new SonarException(UNABLE_TO_READ_NOTIFICATION, e);
104     }
105   }
106
107   @VisibleForTesting
108   void logDeserializationIssue() {
109     LOG.warn("It is impossible to send pending notifications which existed prior to the upgrade of SonarQube. They will be ignored.");
110   }
111
112   public long count() {
113     return dbClient.notificationQueueDao().count();
114   }
115
116   private static void verifyProjectKey(String projectKey) {
117     requireNonNull(projectKey, "projectKey is mandatory");
118   }
119
120   @Override
121   public Set<EmailRecipient> findSubscribedEmailRecipients(String dispatcherKey, String projectKey, SubscriberPermissionsOnProject subscriberPermissionsOnProject) {
122     verifyProjectKey(projectKey);
123
124     try (DbSession dbSession = dbClient.openSession(false)) {
125       Set<EmailSubscriberDto> emailSubscribers = dbClient.propertiesDao().findEmailSubscribersForNotification(
126         dbSession, dispatcherKey, EmailNotificationChannel.class.getSimpleName(), projectKey);
127
128       return keepAuthorizedEmailSubscribers(dbSession, projectKey, subscriberPermissionsOnProject, emailSubscribers);
129     }
130   }
131
132   @Override
133   public Set<EmailRecipient> findSubscribedEmailRecipients(String dispatcherKey, String projectKey, Set<String> logins,
134     SubscriberPermissionsOnProject subscriberPermissionsOnProject) {
135     verifyProjectKey(projectKey);
136     requireNonNull(logins, "logins can't be null");
137     if (logins.isEmpty()) {
138       return emptySet();
139     }
140
141     try (DbSession dbSession = dbClient.openSession(false)) {
142       Set<EmailSubscriberDto> emailSubscribers = dbClient.propertiesDao().findEmailSubscribersForNotification(
143         dbSession, dispatcherKey, EmailNotificationChannel.class.getSimpleName(), projectKey, logins);
144
145       return keepAuthorizedEmailSubscribers(dbSession, projectKey, subscriberPermissionsOnProject, emailSubscribers);
146     }
147   }
148
149   private Set<EmailRecipient> keepAuthorizedEmailSubscribers(DbSession dbSession, String projectKey,
150     SubscriberPermissionsOnProject subscriberPermissionsOnProject, Set<EmailSubscriberDto> emailSubscribers) {
151     if (emailSubscribers.isEmpty()) {
152       return emptySet();
153     }
154
155     return keepAuthorizedEmailSubscribers(dbSession, projectKey, emailSubscribers, subscriberPermissionsOnProject)
156       .map(emailSubscriber -> new EmailRecipient(emailSubscriber.getLogin(), emailSubscriber.getEmail()))
157       .collect(MoreCollectors.toSet());
158   }
159
160   private Stream<EmailSubscriberDto> keepAuthorizedEmailSubscribers(DbSession dbSession, String projectKey, Set<EmailSubscriberDto> emailSubscribers,
161     SubscriberPermissionsOnProject requiredPermissions) {
162     if (requiredPermissions.getGlobalSubscribers().equals(requiredPermissions.getProjectSubscribers())) {
163       return keepAuthorizedEmailSubscribers(dbSession, projectKey, emailSubscribers, null, requiredPermissions.getGlobalSubscribers());
164     } else {
165       return Stream.concat(
166         keepAuthorizedEmailSubscribers(dbSession, projectKey, emailSubscribers, true, requiredPermissions.getGlobalSubscribers()),
167         keepAuthorizedEmailSubscribers(dbSession, projectKey, emailSubscribers, false, requiredPermissions.getProjectSubscribers()));
168     }
169   }
170
171   private Stream<EmailSubscriberDto> keepAuthorizedEmailSubscribers(DbSession dbSession, String projectKey, Set<EmailSubscriberDto> emailSubscribers,
172     @Nullable Boolean global, String permission) {
173     Set<EmailSubscriberDto> subscribers = emailSubscribers.stream()
174       .filter(s -> global == null || s.isGlobal() == global)
175       .collect(Collectors.toSet());
176     if (subscribers.isEmpty()) {
177       return Stream.empty();
178     }
179
180     Set<String> logins = subscribers.stream()
181       .map(EmailSubscriberDto::getLogin)
182       .collect(Collectors.toSet());
183     Set<String> authorizedLogins = dbClient.authorizationDao().keepAuthorizedLoginsOnProject(dbSession, logins, projectKey, permission);
184     return subscribers.stream()
185       .filter(s -> authorizedLogins.contains(s.getLogin()));
186   }
187
188   @VisibleForTesting
189   protected List<NotificationChannel> getChannels() {
190     return Arrays.asList(notificationChannels);
191   }
192
193 }