]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11753 move "failed analysis" notification to email specific algo
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 29 Mar 2019 16:29:07 +0000 (17:29 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 23 Apr 2019 08:37:54 +0000 (10:37 +0200)
29 files changed:
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotification.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationBuilder.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationDispatcher.java [deleted file]
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationEmailTemplate.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationHandler.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationModule.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationSerializer.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationSerializerImpl.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStep.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationHandlerTest.java [new file with mode: 0644]
server/sonar-ce/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListener.java
server/sonar-ce/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListenerTest.java
server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationHandler.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/DoNotFixNotificationHandler.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/MyNewIssuesNotificationHandler.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationHandler.java
server/sonar-server-common/src/main/java/org/sonar/server/notification/NotificationHandler.java
server/sonar-server-common/src/main/java/org/sonar/server/notification/NotificationService.java
server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/notification/QGChangeNotificationHandler.java
server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationHandlerTest.java
server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/DoNotFixNotificationHandlerTest.java
server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/MyNewIssuesNotificationHandlerTest.java
server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationHandlerTest.java
server/sonar-server-common/src/test/java/org/sonar/server/notification/NotificationServiceTest.java
server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/notification/QGChangeNotificationHandlerTest.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationHandler.java
server/sonar-server/src/test/java/org/sonar/server/notification/NotificationMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQPChangeNotificationHandlerTest.java

index 002de46c02910619201129f51d9224ca9f0bcfc9..c6b0d18f42ab072f2f1a57b7a79940e61f664524 100644 (file)
 package org.sonar.ce.task.projectanalysis.notification;
 
 import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
+import org.sonar.api.notifications.Notification;
 
-import static java.util.Objects.requireNonNull;
+public class ReportAnalysisFailureNotification extends Notification {
 
-public class ReportAnalysisFailureNotification {
   public static final String TYPE = "ce-report-task-failure";
 
-  private final Project project;
-  private final Task task;
-  private final String errorMessage;
-
-  public ReportAnalysisFailureNotification(Project project, Task task, @Nullable String errorMessage) {
-    this.project = requireNonNull(project, "project can't be null");
-    this.task = requireNonNull(task, "task can't be null");
-    this.errorMessage = errorMessage;
-  }
-
-  public Project getProject() {
-    return project;
-  }
-
-  public Task getTask() {
-    return task;
+  public ReportAnalysisFailureNotification() {
+    super(TYPE);
   }
 
   @CheckForNull
-  public String getErrorMessage() {
-    return errorMessage;
-  }
-
-  public static final class Project {
-    private final String uuid;
-    private final String key;
-    private final String name;
-    private final String branchName;
-
-    public Project(String uuid, String key, String name, @Nullable String branchName) {
-      this.uuid = requireNonNull(uuid, "uuid can't be null");
-      this.key = requireNonNull(key, "key can't be null");
-      this.name = requireNonNull(name, "name can't be null");
-      this.branchName = branchName;
-    }
-
-    public String getUuid() {
-      return uuid;
-    }
-
-    public String getKey() {
-      return key;
-    }
-
-    public String getName() {
-      return name;
-    }
-
-    @CheckForNull
-    public String getBranchName() {
-      return branchName;
-    }
-  }
-
-  public static final class Task {
-    private final String uuid;
-    private final long createdAt;
-    private final long failedAt;
-
-    public Task(String uuid, long createdAt, long failedAt) {
-      this.uuid = requireNonNull(uuid, "uuid can't be null");
-      this.createdAt = createdAt;
-      this.failedAt = failedAt;
-    }
-
-    public String getUuid() {
-      return uuid;
-    }
-
-    public long getCreatedAt() {
-      return createdAt;
-    }
-
-    public long getFailedAt() {
-      return failedAt;
-    }
+  public String getProjectKey() {
+    return getFieldValue("project.key");
   }
 }
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationBuilder.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationBuilder.java
new file mode 100644 (file)
index 0000000..4f08472
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.ce.task.projectanalysis.notification;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import static java.util.Objects.requireNonNull;
+
+public class ReportAnalysisFailureNotificationBuilder {
+  private final Project project;
+  private final Task task;
+  private final String errorMessage;
+
+  public ReportAnalysisFailureNotificationBuilder(Project project, Task task, @Nullable String errorMessage) {
+    this.project = requireNonNull(project, "project can't be null");
+    this.task = requireNonNull(task, "task can't be null");
+    this.errorMessage = errorMessage;
+  }
+
+  public Project getProject() {
+    return project;
+  }
+
+  public Task getTask() {
+    return task;
+  }
+
+  @CheckForNull
+  public String getErrorMessage() {
+    return errorMessage;
+  }
+
+  public static final class Project {
+    private final String uuid;
+    private final String key;
+    private final String name;
+    private final String branchName;
+
+    public Project(String uuid, String key, String name, @Nullable String branchName) {
+      this.uuid = requireNonNull(uuid, "uuid can't be null");
+      this.key = requireNonNull(key, "key can't be null");
+      this.name = requireNonNull(name, "name can't be null");
+      this.branchName = branchName;
+    }
+
+    public String getUuid() {
+      return uuid;
+    }
+
+    public String getKey() {
+      return key;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    @CheckForNull
+    public String getBranchName() {
+      return branchName;
+    }
+  }
+
+  public static final class Task {
+    private final String uuid;
+    private final long createdAt;
+    private final long failedAt;
+
+    public Task(String uuid, long createdAt, long failedAt) {
+      this.uuid = requireNonNull(uuid, "uuid can't be null");
+      this.createdAt = createdAt;
+      this.failedAt = failedAt;
+    }
+
+    public String getUuid() {
+      return uuid;
+    }
+
+    public long getCreatedAt() {
+      return createdAt;
+    }
+
+    public long getFailedAt() {
+      return failedAt;
+    }
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationDispatcher.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationDispatcher.java
deleted file mode 100644 (file)
index 3e59512..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.ce.task.projectanalysis.notification;
-
-import com.google.common.collect.Multimap;
-import java.util.Collection;
-import java.util.Map;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.api.web.UserRole;
-import org.sonar.server.notification.NotificationDispatcher;
-import org.sonar.server.notification.NotificationDispatcherMetadata;
-import org.sonar.server.notification.NotificationManager;
-import org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject;
-
-public class ReportAnalysisFailureNotificationDispatcher extends NotificationDispatcher {
-
-  public static final String KEY = "CeReportTaskFailure";
-  private static final SubscriberPermissionsOnProject REQUIRED_SUBSCRIBER_PERMISSIONS = new SubscriberPermissionsOnProject(UserRole.ADMIN, UserRole.USER);
-
-  private final NotificationManager manager;
-
-  public ReportAnalysisFailureNotificationDispatcher(NotificationManager manager) {
-    super(ReportAnalysisFailureNotification.TYPE);
-    this.manager = manager;
-  }
-
-  public static NotificationDispatcherMetadata newMetadata() {
-    return NotificationDispatcherMetadata.create(KEY)
-      .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
-      .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
-  }
-
-  @Override
-  public String getKey() {
-    return KEY;
-  }
-
-  @Override
-  public void dispatch(Notification notification, Context context) {
-    String projectKey = notification.getFieldValue("project.key");
-    Multimap<String, NotificationChannel> subscribedRecipients = manager
-      .findSubscribedRecipientsForDispatcher(this, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS);
-
-    for (Map.Entry<String, Collection<NotificationChannel>> channelsByRecipients : subscribedRecipients.asMap().entrySet()) {
-      String userLogin = channelsByRecipients.getKey();
-      for (NotificationChannel channel : channelsByRecipients.getValue()) {
-        context.addUser(userLogin, channel);
-      }
-    }
-  }
-}
index 70e1b0bbe4395898b7304624706fa7c04b399efd..bf2967aba5950019585178d3d402dbdef344fef7 100644 (file)
@@ -42,11 +42,11 @@ public class ReportAnalysisFailureNotificationEmailTemplate extends EmailTemplat
 
   @Override
   public EmailMessage format(Notification notification) {
-    if (!ReportAnalysisFailureNotification.TYPE.equals(notification.getType())) {
+    if (!(notification instanceof ReportAnalysisFailureNotification)) {
       return null;
     }
 
-    ReportAnalysisFailureNotification taskFailureNotification = serializer.fromNotification(notification);
+    ReportAnalysisFailureNotificationBuilder taskFailureNotification = serializer.fromNotification((ReportAnalysisFailureNotification) notification);
     String projectUuid = taskFailureNotification.getProject().getUuid();
     String projectFullName = computeProjectFullName(taskFailureNotification.getProject());
 
@@ -56,7 +56,7 @@ public class ReportAnalysisFailureNotificationEmailTemplate extends EmailTemplat
       .setMessage(message(projectFullName, taskFailureNotification));
   }
 
-  private static String computeProjectFullName(ReportAnalysisFailureNotification.Project project) {
+  private static String computeProjectFullName(ReportAnalysisFailureNotificationBuilder.Project project) {
     String branchName = project.getBranchName();
     if (branchName != null) {
       return String.format("%s (%s)", project.getName(), branchName);
@@ -68,9 +68,9 @@ public class ReportAnalysisFailureNotificationEmailTemplate extends EmailTemplat
     return String.format("%s: Background task in failure", projectFullName);
   }
 
-  private String message(String projectFullName, ReportAnalysisFailureNotification taskFailureNotification) {
-    ReportAnalysisFailureNotification.Project project = taskFailureNotification.getProject();
-    ReportAnalysisFailureNotification.Task task = taskFailureNotification.getTask();
+  private String message(String projectFullName, ReportAnalysisFailureNotificationBuilder taskFailureNotification) {
+    ReportAnalysisFailureNotificationBuilder.Project project = taskFailureNotification.getProject();
+    ReportAnalysisFailureNotificationBuilder.Task task = taskFailureNotification.getTask();
 
     StringBuilder res = new StringBuilder();
     res.append("Project:").append(TAB).append(projectFullName).append(LINE_RETURN);
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationHandler.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationHandler.java
new file mode 100644 (file)
index 0000000..c951b10
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.ce.task.projectanalysis.notification;
+
+import com.google.common.collect.Multimap;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Stream;
+import org.sonar.api.web.UserRole;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.NotificationHandler;
+import org.sonar.server.notification.NotificationManager;
+import org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject;
+import org.sonar.server.notification.email.EmailNotificationChannel;
+import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest;
+
+import static org.sonar.core.util.stream.MoreCollectors.index;
+import static org.sonar.core.util.stream.MoreCollectors.toSet;
+
+public class ReportAnalysisFailureNotificationHandler implements NotificationHandler<ReportAnalysisFailureNotification> {
+  private static final String KEY = "CeReportTaskFailure";
+  private static final NotificationDispatcherMetadata METADATA = NotificationDispatcherMetadata.create(KEY)
+    .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+    .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+  private static final SubscriberPermissionsOnProject REQUIRED_SUBSCRIBER_PERMISSIONS = new SubscriberPermissionsOnProject(UserRole.ADMIN, UserRole.USER);
+
+  private final NotificationManager notificationManager;
+  private final EmailNotificationChannel emailNotificationChannel;
+
+  public ReportAnalysisFailureNotificationHandler(NotificationManager notificationManager, EmailNotificationChannel emailNotificationChannel) {
+    this.notificationManager = notificationManager;
+    this.emailNotificationChannel = emailNotificationChannel;
+  }
+
+  @Override
+  public Optional<NotificationDispatcherMetadata> getMetadata() {
+    return Optional.of(METADATA);
+  }
+
+  public static NotificationDispatcherMetadata newMetadata() {
+    return METADATA;
+  }
+
+  @Override
+  public Class<ReportAnalysisFailureNotification> getNotificationClass() {
+    return ReportAnalysisFailureNotification.class;
+  }
+
+  @Override
+  public int deliver(Collection<ReportAnalysisFailureNotification> notifications) {
+    if (notifications.isEmpty() || !emailNotificationChannel.isActivated()) {
+      return 0;
+    }
+
+    Multimap<String, ReportAnalysisFailureNotification> notificationsByProjectKey = notifications.stream()
+      .filter(t -> t.getProjectKey() != null)
+      .collect(index(ReportAnalysisFailureNotification::getProjectKey));
+    if (notificationsByProjectKey.isEmpty()) {
+      return 0;
+    }
+
+    Set<EmailDeliveryRequest> deliveryRequests = notificationsByProjectKey.asMap().entrySet()
+      .stream()
+      .flatMap(e -> toEmailDeliveryRequests(e.getKey(), e.getValue()))
+      .collect(toSet(notifications.size()));
+    if (deliveryRequests.isEmpty()) {
+      return 0;
+    }
+    return emailNotificationChannel.deliver(deliveryRequests);
+  }
+
+  private Stream<? extends EmailDeliveryRequest> toEmailDeliveryRequests(String projectKey, Collection<ReportAnalysisFailureNotification> notifications) {
+    return notificationManager.findSubscribedEmailRecipients(KEY, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS)
+      .stream()
+      .flatMap(emailRecipient -> notifications.stream()
+        .map(notification -> new EmailDeliveryRequest(emailRecipient.getEmail(), notification)));
+  }
+}
index 9cc999bbe8966825498ae36bf7d23d135b5ef098..88986e5e16731259a090dffa8165a5f8ab8e2df6 100644 (file)
@@ -25,8 +25,8 @@ public class ReportAnalysisFailureNotificationModule extends Module {
   @Override
   protected void configureModule() {
     add(
-      ReportAnalysisFailureNotificationDispatcher.class,
-      ReportAnalysisFailureNotificationDispatcher.newMetadata(),
+      ReportAnalysisFailureNotificationHandler.class,
+      ReportAnalysisFailureNotificationHandler.newMetadata(),
       ReportAnalysisFailureNotificationSerializerImpl.class,
       ReportAnalysisFailureNotificationEmailTemplate.class);
   }
index 167e44b7b30445e331c46cd056d10d71fda447d4..f7a946a7bf0590c2f5198e4b0b7de1bda860b212 100644 (file)
  */
 package org.sonar.ce.task.projectanalysis.notification;
 
-import org.sonar.api.notifications.Notification;
-
 public interface ReportAnalysisFailureNotificationSerializer {
-  Notification toNotification(ReportAnalysisFailureNotification reportAnalysisFailureNotification);
+  ReportAnalysisFailureNotification toNotification(ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder);
 
-  ReportAnalysisFailureNotification fromNotification(Notification notification);
+  ReportAnalysisFailureNotificationBuilder fromNotification(ReportAnalysisFailureNotification notification);
 }
index 35dde78271a6aaec62ed6a29b8c16b285ca4e839..0489ccf71beb1a35560563963eb99a91f0cb1f03 100644 (file)
@@ -19,8 +19,6 @@
  */
 package org.sonar.ce.task.projectanalysis.notification;
 
-import org.sonar.api.notifications.Notification;
-
 import static java.lang.String.valueOf;
 
 public class ReportAnalysisFailureNotificationSerializerImpl implements ReportAnalysisFailureNotificationSerializer {
@@ -34,27 +32,29 @@ public class ReportAnalysisFailureNotificationSerializerImpl implements ReportAn
   private static final String FIELD_ERROR_MESSAGE = "error.message";
 
   @Override
-  public Notification toNotification(ReportAnalysisFailureNotification reportAnalysisFailureNotification) {
-    return new Notification(ReportAnalysisFailureNotification.TYPE)
-      .setFieldValue(FIELD_PROJECT_UUID, reportAnalysisFailureNotification.getProject().getUuid())
-      .setFieldValue(FIELD_PROJECT_KEY, reportAnalysisFailureNotification.getProject().getKey())
-      .setFieldValue(FIELD_PROJECT_NAME, reportAnalysisFailureNotification.getProject().getName())
-      .setFieldValue(FIELD_PROJECT_BRANCH, reportAnalysisFailureNotification.getProject().getBranchName())
-      .setFieldValue(FIELD_TASK_UUID, reportAnalysisFailureNotification.getTask().getUuid())
-      .setFieldValue(FIELD_TASK_CREATED_AT, valueOf(reportAnalysisFailureNotification.getTask().getCreatedAt()))
-      .setFieldValue(FIELD_TASK_FAILED_AT, valueOf(reportAnalysisFailureNotification.getTask().getFailedAt()))
-      .setFieldValue(FIELD_ERROR_MESSAGE, reportAnalysisFailureNotification.getErrorMessage());
+  public ReportAnalysisFailureNotification toNotification(ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder) {
+    ReportAnalysisFailureNotification notification = new ReportAnalysisFailureNotification();
+    notification
+      .setFieldValue(FIELD_PROJECT_UUID, reportAnalysisFailureNotificationBuilder.getProject().getUuid())
+      .setFieldValue(FIELD_PROJECT_KEY, reportAnalysisFailureNotificationBuilder.getProject().getKey())
+      .setFieldValue(FIELD_PROJECT_NAME, reportAnalysisFailureNotificationBuilder.getProject().getName())
+      .setFieldValue(FIELD_PROJECT_BRANCH, reportAnalysisFailureNotificationBuilder.getProject().getBranchName())
+      .setFieldValue(FIELD_TASK_UUID, reportAnalysisFailureNotificationBuilder.getTask().getUuid())
+      .setFieldValue(FIELD_TASK_CREATED_AT, valueOf(reportAnalysisFailureNotificationBuilder.getTask().getCreatedAt()))
+      .setFieldValue(FIELD_TASK_FAILED_AT, valueOf(reportAnalysisFailureNotificationBuilder.getTask().getFailedAt()))
+      .setFieldValue(FIELD_ERROR_MESSAGE, reportAnalysisFailureNotificationBuilder.getErrorMessage());
+    return notification;
   }
 
   @Override
-  public ReportAnalysisFailureNotification fromNotification(Notification notification) {
-    return new ReportAnalysisFailureNotification(
-      new ReportAnalysisFailureNotification.Project(
+  public ReportAnalysisFailureNotificationBuilder fromNotification(ReportAnalysisFailureNotification notification) {
+    return new ReportAnalysisFailureNotificationBuilder(
+      new ReportAnalysisFailureNotificationBuilder.Project(
         notification.getFieldValue(FIELD_PROJECT_UUID),
         notification.getFieldValue(FIELD_PROJECT_KEY),
         notification.getFieldValue(FIELD_PROJECT_NAME),
         notification.getFieldValue(FIELD_PROJECT_BRANCH)),
-      new ReportAnalysisFailureNotification.Task(
+      new ReportAnalysisFailureNotificationBuilder.Task(
         notification.getFieldValue(FIELD_TASK_UUID),
         Long.valueOf(notification.getFieldValue(FIELD_TASK_CREATED_AT)),
         Long.valueOf(notification.getFieldValue(FIELD_TASK_FAILED_AT))),
index 325c9b1813ca7a7b554f6b03190253797436069f..79fde349e69fb0d739dc0a764f35ad6de09c700b 100644 (file)
@@ -34,6 +34,7 @@ import java.util.Set;
 import java.util.function.Predicate;
 import javax.annotation.CheckForNull;
 import org.sonar.api.issue.Issue;
+import org.sonar.api.notifications.Notification;
 import org.sonar.api.rules.RuleType;
 import org.sonar.api.utils.Duration;
 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
@@ -76,7 +77,7 @@ public class SendIssueNotificationsStep implements ComputationStep {
   /**
    * Types of the notifications sent by this step
    */
-  static final Set<String> NOTIF_TYPES = ImmutableSet.of(IssueChangeNotification.TYPE, NewIssuesNotification.TYPE, MyNewIssuesNotification.MY_NEW_ISSUES_NOTIF_TYPE);
+  static final Set<Class<? extends Notification>> NOTIF_TYPES = ImmutableSet.of(NewIssuesNotification.class, MyNewIssuesNotification.class, IssueChangeNotification.class);
 
   private final IssueCache issueCache;
   private final RuleRepository rules;
@@ -104,10 +105,9 @@ public class SendIssueNotificationsStep implements ComputationStep {
   public void execute(ComputationStep.Context context) {
     Component project = treeRootHolder.getRoot();
     NotificationStatistics notificationStatistics = new NotificationStatistics();
-    // FIXME do we still need this fail fast?
-    // if (service.hasProjectSubscribersForTypes(project.getUuid(), NOTIF_TYPES)) {
-    doExecute(notificationStatistics, project);
-    // }
+    if (service.hasProjectSubscribersForTypes(project.getUuid(), NOTIF_TYPES)) {
+      doExecute(notificationStatistics, project);
+    }
     notificationStatistics.dumpTo(context);
   }
 
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationHandlerTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/notification/ReportAnalysisFailureNotificationHandlerTest.java
new file mode 100644 (file)
index 0000000..b8dbdc8
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.ce.task.projectanalysis.notification;
+
+import java.util.Collections;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.api.web.UserRole;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.NotificationManager;
+import org.sonar.server.notification.NotificationManager.EmailRecipient;
+import org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject;
+import org.sonar.server.notification.email.EmailNotificationChannel;
+import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest;
+
+import static java.util.Collections.emptySet;
+import static java.util.stream.Collectors.toSet;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
+import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION;
+
+public class ReportAnalysisFailureNotificationHandlerTest {
+  private static final String REPORT_FAILURE_DISPATCHER_KEY = "CeReportTaskFailure";
+  private static final SubscriberPermissionsOnProject REQUIRED_SUBSCRIBER_PERMISSIONS = new SubscriberPermissionsOnProject(UserRole.ADMIN, UserRole.USER);
+  private NotificationManager notificationManager = mock(NotificationManager.class);
+  private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class);
+  private ReportAnalysisFailureNotificationHandler underTest = new ReportAnalysisFailureNotificationHandler(notificationManager, emailNotificationChannel);
+
+  @Test
+  public void getMetadata_returns_same_instance_as_static_method() {
+    assertThat(underTest.getMetadata().get()).isSameAs(ReportAnalysisFailureNotificationHandler.newMetadata());
+  }
+
+  @Test
+  public void verify_reportFailures_notification_dispatcher_key() {
+    NotificationDispatcherMetadata metadata = ReportAnalysisFailureNotificationHandler.newMetadata();
+
+    assertThat(metadata.getDispatcherKey()).isEqualTo(REPORT_FAILURE_DISPATCHER_KEY);
+  }
+
+  @Test
+  public void reportFailures_notification_is_enable_at_global_level() {
+    NotificationDispatcherMetadata metadata = ReportAnalysisFailureNotificationHandler.newMetadata();
+
+    assertThat(metadata.getProperty(GLOBAL_NOTIFICATION)).isEqualTo("true");
+  }
+
+  @Test
+  public void reportFailures_notification_is_enable_at_project_level() {
+    NotificationDispatcherMetadata metadata = ReportAnalysisFailureNotificationHandler.newMetadata();
+
+    assertThat(metadata.getProperty(PER_PROJECT_NOTIFICATION)).isEqualTo("true");
+  }
+
+  @Test
+  public void getNotificationClass_is_ReportAnalysisFailureNotification() {
+    assertThat(underTest.getNotificationClass()).isEqualTo(ReportAnalysisFailureNotification.class);
+  }
+
+  @Test
+  public void deliver_has_no_effect_if_notifications_is_empty() {
+    when(emailNotificationChannel.isActivated()).thenReturn(true);
+    int deliver = underTest.deliver(Collections.emptyList());
+
+    assertThat(deliver).isZero();
+    verifyZeroInteractions(notificationManager, emailNotificationChannel);
+  }
+
+  @Test
+  public void deliver_has_no_effect_if_emailNotificationChannel_is_disabled() {
+    when(emailNotificationChannel.isActivated()).thenReturn(false);
+    Set<ReportAnalysisFailureNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10))
+      .mapToObj(i -> mock(ReportAnalysisFailureNotification.class))
+      .collect(toSet());
+
+    int deliver = underTest.deliver(notifications);
+
+    assertThat(deliver).isZero();
+    verifyZeroInteractions(notificationManager);
+    verify(emailNotificationChannel).isActivated();
+    verifyNoMoreInteractions(emailNotificationChannel);
+    notifications.forEach(Mockito::verifyZeroInteractions);
+  }
+
+  @Test
+  public void deliver_has_no_effect_if_no_notification_has_projectKey() {
+    when(emailNotificationChannel.isActivated()).thenReturn(true);
+    Set<ReportAnalysisFailureNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10))
+      .mapToObj(i -> newNotification(null))
+      .collect(toSet());
+
+    int deliver = underTest.deliver(notifications);
+
+    assertThat(deliver).isZero();
+    verifyZeroInteractions(notificationManager);
+    verify(emailNotificationChannel).isActivated();
+    verifyNoMoreInteractions(emailNotificationChannel);
+    notifications.forEach(notification -> {
+      verify(notification).getProjectKey();
+      verifyNoMoreInteractions(notification);
+    });
+  }
+
+  @Test
+  public void deliver_has_no_effect_if_no_notification_has_subscribed_recipients_to_ReportFailure_notifications() {
+    String projectKey = randomAlphabetic(12);
+    ReportAnalysisFailureNotification notification = newNotification(projectKey);
+    when(emailNotificationChannel.isActivated()).thenReturn(true);
+    when(notificationManager.findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS))
+      .thenReturn(emptySet());
+
+    int deliver = underTest.deliver(Collections.singleton(notification));
+
+    assertThat(deliver).isZero();
+    verify(notificationManager).findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS);
+    verifyNoMoreInteractions(notificationManager);
+    verify(emailNotificationChannel).isActivated();
+    verifyNoMoreInteractions(emailNotificationChannel);
+  }
+
+  @Test
+  public void deliver_ignores_notification_without_projectKey() {
+    String projectKey = randomAlphabetic(10);
+    Set<ReportAnalysisFailureNotification> withProjectKey = IntStream.range(0, 1 + new Random().nextInt(5))
+      .mapToObj(i -> newNotification(projectKey))
+      .collect(toSet());
+    Set<ReportAnalysisFailureNotification> noProjectKey = IntStream.range(0, 1 + new Random().nextInt(5))
+      .mapToObj(i -> newNotification(null))
+      .collect(toSet());
+    Set<EmailRecipient> emailRecipients = IntStream.range(0, 1 + new Random().nextInt(10))
+      .mapToObj(i -> "user_" + i)
+      .map(login -> new EmailRecipient(login, emailOf(login)))
+      .collect(toSet());
+    Set<EmailDeliveryRequest> expectedRequests = emailRecipients.stream()
+      .flatMap(emailRecipient -> withProjectKey.stream().map(notif -> new EmailDeliveryRequest(emailRecipient.getEmail(), notif)))
+      .collect(toSet());
+    when(emailNotificationChannel.isActivated()).thenReturn(true);
+    when(notificationManager.findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS))
+      .thenReturn(emailRecipients);
+
+    Set<ReportAnalysisFailureNotification> notifications = Stream.of(withProjectKey.stream(), noProjectKey.stream())
+      .flatMap(t -> t)
+      .collect(toSet());
+    int deliver = underTest.deliver(notifications);
+
+    assertThat(deliver).isZero();
+    verify(notificationManager).findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey, REQUIRED_SUBSCRIBER_PERMISSIONS);
+    verifyNoMoreInteractions(notificationManager);
+    verify(emailNotificationChannel).isActivated();
+    verify(emailNotificationChannel).deliver(expectedRequests);
+    verifyNoMoreInteractions(emailNotificationChannel);
+  }
+
+  @Test
+  public void deliver_checks_by_projectKey_if_notifications_have_subscribed_assignee_to_ReportFailure_notifications() {
+    String projectKey1 = randomAlphabetic(10);
+    String projectKey2 = randomAlphabetic(11);
+    Set<ReportAnalysisFailureNotification> notifications1 = randomSetOfNotifications(projectKey1);
+    Set<ReportAnalysisFailureNotification> notifications2 = randomSetOfNotifications(projectKey2);
+    when(emailNotificationChannel.isActivated()).thenReturn(true);
+
+    Set<EmailRecipient> emailRecipients1 = IntStream.range(0, 1 + new Random().nextInt(10))
+      .mapToObj(i -> "user1_" + i)
+      .map(login -> new EmailRecipient(login, emailOf(login)))
+      .collect(toSet());
+    Set<EmailRecipient> emailRecipients2 = IntStream.range(0, 1 + new Random().nextInt(10))
+      .mapToObj(i -> "user2_" + i)
+      .map(login -> new EmailRecipient(login, emailOf(login)))
+      .collect(toSet());
+    when(notificationManager.findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey1, REQUIRED_SUBSCRIBER_PERMISSIONS))
+      .thenReturn(emailRecipients1);
+    when(notificationManager.findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey2, REQUIRED_SUBSCRIBER_PERMISSIONS))
+      .thenReturn(emailRecipients2);
+    Set<EmailDeliveryRequest> expectedRequests = Stream.concat(
+      emailRecipients1.stream()
+        .flatMap(emailRecipient -> notifications1.stream().map(notif -> new EmailDeliveryRequest(emailRecipient.getEmail(), notif))),
+      emailRecipients2.stream()
+        .flatMap(emailRecipient -> notifications2.stream().map(notif -> new EmailDeliveryRequest(emailRecipient.getEmail(), notif))))
+      .collect(toSet());
+
+    int deliver = underTest.deliver(Stream.concat(notifications1.stream(), notifications2.stream()).collect(toSet()));
+
+    assertThat(deliver).isZero();
+    verify(notificationManager).findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey1, REQUIRED_SUBSCRIBER_PERMISSIONS);
+    verify(notificationManager).findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey2, REQUIRED_SUBSCRIBER_PERMISSIONS);
+    verifyNoMoreInteractions(notificationManager);
+    verify(emailNotificationChannel).isActivated();
+    verify(emailNotificationChannel).deliver(expectedRequests);
+    verifyNoMoreInteractions(emailNotificationChannel);
+  }
+
+  @Test
+  public void deliver_send_notifications_to_all_subscribers_of_all_projects() {
+    String projectKey1 = randomAlphabetic(10);
+    String projectKey2 = randomAlphabetic(11);
+    Set<ReportAnalysisFailureNotification> notifications1 = randomSetOfNotifications(projectKey1);
+    Set<ReportAnalysisFailureNotification> notifications2 = randomSetOfNotifications(projectKey2);
+    when(emailNotificationChannel.isActivated()).thenReturn(true);
+    when(notificationManager.findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey1, REQUIRED_SUBSCRIBER_PERMISSIONS))
+      .thenReturn(emptySet());
+    when(notificationManager.findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey2, REQUIRED_SUBSCRIBER_PERMISSIONS))
+      .thenReturn(emptySet());
+
+    int deliver = underTest.deliver(Stream.concat(notifications1.stream(), notifications2.stream()).collect(toSet()));
+
+    assertThat(deliver).isZero();
+    verify(notificationManager).findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey1, REQUIRED_SUBSCRIBER_PERMISSIONS);
+    verify(notificationManager).findSubscribedEmailRecipients(REPORT_FAILURE_DISPATCHER_KEY, projectKey2, REQUIRED_SUBSCRIBER_PERMISSIONS);
+    verifyNoMoreInteractions(notificationManager);
+    verify(emailNotificationChannel).isActivated();
+    verifyNoMoreInteractions(emailNotificationChannel);
+  }
+
+  private static Set<ReportAnalysisFailureNotification> randomSetOfNotifications(@Nullable String projectKey) {
+    return IntStream.range(0, 1 + new Random().nextInt(5))
+      .mapToObj(i -> newNotification(projectKey))
+      .collect(Collectors.toSet());
+  }
+
+  private static ReportAnalysisFailureNotification newNotification(@Nullable String projectKey) {
+    ReportAnalysisFailureNotification notification = mock(ReportAnalysisFailureNotification.class);
+    when(notification.getProjectKey()).thenReturn(projectKey);
+    return notification;
+  }
+  private static String emailOf(String assignee1) {
+    return assignee1 + "@house";
+  }
+
+}
index 31723ca3f39f44d4a44a0b1ff9df0ec355095a98..1e47682a4e9b3156b1fffa67269f9d9439929e2a 100644 (file)
@@ -26,6 +26,7 @@ import org.sonar.api.utils.System2;
 import org.sonar.ce.task.CeTask;
 import org.sonar.ce.task.CeTaskResult;
 import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotification;
+import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationBuilder;
 import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationSerializer;
 import org.sonar.ce.taskprocessor.CeWorker;
 import org.sonar.db.DbClient;
@@ -69,14 +70,18 @@ public class ReportAnalysisFailureNotificationExecutionListener implements CeWor
       return;
     }
 
-    if (notificationService.hasProjectSubscribersForTypes(projectUuid, singleton(ReportAnalysisFailureNotification.TYPE))) {
+    if (notificationService.hasProjectSubscribersForTypes(projectUuid, singleton(ReportAnalysisFailureNotification.class))) {
       try (DbSession dbSession = dbClient.openSession(false)) {
         ComponentDto projectDto = dbClient.componentDao().selectOrFailByUuid(dbSession, projectUuid);
         checkScopeAndQualifier(projectDto);
         CeActivityDto ceActivityDto = dbClient.ceActivityDao().selectByUuid(dbSession, ceTask.getUuid())
           .orElseThrow(() -> new RowNotFoundException(format("CeActivity with uuid '%s' not found", ceTask.getUuid())));
-        ReportAnalysisFailureNotification taskFailureNotification = buildNotification(ceActivityDto, projectDto, error);
-        notificationService.deliver(taskFailureNotificationSerializer.toNotification(taskFailureNotification));
+        ReportAnalysisFailureNotificationBuilder taskFailureNotification = buildNotification(ceActivityDto, projectDto, error);
+        ReportAnalysisFailureNotification notification = taskFailureNotificationSerializer.toNotification(taskFailureNotification);
+        notificationService.deliverEmails(singleton(notification));
+
+        // compatibility with old API
+        notificationService.deliver(notification);
       }
     }
   }
@@ -92,15 +97,15 @@ public class ReportAnalysisFailureNotificationExecutionListener implements CeWor
       "Component %s must be a project (scope=%s, qualifier=%s)", projectDto.uuid(), scope, qualifier);
   }
 
-  private ReportAnalysisFailureNotification buildNotification(CeActivityDto ceActivityDto, ComponentDto projectDto, @Nullable Throwable error) {
+  private ReportAnalysisFailureNotificationBuilder buildNotification(CeActivityDto ceActivityDto, ComponentDto projectDto, @Nullable Throwable error) {
     Long executedAt = ceActivityDto.getExecutedAt();
-    return new ReportAnalysisFailureNotification(
-      new ReportAnalysisFailureNotification.Project(
+    return new ReportAnalysisFailureNotificationBuilder(
+      new ReportAnalysisFailureNotificationBuilder.Project(
         projectDto.uuid(),
         projectDto.getKey(),
         projectDto.name(),
         projectDto.getBranch()),
-      new ReportAnalysisFailureNotification.Task(
+      new ReportAnalysisFailureNotificationBuilder.Task(
         ceActivityDto.getUuid(),
         ceActivityDto.getSubmittedAt(),
         executedAt == null ? system2.now() : executedAt),
index 3de40a339b088c8117c4b84bc5f2acd0c486bd72..014198f1e21b40f9fcba1690d0b0faafa31f6524 100644 (file)
@@ -33,6 +33,7 @@ import org.sonar.api.utils.System2;
 import org.sonar.ce.task.CeTask;
 import org.sonar.ce.task.CeTaskResult;
 import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotification;
+import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationBuilder;
 import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationSerializer;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbTester;
@@ -123,7 +124,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
     String componentUuid = randomAlphanumeric(6);
     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
-    when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.TYPE)))
+    when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
       .thenReturn(false);
 
     fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
@@ -136,7 +137,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
     String componentUuid = randomAlphanumeric(6);
     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
-    when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.TYPE)))
+    when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
       .thenReturn(true);
 
     expectedException.expect(RowNotFoundException.class);
@@ -162,7 +163,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
       .forEach(component -> {
         try {
           when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(component.uuid(), null, null)));
-          when(notificationService.hasProjectSubscribersForTypes(component.uuid(), singleton(ReportAnalysisFailureNotification.TYPE)))
+          when(notificationService.hasProjectSubscribersForTypes(component.uuid(), singleton(ReportAnalysisFailureNotification.class)))
             .thenReturn(true);
 
           underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
@@ -182,7 +183,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
     when(ceTaskMock.getUuid()).thenReturn(taskUuid);
     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
-    when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.TYPE)))
+    when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
       .thenReturn(true);
     dbTester.components().insertPrivateProject(s -> s.setUuid(componentUuid));
 
@@ -202,17 +203,17 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
 
     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
 
-    ArgumentCaptor<ReportAnalysisFailureNotification> notificationCaptor = verifyAndCaptureSerializedNotification();
+    ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
     verify(notificationService).deliver(same(notificationMock));
 
-    ReportAnalysisFailureNotification reportAnalysisFailureNotification = notificationCaptor.getValue();
+    ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
 
-    ReportAnalysisFailureNotification.Project notificationProject = reportAnalysisFailureNotification.getProject();
+    ReportAnalysisFailureNotificationBuilder.Project notificationProject = reportAnalysisFailureNotificationBuilder.getProject();
     assertThat(notificationProject.getName()).isEqualTo(project.name());
     assertThat(notificationProject.getKey()).isEqualTo(project.getKey());
     assertThat(notificationProject.getUuid()).isEqualTo(project.uuid());
     assertThat(notificationProject.getBranchName()).isEqualTo(project.getBranch());
-    ReportAnalysisFailureNotification.Task notificationTask = reportAnalysisFailureNotification.getTask();
+    ReportAnalysisFailureNotificationBuilder.Task notificationTask = reportAnalysisFailureNotificationBuilder.getTask();
     assertThat(notificationTask.getUuid()).isEqualTo(taskUuid);
     assertThat(notificationTask.getCreatedAt()).isEqualTo(createdAt);
     assertThat(notificationTask.getFailedAt()).isEqualTo(executedAt);
@@ -226,10 +227,10 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
 
     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
 
-    ArgumentCaptor<ReportAnalysisFailureNotification> notificationCaptor = verifyAndCaptureSerializedNotification();
+    ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
 
-    ReportAnalysisFailureNotification reportAnalysisFailureNotification = notificationCaptor.getValue();
-    assertThat(reportAnalysisFailureNotification.getErrorMessage()).isEqualTo(message);
+    ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
+    assertThat(reportAnalysisFailureNotificationBuilder.getErrorMessage()).isEqualTo(message);
   }
 
   @Test
@@ -241,10 +242,10 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, null);
 
     verify(notificationService).deliver(same(notificationMock));
-    ArgumentCaptor<ReportAnalysisFailureNotification> notificationCaptor = verifyAndCaptureSerializedNotification();
+    ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
 
-    ReportAnalysisFailureNotification reportAnalysisFailureNotification = notificationCaptor.getValue();
-    assertThat(reportAnalysisFailureNotification.getErrorMessage()).isNull();
+    ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
+    assertThat(reportAnalysisFailureNotificationBuilder.getErrorMessage()).isNull();
   }
 
   @Test
@@ -281,13 +282,13 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, null);
 
     verify(notificationService).deliver(same(notificationMock));
-    ArgumentCaptor<ReportAnalysisFailureNotification> notificationCaptor = verifyAndCaptureSerializedNotification();
+    ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
     assertThat(notificationCaptor.getValue().getTask().getFailedAt()).isEqualTo(now);
   }
 
-  private Notification mockSerializer() {
-    Notification notificationMock = mock(Notification.class);
-    when(serializer.toNotification(any(ReportAnalysisFailureNotification.class))).thenReturn(notificationMock);
+  private ReportAnalysisFailureNotification mockSerializer() {
+    ReportAnalysisFailureNotification notificationMock = mock(ReportAnalysisFailureNotification.class);
+    when(serializer.toNotification(any(ReportAnalysisFailureNotificationBuilder.class))).thenReturn(notificationMock);
     return notificationMock;
   }
 
@@ -296,7 +297,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(project.uuid(), null, null)));
     when(ceTaskMock.getUuid()).thenReturn(taskUuid);
-    when(notificationService.hasProjectSubscribersForTypes(project.uuid(), singleton(ReportAnalysisFailureNotification.TYPE)))
+    when(notificationService.hasProjectSubscribersForTypes(project.uuid(), singleton(ReportAnalysisFailureNotification.class)))
       .thenReturn(true);
     insertActivityDto(taskUuid, createdAt, executedAt, project);
     return project;
@@ -313,8 +314,8 @@ public class ReportAnalysisFailureNotificationExecutionListenerTest {
     dbTester.getSession().commit();
   }
 
-  private ArgumentCaptor<ReportAnalysisFailureNotification> verifyAndCaptureSerializedNotification() {
-    ArgumentCaptor<ReportAnalysisFailureNotification> notificationCaptor = ArgumentCaptor.forClass(ReportAnalysisFailureNotification.class);
+  private ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> verifyAndCaptureSerializedNotification() {
+    ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = ArgumentCaptor.forClass(ReportAnalysisFailureNotificationBuilder.class);
     verify(serializer).toNotification(notificationCaptor.capture());
     return notificationCaptor;
   }
index 805cc993faae2d822d4bb059076c39e3f8ea0b5a..465c8e4d9e2e1bdc2df527965eef0d1aae062b4d 100644 (file)
@@ -76,6 +76,10 @@ public class PropertiesDao implements Dao {
   }
 
   public boolean hasProjectNotificationSubscribersForDispatchers(String projectUuid, Collection<String> dispatcherKeys) {
+    if (dispatcherKeys.isEmpty()) {
+      return false;
+    }
+
     try (DbSession session = mybatis.openSession(false);
       Connection connection = session.getConnection();
       PreparedStatement pstmt = createStatement(projectUuid, dispatcherKeys, connection);
index 1d72f66ea34b500a3fc092765f43b1f1ca323dec..89125189aece301aa163fe01e287bdb0f389ef6d 100644 (file)
@@ -23,6 +23,7 @@ import com.google.common.collect.Multimap;
 import java.util.Collection;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
 import javax.annotation.CheckForNull;
@@ -41,6 +42,9 @@ import static org.sonar.server.notification.NotificationManager.SubscriberPermis
 public class ChangesOnMyIssueNotificationHandler implements NotificationHandler<IssueChangeNotification> {
 
   private static final String KEY = "ChangesOnMyIssue";
+  private static final NotificationDispatcherMetadata METADATA = NotificationDispatcherMetadata.create(KEY)
+    .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+    .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
 
   private final NotificationManager notificationManager;
   private final EmailNotificationChannel emailNotificationChannel;
@@ -50,10 +54,13 @@ public class ChangesOnMyIssueNotificationHandler implements NotificationHandler<
     this.emailNotificationChannel = emailNotificationChannel;
   }
 
+  @Override
+  public Optional<NotificationDispatcherMetadata> getMetadata() {
+    return Optional.of(METADATA);
+  }
+
   public static NotificationDispatcherMetadata newMetadata() {
-    return NotificationDispatcherMetadata.create(KEY)
-      .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
-      .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+    return METADATA;
   }
 
   @Override
index 71107fd71bda78926f8d06afdff681b49097a403..c6c742302115b4a402a792c68cce346ac2d34edd 100644 (file)
@@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Multimap;
 import java.util.Collection;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
 import org.sonar.api.issue.Issue;
@@ -33,6 +34,7 @@ import org.sonar.server.notification.NotificationManager.EmailRecipient;
 import org.sonar.server.notification.email.EmailNotificationChannel;
 import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest;
 
+import static java.util.Optional.of;
 import static org.sonar.core.util.stream.MoreCollectors.index;
 import static org.sonar.core.util.stream.MoreCollectors.toSet;
 import static org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject.ALL_MUST_HAVE_ROLE_USER;
@@ -40,6 +42,9 @@ import static org.sonar.server.notification.NotificationManager.SubscriberPermis
 public class DoNotFixNotificationHandler implements NotificationHandler<IssueChangeNotification> {
 
   public static final String KEY = "NewFalsePositiveIssue";
+  private static final NotificationDispatcherMetadata METADATA = NotificationDispatcherMetadata.create(KEY)
+    .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+    .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
 
   private static final Set<String> SUPPORTED_NEW_RESOLUTIONS = ImmutableSet.of(Issue.RESOLUTION_FALSE_POSITIVE, Issue.RESOLUTION_WONT_FIX);
 
@@ -51,10 +56,13 @@ public class DoNotFixNotificationHandler implements NotificationHandler<IssueCha
     this.emailNotificationChannel = emailNotificationChannel;
   }
 
+  @Override
+  public Optional<NotificationDispatcherMetadata> getMetadata() {
+    return of(METADATA);
+  }
+
   public static NotificationDispatcherMetadata newMetadata() {
-    return NotificationDispatcherMetadata.create(KEY)
-      .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
-      .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+    return METADATA;
   }
 
   @Override
index bbdad059b7357082ad53d164f9df1d1f38bbf8e0..7c8ce28a7375e6a6a6f1bf1bcaca1f80d8b2105e 100644 (file)
@@ -23,6 +23,7 @@ import com.google.common.collect.Multimap;
 import java.util.Collection;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
 import javax.annotation.CheckForNull;
@@ -39,6 +40,9 @@ import static org.sonar.server.notification.NotificationManager.SubscriberPermis
 
 public class MyNewIssuesNotificationHandler implements NotificationHandler<MyNewIssuesNotification> {
   public static final String KEY = "SQ-MyNewIssues";
+  private static final NotificationDispatcherMetadata METADATA = NotificationDispatcherMetadata.create(KEY)
+    .setProperty(GLOBAL_NOTIFICATION, String.valueOf(true))
+    .setProperty(PER_PROJECT_NOTIFICATION, String.valueOf(true));
 
   private final NotificationManager notificationManager;
   private final EmailNotificationChannel emailNotificationChannel;
@@ -48,10 +52,13 @@ public class MyNewIssuesNotificationHandler implements NotificationHandler<MyNew
     this.emailNotificationChannel = emailNotificationChannel;
   }
 
+  @Override
+  public Optional<NotificationDispatcherMetadata> getMetadata() {
+    return Optional.of(METADATA);
+  }
+
   public static NotificationDispatcherMetadata newMetadata() {
-    return NotificationDispatcherMetadata.create(KEY)
-      .setProperty(GLOBAL_NOTIFICATION, String.valueOf(true))
-      .setProperty(PER_PROJECT_NOTIFICATION, String.valueOf(true));
+    return METADATA;
   }
 
   @Override
index 0dd5ee1e0960683952a12d9b5db4beec0720a208..683993449a763f36cb41583f04376c9f94933f0d 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.issue.notification;
 
 import com.google.common.collect.Multimap;
 import java.util.Collection;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
 import org.sonar.server.notification.NotificationDispatcherMetadata;
@@ -36,6 +37,9 @@ import static org.sonar.server.notification.NotificationManager.SubscriberPermis
 public class NewIssuesNotificationHandler implements NotificationHandler<NewIssuesNotification> {
 
   public static final String KEY = "NewIssues";
+  private static final NotificationDispatcherMetadata METADATA = NotificationDispatcherMetadata.create(KEY)
+    .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+    .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
 
   private final NotificationManager notificationManager;
   private final EmailNotificationChannel emailNotificationChannel;
@@ -45,10 +49,13 @@ public class NewIssuesNotificationHandler implements NotificationHandler<NewIssu
     this.emailNotificationChannel = emailNotificationChannel;
   }
 
+  @Override
+  public Optional<NotificationDispatcherMetadata> getMetadata() {
+    return Optional.of(METADATA);
+  }
+
   public static NotificationDispatcherMetadata newMetadata() {
-    return NotificationDispatcherMetadata.create(KEY)
-      .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
-      .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+    return METADATA;
   }
 
   @Override
index 7898d0b9c3babbacc6985e818702ff0739e41cbc..d60ea79c5374ab538ac7d332fc893ec13fe39a50 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.notification;
 
 import java.util.Collection;
+import java.util.Optional;
 import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.notifications.Notification;
 import org.sonar.api.server.ServerSide;
@@ -27,6 +28,8 @@ import org.sonar.api.server.ServerSide;
 @ServerSide
 @ComputeEngineSide
 public interface NotificationHandler<T extends Notification> {
+  Optional<NotificationDispatcherMetadata> getMetadata();
+
   Class<T> getNotificationClass();
 
   int deliver(Collection<T> notifications);
index 84a4789b8a54043eaa58b906117c24a8e4774044..3d35b5fdacd5d03882b294627ffde0f537049ce6 100644 (file)
@@ -24,10 +24,10 @@ import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.SetMultimap;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
@@ -37,6 +37,7 @@ import org.sonar.api.notifications.NotificationChannel;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.DbClient;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -48,10 +49,10 @@ public class NotificationService {
   private static final Logger LOG = Loggers.get(NotificationService.class);
 
   private final List<NotificationDispatcher> dispatchers;
-  private final List<NotificationHandler> handlers;
+  private final List<NotificationHandler<? extends Notification>> handlers;
   private final DbClient dbClient;
 
-  public NotificationService(DbClient dbClient, NotificationDispatcher[] dispatchers, NotificationHandler[] handlers) {
+  public NotificationService(DbClient dbClient, NotificationDispatcher[] dispatchers, NotificationHandler<? extends Notification>[] handlers) {
     this.dbClient = dbClient;
     this.dispatchers = ImmutableList.copyOf(dispatchers);
     this.handlers = ImmutableList.copyOf(handlers);
@@ -153,13 +154,14 @@ public class NotificationService {
    * Returns true if at least one user is subscribed to at least one notification with given types.
    * Subscription can be global or on the specific project.
    */
-  public boolean hasProjectSubscribersForTypes(String projectUuid, Set<String> notificationTypes) {
-    Collection<String> dispatcherKeys = new ArrayList<>();
-    for (NotificationDispatcher dispatcher : dispatchers) {
-      if (notificationTypes.contains(dispatcher.getType())) {
-        dispatcherKeys.add(dispatcher.getKey());
-      }
-    }
+  public boolean hasProjectSubscribersForTypes(String projectUuid, Set<Class<? extends Notification>> notificationTypes) {
+    Set<String> dispatcherKeys = handlers.stream()
+      .filter(handler -> notificationTypes.stream().anyMatch(notificationType -> handler.getNotificationClass() == notificationType))
+      .map(NotificationHandler::getMetadata)
+      .filter(Optional::isPresent)
+      .map(Optional::get)
+      .map(NotificationDispatcherMetadata::getDispatcherKey)
+      .collect(MoreCollectors.toSet(notificationTypes.size()));
 
     return dbClient.propertiesDao().hasProjectNotificationSubscribersForDispatchers(projectUuid, dispatcherKeys);
   }
index 18402cff45e707e666f763b733f1eb3d1083531b..c5a347906bb487aee24e7f5439063d24a250fe94 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.qualitygate.notification;
 
 import com.google.common.collect.Multimap;
 import java.util.Collection;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
 import org.sonar.server.notification.NotificationDispatcherMetadata;
@@ -36,6 +37,9 @@ import static org.sonar.server.notification.NotificationManager.SubscriberPermis
 public class QGChangeNotificationHandler implements NotificationHandler<QGChangeNotification> {
 
   public static final String KEY = "NewAlerts";
+  private static final NotificationDispatcherMetadata METADATA = NotificationDispatcherMetadata.create(KEY)
+    .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+    .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
 
   private final NotificationManager notificationManager;
   private final EmailNotificationChannel emailNotificationChannel;
@@ -50,10 +54,13 @@ public class QGChangeNotificationHandler implements NotificationHandler<QGChange
     return QGChangeNotification.class;
   }
 
+  @Override
+  public Optional<NotificationDispatcherMetadata> getMetadata() {
+    return Optional.of(METADATA);
+  }
+
   public static NotificationDispatcherMetadata newMetadata() {
-    return NotificationDispatcherMetadata.create(KEY)
-      .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
-      .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+    return METADATA;
   }
 
   @Override
index d15afb6a26f4a879372a1d5b2ddc27d7977cc193..6857262e9d8ffb06f28fe1573660955afae340bf 100644 (file)
@@ -59,6 +59,11 @@ public class ChangesOnMyIssueNotificationHandlerTest {
   private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class);
   private ChangesOnMyIssueNotificationHandler underTest = new ChangesOnMyIssueNotificationHandler(notificationManager, emailNotificationChannel);
 
+  @Test
+  public void getMetadata_returns_same_instance_as_static_method() {
+    assertThat(underTest.getMetadata().get()).isSameAs(ChangesOnMyIssueNotificationHandler.newMetadata());
+  }
+
   @Test
   public void verify_changeOnMyIssues_notification_dispatcher_key() {
     NotificationDispatcherMetadata metadata = ChangesOnMyIssueNotificationHandler.newMetadata();
index 50e47496832cc4ddc9d52489bea8615d829657d9..abb8ad1ca82acaf7abc88be1451b87fd7499b36a 100644 (file)
@@ -57,6 +57,11 @@ public class DoNotFixNotificationHandlerTest {
   private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class);
   private DoNotFixNotificationHandler underTest = new DoNotFixNotificationHandler(notificationManager, emailNotificationChannel);
 
+  @Test
+  public void getMetadata_returns_same_instance_as_static_method() {
+    assertThat(underTest.getMetadata().get()).isSameAs(DoNotFixNotificationHandler.newMetadata());
+  }
+
   @Test
   public void verify_changeOnMyIssues_notification_dispatcher_key() {
     NotificationDispatcherMetadata metadata = DoNotFixNotificationHandler.newMetadata();
index 32b42cc545345ef2f1706c083228cb0c55d17629..6a85a4ddc9f27fbc517fa1c6bb0713a8978cf731 100644 (file)
@@ -55,6 +55,11 @@ public class MyNewIssuesNotificationHandlerTest {
 
   private MyNewIssuesNotificationHandler underTest = new MyNewIssuesNotificationHandler(notificationManager, emailNotificationChannel);
 
+  @Test
+  public void getMetadata_returns_same_instance_as_static_method() {
+    assertThat(underTest.getMetadata().get()).isSameAs(MyNewIssuesNotificationHandler.newMetadata());
+  }
+
   @Test
   public void verify_myNewIssues_notification_dispatcher_key() {
     NotificationDispatcherMetadata metadata = MyNewIssuesNotificationHandler.newMetadata();
index c6eb895419569d1ab21a1e9c5a1aabd835079631..8081632168209fa4a42b6081f46ea7f44ca322a3 100644 (file)
@@ -53,6 +53,11 @@ public class NewIssuesNotificationHandlerTest {
   private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class);
   private NewIssuesNotificationHandler underTest = new NewIssuesNotificationHandler(notificationManager, emailNotificationChannel);
 
+  @Test
+  public void getMetadata_returns_same_instance_as_static_method() {
+    assertThat(underTest.getMetadata().get()).isSameAs(NewIssuesNotificationHandler.newMetadata());
+  }
+
   @Test
   public void verify_myNewIssues_notification_dispatcher_key() {
     NotificationDispatcherMetadata metadata = NewIssuesNotificationHandler.newMetadata();
@@ -174,7 +179,7 @@ public class NewIssuesNotificationHandlerTest {
   }
 
   @Test
-  public void deliver_checks_by_projectKey_if_notifications_have_subscribed_assignee_to_MyNewIssue_notifications() {
+  public void deliver_checks_by_projectKey_if_notifications_have_subscribed_assignee_to_NewIssue_notifications() {
     String projectKey1 = randomAlphabetic(10);
     String projectKey2 = randomAlphabetic(11);
     Set<NewIssuesNotification> notifications1 = randomSetOfNotifications(projectKey1);
index de666b87c036522e971bd9feb1719f365ff7e349..7a78e3bee2b299026e71129d64f370085373ce07 100644 (file)
  */
 package org.sonar.server.notification;
 
+import com.google.common.collect.ImmutableSet;
 import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
 import java.util.Random;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.sonar.api.notifications.Notification;
 import org.sonar.db.DbClient;
+import org.sonar.db.property.PropertiesDao;
 
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.anyCollection;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
@@ -44,6 +50,12 @@ public class NotificationServiceTest {
   public ExpectedException expectedException = ExpectedException.none();
 
   private final DbClient dbClient = mock(DbClient.class);
+  private final PropertiesDao propertiesDao = mock(PropertiesDao.class);
+
+  @Before
+  public void wire_mocks() {
+    when(dbClient.propertiesDao()).thenReturn(propertiesDao);
+  }
 
   @Test
   public void deliverEmails_fails_with_IAE_if_type_of_collection_is_Notification() {
@@ -210,6 +222,116 @@ public class NotificationServiceTest {
     verify(handler1B, times(0)).deliver(anyCollection());
   }
 
+  @Test
+  public void hasProjectSubscribersForType_returns_false_if_there_are_no_handler() {
+    String projectUuid = randomAlphabetic(7);
+    NotificationService underTest = new NotificationService(dbClient);
+
+    assertThat(underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of(Notification1.class))).isFalse();
+    assertThat(underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of(Notification2.class))).isFalse();
+  }
+
+  @Test
+  public void hasProjectSubscribersForType_checks_property_for_each_dispatcher_key_supporting_Notification_type() {
+    String dispatcherKey1A = randomAlphabetic(5);
+    String dispatcherKey1B = randomAlphabetic(6);
+    String projectUuid = randomAlphabetic(7);
+    NotificationHandler1A handler1A = mock(NotificationHandler1A.class);
+    when(handler1A.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1A)));
+    NotificationHandler1B handler1B = mock(NotificationHandler1B.class);
+    when(handler1B.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1B)));
+    NotificationHandler2 handler2 = mock(NotificationHandler2.class);
+    when(handler2.getMetadata()).thenReturn(Optional.empty());
+    boolean expected = new Random().nextBoolean();
+    when(propertiesDao.hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey1A, dispatcherKey1B)))
+      .thenReturn(expected);
+    NotificationService underTest = new NotificationService(dbClient, new NotificationHandler[] {handler1A, handler1B, handler2});
+
+    boolean flag = underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of(Notification1.class));
+
+    verify(propertiesDao).hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey1A, dispatcherKey1B));
+    verifyNoMoreInteractions(propertiesDao);
+    assertThat(flag).isEqualTo(expected);
+
+    flag = underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of(Notification1.class, Notification2.class));
+
+    verify(propertiesDao, times(2)).hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey1A, dispatcherKey1B));
+    verifyNoMoreInteractions(propertiesDao);
+    assertThat(flag).isEqualTo(expected);
+  }
+
+  @Test
+  public void hasProjectSubscribersForType_checks_property_for_each_dispatcher_key_supporting_Notification_types() {
+    String dispatcherKey1A = randomAlphabetic(5);
+    String dispatcherKey1B = randomAlphabetic(6);
+    String dispatcherKey2 = randomAlphabetic(7);
+    String projectUuid = randomAlphabetic(8);
+    NotificationHandler1A handler1A = mock(NotificationHandler1A.class);
+    when(handler1A.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1A)));
+    NotificationHandler1B handler1B = mock(NotificationHandler1B.class);
+    when(handler1B.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1B)));
+    NotificationHandler2 handler2 = mock(NotificationHandler2.class);
+    when(handler2.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey2)));
+    boolean expected1 = new Random().nextBoolean();
+    when(propertiesDao.hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey1A, dispatcherKey1B, dispatcherKey2)))
+      .thenReturn(expected1);
+    boolean expected2 = new Random().nextBoolean();
+    when(propertiesDao.hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey2)))
+      .thenReturn(expected2);
+    NotificationService underTest = new NotificationService(dbClient, new NotificationHandler[] {handler1A, handler1B, handler2});
+
+    boolean flag = underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of(Notification1.class, Notification2.class));
+
+    verify(propertiesDao).hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey1A, dispatcherKey1B, dispatcherKey2));
+    verifyNoMoreInteractions(propertiesDao);
+    assertThat(flag).isEqualTo(expected1);
+
+    flag = underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of(Notification2.class));
+
+    verify(propertiesDao).hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey1A, dispatcherKey1B, dispatcherKey2));
+    verify(propertiesDao).hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of(dispatcherKey2));
+    verifyNoMoreInteractions(propertiesDao);
+    assertThat(flag).isEqualTo(expected2);
+  }
+
+  @Test
+  public void hasProjectSubscribersForType_returns_false_if_set_is_empty() {
+    String dispatcherKey1A = randomAlphabetic(5);
+    String dispatcherKey1B = randomAlphabetic(6);
+    String projectUuid = randomAlphabetic(7);
+    NotificationHandler1A handler1A = mock(NotificationHandler1A.class);
+    when(handler1A.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1A)));
+    NotificationHandler1B handler1B = mock(NotificationHandler1B.class);
+    when(handler1B.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1B)));
+    NotificationHandler2 handler2 = mock(NotificationHandler2.class);
+    when(handler2.getMetadata()).thenReturn(Optional.empty());
+    NotificationService underTest = new NotificationService(dbClient, new NotificationHandler[] {handler1A, handler1B, handler2});
+
+    boolean flag = underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of());
+
+    assertThat(flag).isFalse();
+    verify(propertiesDao).hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of());
+    verifyNoMoreInteractions(propertiesDao);
+  }
+
+  @Test
+  public void hasProjectSubscribersForType_returns_false_for_type_which_have_no_handler() {
+    String dispatcherKey1A = randomAlphabetic(5);
+    String dispatcherKey1B = randomAlphabetic(6);
+    String projectUuid = randomAlphabetic(7);
+    NotificationHandler1A handler1A = mock(NotificationHandler1A.class);
+    when(handler1A.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1A)));
+    NotificationHandler1B handler1B = mock(NotificationHandler1B.class);
+    when(handler1B.getMetadata()).thenReturn(Optional.of(NotificationDispatcherMetadata.create(dispatcherKey1B)));
+    NotificationService underTest = new NotificationService(dbClient, new NotificationHandler[] {handler1A, handler1B});
+
+    boolean flag = underTest.hasProjectSubscribersForTypes(projectUuid, ImmutableSet.of(Notification2.class));
+
+    assertThat(flag).isFalse();
+    verify(propertiesDao).hasProjectNotificationSubscribersForDispatchers(projectUuid, ImmutableSet.of());
+    verifyNoMoreInteractions(propertiesDao);
+  }
+
   private static final class Notification1 extends Notification {
 
     public Notification1() {
index a9faf4d26dbd5b223bd98a49d30dc07513c30fdc..e4e94c12a94d9c7a36051791837cc148c4fa365b 100644 (file)
@@ -51,6 +51,11 @@ public class QGChangeNotificationHandlerTest {
   private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class);
   private QGChangeNotificationHandler underTest = new QGChangeNotificationHandler(notificationManager, emailNotificationChannel);
 
+  @Test
+  public void getMetadata_returns_same_instance_as_static_method() {
+    assertThat(underTest.getMetadata().get()).isSameAs(QGChangeNotificationHandler.newMetadata());
+  }
+
   @Test
   public void verify_qgChange_notification_dispatcher_key() {
     NotificationDispatcherMetadata metadata = QGChangeNotificationHandler.newMetadata();
@@ -247,6 +252,4 @@ public class QGChangeNotificationHandlerTest {
     return assignee1 + "@giraffe";
   }
 
-
-
 }
index 686e5233ae4bad9344d16444b2d7a458f0932c81..5e566d48e39e4623019dcf3887abf44aea9c4981 100644 (file)
 package org.sonar.server.qualityprofile;
 
 import java.util.Collection;
+import java.util.Optional;
 import java.util.Set;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
 import org.sonar.server.notification.NotificationHandler;
 import org.sonar.server.notification.email.EmailNotificationChannel;
 import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest;
@@ -38,6 +40,11 @@ public class BuiltInQPChangeNotificationHandler implements NotificationHandler<B
     this.emailNotificationChannel = emailNotificationChannel;
   }
 
+  @Override
+  public Optional<NotificationDispatcherMetadata> getMetadata() {
+    return Optional.empty();
+  }
+
   @Override
   public Class<BuiltInQPChangeNotification> getNotificationClass() {
     return BuiltInQPChangeNotification.class;
index a0004540ed471041593bbe353cd9777a5cdea887..a8d6d52544da5184dbd1022f7fe5ccddc9c36a11 100644 (file)
@@ -228,22 +228,6 @@ public class NotificationMediumTest {
     underTest.stop();
   }
 
-  @Test
-  public void hasProjectSubscribersForType() {
-    setUpMocks();
-
-    PropertiesDao dao = mock(PropertiesDao.class);
-    when(dbClient.propertiesDao()).thenReturn(dao);
-
-    // no subscribers
-    when(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_UUID", Arrays.asList("CommentOnIssueAssignedToMe", "CommentOnIssueCreatedByMe"))).thenReturn(false);
-    assertThat(service.hasProjectSubscribersForTypes("PROJECT_UUID", Sets.newHashSet("issue-changes"))).isFalse();
-
-    // has subscribers on one dispatcher (among the two)
-    when(dao.hasProjectNotificationSubscribersForDispatchers("PROJECT_UUID", Arrays.asList("CommentOnIssueAssignedToMe", "CommentOnIssueCreatedByMe"))).thenReturn(true);
-    assertThat(service.hasProjectSubscribersForTypes("PROJECT_UUID", Sets.newHashSet("issue-changes"))).isTrue();
-  }
-
   private static Answer<Object> addUser(final String user, final NotificationChannel channel) {
     return addUser(user, new NotificationChannel[] {channel});
   }
index d2fc58f85de0a1d04d40affa5fcb3600413fe368..7691d0e86d1f919c175babd5765954326d64f2a5 100644 (file)
@@ -49,11 +49,16 @@ public class BuiltInQPChangeNotificationHandlerTest {
   private BuiltInQPChangeNotificationHandler underTest = new BuiltInQPChangeNotificationHandler(dbClient, emailNotificationChannel);
 
   @Before
-  public void wire_mocks()  {
+  public void wire_mocks() {
     when(dbClient.openSession(false)).thenReturn(dbSession);
     when(dbClient.authorizationDao()).thenReturn(authorizationDao);
   }
 
+  @Test
+  public void getMetadata_returns_empty() {
+    assertThat(underTest.getMetadata()).isEmpty();
+  }
+
   @Test
   public void getNotificationClass_is_BuiltInQPChangeNotification() {
     assertThat(underTest.getNotificationClass()).isEqualTo(BuiltInQPChangeNotification.class);
@@ -128,5 +133,4 @@ public class BuiltInQPChangeNotificationHandlerTest {
     notifications.forEach(Mockito::verifyZeroInteractions);
   }
 
-
 }