]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6567 move notifications from sonar-core to sonar-server
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Mon, 8 Jun 2015 13:22:34 +0000 (15:22 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Mon, 8 Jun 2015 13:22:34 +0000 (15:22 +0200)
54 files changed:
server/sonar-server/src/main/java/org/sonar/server/computation/step/SendIssueNotificationsStep.java
server/sonar-server/src/main/java/org/sonar/server/event/NewAlerts.java
server/sonar-server/src/main/java/org/sonar/server/issue/IssueBulkChangeService.java
server/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java
server/sonar-server/src/main/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationDispatcher.java
server/sonar-server/src/main/java/org/sonar/server/issue/notification/DoNotFixNotificationDispatcher.java
server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesNotificationDispatcher.java
server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcher.java
server/sonar-server/src/main/java/org/sonar/server/notification/DefaultNotificationManager.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/notification/NotificationCenter.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/notification/NotificationDispatcher.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/notification/NotificationDispatcherMetadata.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/notification/NotificationManager.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/notification/NotificationService.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/notification/email/AlertsEmailTemplate.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/notification/email/EmailNotificationChannel.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/notification/email/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/notification/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/notifications/NotificationCenter.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/notifications/NotificationService.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/notifications/email/AlertsEmailTemplate.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/notifications/email/EmailNotificationChannel.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/notifications/email/package-info.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/notifications/package-info.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/SendIssueNotificationsStepTest.java
server/sonar-server/src/test/java/org/sonar/server/event/NewAlertsTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/notification/ChangesOnMyIssueNotificationDispatcherTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/notification/DoNotFixNotificationDispatcherTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesNotificationDispatcherTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationDispatcherTest.java
server/sonar-server/src/test/java/org/sonar/server/notification/DefaultNotificationManagerTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/notification/NotificationCenterTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/notification/NotificationChannelTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/notification/NotificationDispatcherMetadataTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/notification/NotificationDispatcherTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/notification/NotificationServiceTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/notification/NotificationTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/notification/email/AlertsEmailTemplateTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/notification/email/EmailNotificationChannelTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/notifications/NotificationCenterTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/notifications/NotificationServiceTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/notifications/NotificationTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/notifications/email/AlertsEmailTemplateTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/notifications/email/EmailNotificationChannelTest.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java
sonar-core/src/main/java/org/sonar/core/notification/DefaultNotificationManager.java [deleted file]
sonar-core/src/main/java/org/sonar/core/notification/NotificationDispatcher.java [deleted file]
sonar-core/src/main/java/org/sonar/core/notification/NotificationDispatcherMetadata.java [deleted file]
sonar-core/src/main/java/org/sonar/core/notification/NotificationManager.java [deleted file]
sonar-core/src/test/java/org/sonar/core/notification/DefaultNotificationManagerTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/notification/NotificationChannelTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/notification/NotificationDispatcherMetadataTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/notification/NotificationDispatcherTest.java [deleted file]

index cbc929306eaa33e2262fe2575884e4006fb05538..5129e8a4959b763f843adf6c8cabc84be0543782 100644 (file)
@@ -34,13 +34,13 @@ import org.sonar.server.issue.notification.MyNewIssuesNotification;
 import org.sonar.server.issue.notification.NewIssuesNotification;
 import org.sonar.server.issue.notification.NewIssuesNotificationFactory;
 import org.sonar.server.issue.notification.NewIssuesStatistics;
-import org.sonar.server.notifications.NotificationService;
+import org.sonar.server.notification.NotificationService;
 import org.sonar.server.util.CloseableIterator;
 
 /**
  * Reads issues from disk cache and send related notifications. For performance reasons,
  * the standard notification DB queue is not used as a temporary storage. Notifications
- * are directly processed by {@link org.sonar.server.notifications.NotificationService}.
+ * are directly processed by {@link NotificationService}.
  */
 public class SendIssueNotificationsStep implements ComputationStep {
   /**
index cf4c219e8486b964973138a21d2f2c08d8ea5207..6ec224ec1f667346863cb483603e72a46451ebb0 100644 (file)
@@ -24,9 +24,9 @@ import org.sonar.api.notifications.*;
 
 import java.util.Collection;
 import java.util.Map;
-import org.sonar.core.notification.NotificationDispatcher;
-import org.sonar.core.notification.NotificationDispatcherMetadata;
-import org.sonar.core.notification.NotificationManager;
+import org.sonar.server.notification.NotificationDispatcher;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.NotificationManager;
 
 /**
  * This dispatcher means: "notify me each new alert event".
index 29f84cafcca6ef1437ff1478b4f583ace51f0ab3..53c4539b16f82805a10e75e4ff6a67cb642aae89 100644 (file)
@@ -44,7 +44,7 @@ import org.sonar.api.utils.log.Loggers;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.issue.db.IssueDto;
 import org.sonar.core.issue.db.IssueStorage;
-import org.sonar.core.notification.NotificationManager;
+import org.sonar.server.notification.NotificationManager;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.server.db.DbClient;
index b407ec4b6357667bf6497b0f60603a48ffe350df..0f342ce585acde8d0513f9152f563a4b8bd290c4 100644 (file)
@@ -26,7 +26,7 @@ import org.sonar.api.issue.ActionPlan;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.api.issue.internal.IssueChangeContext;
-import org.sonar.core.notification.NotificationManager;
+import org.sonar.server.notification.NotificationManager;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.rules.Rule;
index b38eb2f855b7ea7717e218cdb6ee52c7d2923935..5aa290eb83baa0fcb58953a75f31cf827938a039 100644 (file)
@@ -25,9 +25,9 @@ import org.sonar.api.notifications.*;
 
 import javax.annotation.Nullable;
 import java.util.Collection;
-import org.sonar.core.notification.NotificationDispatcher;
-import org.sonar.core.notification.NotificationDispatcherMetadata;
-import org.sonar.core.notification.NotificationManager;
+import org.sonar.server.notification.NotificationDispatcher;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.NotificationManager;
 
 /**
  * This dispatcher means: "notify me when a change is done on an issue that is assigned to me or reported by me".
index e4f0a5f81d9c201e46ab2bdb7f304f7ee69f1547..1ee261ea2149397f2a931bc231537b418bed2c5a 100644 (file)
@@ -27,9 +27,9 @@ import org.sonar.api.notifications.*;
 
 import java.util.Collection;
 import java.util.Map;
-import org.sonar.core.notification.NotificationDispatcher;
-import org.sonar.core.notification.NotificationDispatcherMetadata;
-import org.sonar.core.notification.NotificationManager;
+import org.sonar.server.notification.NotificationDispatcher;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.NotificationManager;
 
 /**
  * This dispatcher means: "notify me when an issue is resolved as false positive or won't fix".
index 3810279554f71a1a195d37cc5ce1428de75892ee..c4be5a62776132eb3d85e69bda15db1295aa09d8 100644 (file)
@@ -24,9 +24,9 @@ import com.google.common.collect.Multimap;
 import org.sonar.api.notifications.*;
 
 import java.util.Collection;
-import org.sonar.core.notification.NotificationDispatcher;
-import org.sonar.core.notification.NotificationDispatcherMetadata;
-import org.sonar.core.notification.NotificationManager;
+import org.sonar.server.notification.NotificationDispatcher;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.NotificationManager;
 
 /**
  * This dispatcher means: "notify me when new issues are introduced during project analysis"
index e346b1af744240eb22bc71757f30525627f4aadb..6c28a8aadf9d10501748a2ec70f04d3cea9bb725 100644 (file)
@@ -24,9 +24,9 @@ import org.sonar.api.notifications.*;
 
 import java.util.Collection;
 import java.util.Map;
-import org.sonar.core.notification.NotificationDispatcher;
-import org.sonar.core.notification.NotificationDispatcherMetadata;
-import org.sonar.core.notification.NotificationManager;
+import org.sonar.server.notification.NotificationDispatcher;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.NotificationManager;
 
 /**
  * This dispatcher means: "notify me when new issues are introduced during project analysis"
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notification/DefaultNotificationManager.java b/server/sonar-server/src/main/java/org/sonar/server/notification/DefaultNotificationManager.java
new file mode 100644 (file)
index 0000000..82706d1
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.SetMultimap;
+import java.io.IOException;
+import java.io.InvalidClassException;
+import java.util.Arrays;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationChannel;
+import org.sonar.api.utils.SonarException;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.notification.db.NotificationQueueDao;
+import org.sonar.core.notification.db.NotificationQueueDto;
+import org.sonar.core.properties.PropertiesDao;
+
+public class DefaultNotificationManager implements NotificationManager {
+
+  private static final Logger LOG = Loggers.get(DefaultNotificationManager.class);
+
+  private static final String UNABLE_TO_READ_NOTIFICATION = "Unable to read notification";
+
+  private NotificationChannel[] notificationChannels;
+  private NotificationQueueDao notificationQueueDao;
+  private PropertiesDao propertiesDao;
+
+  private boolean alreadyLoggedDeserializationIssue = false;
+
+  /**
+   * Default constructor used by Pico
+   */
+  public DefaultNotificationManager(NotificationChannel[] channels, NotificationQueueDao notificationQueueDao, PropertiesDao propertiesDao) {
+    this.notificationChannels = channels;
+    this.notificationQueueDao = notificationQueueDao;
+    this.propertiesDao = propertiesDao;
+  }
+
+  /**
+   * Constructor if no notification channel
+   */
+  public DefaultNotificationManager(NotificationQueueDao notificationQueueDao, PropertiesDao propertiesDao) {
+    this(new NotificationChannel[0], notificationQueueDao, propertiesDao);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void scheduleForSending(Notification notification) {
+    NotificationQueueDto dto = NotificationQueueDto.toNotificationQueueDto(notification);
+    notificationQueueDao.insert(Arrays.asList(dto));
+  }
+
+  @Override
+  public void scheduleForSending(List<Notification> notification) {
+    notificationQueueDao.insert(Lists.transform(notification, new Function<Notification, NotificationQueueDto>() {
+      @Override
+      public NotificationQueueDto apply(Notification notification) {
+        return NotificationQueueDto.toNotificationQueueDto(notification);
+      }
+    }));
+  }
+
+  /**
+   * Give the notification queue so that it can be processed
+   */
+  public Notification getFromQueue() {
+    int batchSize = 1;
+    List<NotificationQueueDto> notificationDtos = notificationQueueDao.findOldest(batchSize);
+    if (notificationDtos.isEmpty()) {
+      return null;
+    }
+    notificationQueueDao.delete(notificationDtos);
+
+    return convertToNotification(notificationDtos);
+  }
+
+  private Notification convertToNotification(List<NotificationQueueDto> notifications) {
+    try {
+      // If batchSize is increased then we should return a list instead of a single element
+      return notifications.get(0).toNotification();
+    } catch (InvalidClassException e) {
+      // SONAR-4739
+      if (!alreadyLoggedDeserializationIssue) {
+        logDeserializationIssue();
+        alreadyLoggedDeserializationIssue = true;
+      }
+      return null;
+    } catch (IOException e) {
+      throw new SonarException(UNABLE_TO_READ_NOTIFICATION, e);
+
+    } catch (ClassNotFoundException e) {
+      throw new SonarException(UNABLE_TO_READ_NOTIFICATION, e);
+    }
+  }
+
+  @VisibleForTesting
+  void logDeserializationIssue() {
+    LOG.warn("It is impossible to send pending notifications which existed prior to the upgrade of SonarQube. They will be ignored.");
+  }
+
+  public long count() {
+    return notificationQueueDao.count();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public Multimap<String, NotificationChannel> findSubscribedRecipientsForDispatcher(NotificationDispatcher dispatcher, @Nullable Integer resourceId) {
+    String dispatcherKey = dispatcher.getKey();
+
+    SetMultimap<String, NotificationChannel> recipients = HashMultimap.create();
+    for (NotificationChannel channel : notificationChannels) {
+      String channelKey = channel.getKey();
+
+      // Find users subscribed globally to the dispatcher (i.e. not on a specific project)
+      addUsersToRecipientListForChannel(propertiesDao.findUsersForNotification(dispatcherKey, channelKey, null), recipients, channel);
+
+      if (resourceId != null) {
+        // Find users subscribed to the dispatcher specifically for the resource
+        addUsersToRecipientListForChannel(propertiesDao.findUsersForNotification(dispatcherKey, channelKey, resourceId.longValue()), recipients, channel);
+      }
+    }
+
+    return recipients;
+  }
+
+  @Override
+  public Multimap<String, NotificationChannel> findNotificationSubscribers(NotificationDispatcher dispatcher, @Nullable String componentKey) {
+    String dispatcherKey = dispatcher.getKey();
+
+    SetMultimap<String, NotificationChannel> recipients = HashMultimap.create();
+    for (NotificationChannel channel : notificationChannels) {
+      addUsersToRecipientListForChannel(propertiesDao.findNotificationSubscribers(dispatcherKey, channel.getKey(), componentKey), recipients, channel);
+    }
+
+    return recipients;
+  }
+
+  @VisibleForTesting
+  protected List<NotificationChannel> getChannels() {
+    return Arrays.asList(notificationChannels);
+  }
+
+  private static void addUsersToRecipientListForChannel(List<String> users, SetMultimap<String, NotificationChannel> recipients, NotificationChannel channel) {
+    for (String username : users) {
+      recipients.put(username, channel);
+    }
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationCenter.java b/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationCenter.java
new file mode 100644 (file)
index 0000000..b739a60
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification;
+
+import com.google.common.collect.Lists;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.notifications.NotificationChannel;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import javax.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @since 3.5
+ */
+@ServerSide
+public class NotificationCenter {
+
+  private static final Logger LOG = Loggers.get(NotificationCenter.class);
+
+  private final NotificationDispatcherMetadata[] dispatchersMetadata;
+  private final NotificationChannel[] channels;
+
+  /**
+   * Constructor for {@link NotificationCenter}
+   */
+  public NotificationCenter(NotificationDispatcherMetadata[] metadata, NotificationChannel[] channels) {
+    this.dispatchersMetadata = metadata;
+    this.channels = channels;
+  }
+
+  /**
+   * Default constructor when no channels.
+   */
+  public NotificationCenter(NotificationDispatcherMetadata[] metadata) {
+    this(metadata, new NotificationChannel[0]);
+    LOG.warn("There is no notification channel - no notification will be delivered!");
+  }
+
+  /**
+   * Default constructor when no dispatcher metadata.
+   */
+  public NotificationCenter(NotificationChannel[] channels) {
+    this(new NotificationDispatcherMetadata[0], channels);
+  }
+
+  /**
+   * Default constructor.
+   */
+  public NotificationCenter() {
+    this(new NotificationDispatcherMetadata[0], new NotificationChannel[0]);
+    LOG.warn("There is no notification channel - no notification will be delivered!");
+  }
+
+  /**
+   * Returns all the available channels.
+   */
+  public List<NotificationChannel> getChannels() {
+    return Arrays.asList(channels);
+  }
+
+  /**
+   * Returns all the available dispatchers which metadata matches the given property and its value.
+   * <br/>
+   * If "propertyValue" is null, the verification is done on the existence of such a property (whatever the value).
+   */
+  public List<String> getDispatcherKeysForProperty(String propertyKey, @Nullable String propertyValue) {
+    List<String> keys = Lists.newArrayList();
+    for (NotificationDispatcherMetadata metadata : dispatchersMetadata) {
+      String dispatcherKey = metadata.getDispatcherKey();
+      String value = metadata.getProperty(propertyKey);
+      if (value != null && (propertyValue == null || value.equals(propertyValue))) {
+        keys.add(dispatcherKey);
+      }
+    }
+    return keys;
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationDispatcher.java b/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationDispatcher.java
new file mode 100644 (file)
index 0000000..3c99cbe
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification;
+
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.ExtensionPoint;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationChannel;
+import org.sonar.api.server.ServerSide;
+
+/**
+ * <p>
+ * Plugins should extend this class to provide logic to determine which users are interested in receiving notifications,
+ * along with which delivery channels they selected.
+ * </p>
+ * For example:
+ * <ul>
+ * <li>notify me by email when someone comments an issue reported by me</li>
+ * <li>notify me by twitter when someone comments an issue assigned to me</li>
+ * <li>notify me by Jabber when someone mentions me in an issue comment</li>
+ * <li>send me by SMS when there are system notifications (like password reset, account creation, ...)</li>
+ * </ul> 
+ * 
+ * @since 2.10
+ */
+@ServerSide
+@ExtensionPoint
+public abstract class NotificationDispatcher {
+
+  private final String notificationType;
+
+  /**
+   * Additional information related to the notification, which will be used
+   * to know who should receive the notification.
+   */
+  public interface Context {
+    /**
+     * This method is not used any longer. Calling it will result in an {@link UnsupportedOperationException}.
+     * 
+     * @deprecated Use {@link #addUser(String, NotificationChannel)} instead.
+     */
+    @Deprecated
+    void addUser(String userLogin);
+
+    /**
+     * Adds a user that will be notified through the given notification channel.
+     * 
+     * @param userLogin the user login
+     * @param notificationChannel the notification channel to use for this user
+     */
+    void addUser(String userLogin, NotificationChannel notificationChannel);
+  }
+
+  /**
+   * Creates a new dispatcher for notifications of the given type.
+   * 
+   * @param notificationType the type of notifications handled by this dispatcher
+   */
+  public NotificationDispatcher(String notificationType) {
+    this.notificationType = notificationType;
+  }
+
+  /**
+   * Creates a new generic dispatcher, used for any kind of notification. 
+   * <p/>
+   * Should be avoided and replaced by the other constructor - as it is easier to understand that a
+   * dispatcher listens for a specific type of notification.
+   */
+  public NotificationDispatcher() {
+    this("");
+  }
+
+  /**
+   * The unique key of this dispatcher. By default it's the class name without the package prefix.
+   * <p/>
+   * The related label in l10n bundles is 'notification.dispatcher.<key>', for example 'notification.dispatcher.NewFalsePositive'.
+   */
+  public String getKey() {
+    return getClass().getSimpleName();
+  }
+
+  /**
+   * @since 5.1
+   */
+  public String getType() {
+    return notificationType;
+  }
+
+  /**
+   * <p>
+   * Performs the dispatch.
+   * </p>
+   */
+  public final void performDispatch(Notification notification, Context context) {
+    if (StringUtils.equals(notification.getType(), notificationType) || StringUtils.equals("", notificationType)) {
+      dispatch(notification, context);
+    }
+  }
+
+  /**
+   * <p>
+   * Implements the logic that defines which users will receive the notification.
+   * </p>
+   * The purpose of this method is to populate the context object with users, based on the type of notification and the content of the notification.
+   */
+  public abstract void dispatch(Notification notification, Context context);
+
+  @Override
+  public String toString() {
+    return getKey();
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationDispatcherMetadata.java b/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationDispatcherMetadata.java
new file mode 100644 (file)
index 0000000..de80ec7
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification;
+
+import com.google.common.collect.Maps;
+import java.util.Map;
+import org.sonar.api.server.ServerSide;
+
+/**
+ * <p>
+ * Notification dispatchers (see {@link NotificationDispatcher}) can define their own metadata class in order
+ * to tell more about them.
+ * <p/>
+ * Instances of these classes must be declared in {@link org.sonar.api.SonarPlugin#getExtensions()}.
+ *
+ * @since 3.5
+ */
+@ServerSide
+public final class NotificationDispatcherMetadata {
+
+  public static final String GLOBAL_NOTIFICATION = "globalNotification";
+  public static final String PER_PROJECT_NOTIFICATION = "perProjectNotification";
+
+  private String dispatcherKey;
+  private Map<String, String> properties;
+
+  private NotificationDispatcherMetadata(String dispatcherKey) {
+    this.dispatcherKey = dispatcherKey;
+    this.properties = Maps.newHashMap();
+  }
+
+  /**
+   * Creates a new metadata instance for the given dispatcher.
+   * <p/>
+   * By default the key is the class name without package. It can be changed by overriding
+   * {@link NotificationDispatcher#getKey()}.
+   */
+  public static NotificationDispatcherMetadata create(String dispatcherKey) {
+    return new NotificationDispatcherMetadata(dispatcherKey);
+  }
+
+  /**
+   * Sets a property on this metadata object.
+   */
+  public NotificationDispatcherMetadata setProperty(String key, String value) {
+    properties.put(key, value);
+    return this;
+  }
+
+  /**
+   * Gives the property for the given key.
+   */
+  public String getProperty(String key) {
+    return properties.get(key);
+  }
+
+  /**
+   * Returns the unique key of the dispatcher.
+   */
+  public String getDispatcherKey() {
+    return dispatcherKey;
+  }
+
+  @Override
+  public String toString() {
+    return dispatcherKey;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    NotificationDispatcherMetadata that = (NotificationDispatcherMetadata) o;
+    return dispatcherKey.equals(that.dispatcherKey);
+  }
+
+  @Override
+  public int hashCode() {
+    return dispatcherKey.hashCode();
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationManager.java b/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationManager.java
new file mode 100644 (file)
index 0000000..d499a8f
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification;
+
+import com.google.common.collect.Multimap;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationChannel;
+
+/**
+ * <p>
+ * The notification manager receives notifications and is in charge of storing them so that they are processed by the notification service.
+ * </p>
+ * <p>
+ * Pico provides an instance of this class, and plugins just need to create notifications and pass them to this manager with
+ * the {@link NotificationManager#scheduleForSending(Notification)} method.
+ * </p>
+ */
+public interface NotificationManager {
+
+  /**
+   * Receives a notification and stores it so that it is processed by the notification service.
+   *
+   * @param notification the notification.
+   */
+  void scheduleForSending(Notification notification);
+
+  /**
+   * Receives notifications and stores them so that they are processed by the notification service.
+   *
+   * @param notifications the notifications.
+   * @since 3.7.1
+   */
+  void scheduleForSending(List<Notification> notifications);
+
+  /**
+   * <p>
+   * Returns the list of users who subscribed to the given dispatcher, along with the notification channels (email, twitter, ...) that they choose
+   * for this dispatcher.
+   * </p>
+   * <p>
+   * The resource ID can be null in case of notifications that have nothing to do with a specific project (like system notifications).
+   * </p>
+   *
+   * @param dispatcher the dispatcher for which this list of users is requested
+   * @param resourceId the optional resource which is concerned by this request
+   * @return the list of user login along with the subscribed channels
+   */
+  Multimap<String, NotificationChannel> findSubscribedRecipientsForDispatcher(NotificationDispatcher dispatcher, @Nullable Integer resourceId);
+
+  Multimap<String, NotificationChannel> findNotificationSubscribers(NotificationDispatcher dispatcher, @Nullable String componentKey);
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationService.java b/server/sonar-server/src/main/java/org/sonar/server/notification/NotificationService.java
new file mode 100644 (file)
index 0000000..b3951e8
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+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.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import org.picocontainer.Startable;
+import org.sonar.api.Properties;
+import org.sonar.api.Property;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.config.Settings;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationChannel;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.jpa.session.DatabaseSessionFactory;
+import org.sonar.server.db.DbClient;
+
+/**
+ * @since 2.10
+ */
+@Properties({
+  @Property(
+    key = NotificationService.PROPERTY_DELAY,
+    defaultValue = "60",
+    name = "Delay of notifications, in seconds",
+    project = false,
+    global = false),
+  @Property(
+    key = NotificationService.PROPERTY_DELAY_BEFORE_REPORTING_STATUS,
+    defaultValue = "600",
+    name = "Delay before reporting notification status, in seconds",
+    project = false,
+    global = false)
+})
+@ServerSide
+public class NotificationService implements Startable {
+
+  private static final Logger LOG = Loggers.get(NotificationService.class);
+
+  public static final String PROPERTY_DELAY = "sonar.notifications.delay";
+  public static final String PROPERTY_DELAY_BEFORE_REPORTING_STATUS = "sonar.notifications.runningDelayBeforeReportingStatus";
+
+  private final long delayInSeconds;
+  private final long delayBeforeReportingStatusInSeconds;
+  private final DatabaseSessionFactory databaseSessionFactory;
+  private final DefaultNotificationManager manager;
+  private final List<NotificationDispatcher> dispatchers;
+  private final DbClient dbClient;
+
+  private ScheduledExecutorService executorService;
+  private boolean stopping = false;
+
+  /**
+   * Constructor for {@link NotificationService}
+   */
+  public NotificationService(Settings settings, DefaultNotificationManager manager, DbClient dbClient,
+                             DatabaseSessionFactory databaseSessionFactory, NotificationDispatcher[] dispatchers) {
+    this.databaseSessionFactory = databaseSessionFactory;
+    this.delayInSeconds = settings.getLong(PROPERTY_DELAY);
+    this.delayBeforeReportingStatusInSeconds = settings.getLong(PROPERTY_DELAY_BEFORE_REPORTING_STATUS);
+    this.manager = manager;
+    this.dbClient = dbClient;
+    this.dispatchers = ImmutableList.copyOf(dispatchers);
+  }
+
+  /**
+   * Default constructor when no dispatchers.
+   */
+  public NotificationService(Settings settings, DefaultNotificationManager manager, DbClient dbClient,
+                             DatabaseSessionFactory databaseSessionFactory) {
+    this(settings, manager, dbClient, databaseSessionFactory, new NotificationDispatcher[0]);
+  }
+
+  @Override
+  public void start() {
+    executorService = Executors.newSingleThreadScheduledExecutor();
+    executorService.scheduleWithFixedDelay(new Runnable() {
+      @Override
+      public void run() {
+        try {
+          processQueue();
+        } catch (Exception e) {
+          LOG.error("Error in NotificationService", e);
+        } finally {
+          // Free Hibernate session
+          // See https://jira.sonarsource.com/browse/SONAR-6566
+          databaseSessionFactory.clear();
+        }
+      }
+    }, 0, delayInSeconds, TimeUnit.SECONDS);
+    LOG.info("Notification service started (delay {} sec.)", delayInSeconds);
+  }
+
+  @Override
+  public void stop() {
+    try {
+      stopping = true;
+      executorService.shutdown();
+      executorService.awaitTermination(5, TimeUnit.SECONDS);
+    } catch (InterruptedException e) {
+      LOG.error("Error during stop of notification service", e);
+    }
+    LOG.info("Notification service stopped");
+  }
+
+  @VisibleForTesting
+  synchronized void processQueue() {
+    long start = now();
+    long lastLog = start;
+    long notifSentCount = 0;
+
+    Notification notifToSend = manager.getFromQueue();
+    while (notifToSend != null) {
+      deliver(notifToSend);
+      notifSentCount++;
+      if (stopping) {
+        break;
+      }
+      long now = now();
+      if (now - lastLog > delayBeforeReportingStatusInSeconds * 1000) {
+        long remainingNotifCount = manager.count();
+        lastLog = now;
+        long spentTimeInMinutes = (now - start) / (60 * 1000);
+        log(notifSentCount, remainingNotifCount, spentTimeInMinutes);
+      }
+      notifToSend = manager.getFromQueue();
+    }
+  }
+
+  @VisibleForTesting
+  void log(long notifSentCount, long remainingNotifCount, long spentTimeInMinutes) {
+    LOG.info("{} notifications sent during the past {} minutes and {} still waiting to be sent",
+      new Object[] {notifSentCount, spentTimeInMinutes, remainingNotifCount});
+  }
+
+  @VisibleForTesting
+  long now() {
+    return System.currentTimeMillis();
+  }
+
+  public void deliver(Notification notification) {
+    final SetMultimap<String, NotificationChannel> recipients = HashMultimap.create();
+    for (NotificationDispatcher dispatcher : dispatchers) {
+      NotificationDispatcher.Context context = new NotificationDispatcher.Context() {
+        @Override
+        public void addUser(String username) {
+          // This method is not used anymore
+        }
+
+        @Override
+        public void addUser(String userLogin, NotificationChannel notificationChannel) {
+          if (userLogin != null) {
+            recipients.put(userLogin, notificationChannel);
+          }
+        }
+      };
+      try {
+        dispatcher.performDispatch(notification, context);
+      } catch (Exception e) {
+        // catch all exceptions in order to dispatch using other dispatchers
+        LOG.warn("Unable to dispatch notification " + notification + " using " + dispatcher, e);
+      }
+    }
+    dispatch(notification, recipients);
+  }
+
+  private void dispatch(Notification notification, SetMultimap<String, NotificationChannel> recipients) {
+    for (Map.Entry<String, Collection<NotificationChannel>> entry : recipients.asMap().entrySet()) {
+      String username = entry.getKey();
+      Collection<NotificationChannel> userChannels = entry.getValue();
+      LOG.debug("For user {} via {}", username, userChannels);
+      for (NotificationChannel channel : userChannels) {
+        try {
+          channel.deliver(notification, username);
+        } catch (Exception e) {
+          // catch all exceptions in order to deliver via other channels
+          LOG.warn("Unable to deliver notification " + notification + " for user " + username + " via " + channel, e);
+        }
+      }
+    }
+  }
+
+  @VisibleForTesting
+  protected List<NotificationDispatcher> getDispatchers() {
+    return dispatchers;
+  }
+
+  /**
+   * Returns true if at least one user is subscribed to at least one notifications with given types.
+   * Subscription can be globally 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());
+      }
+    }
+
+    return dbClient.propertiesDao().hasProjectNotificationSubscribersForDispatchers(projectUuid, dispatcherKeys);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notification/email/AlertsEmailTemplate.java b/server/sonar-server/src/main/java/org/sonar/server/notification/email/AlertsEmailTemplate.java
new file mode 100644 (file)
index 0000000..77a5350
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification.email;
+
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.config.EmailSettings;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.notifications.Notification;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+import org.sonar.plugins.emailnotifications.api.EmailTemplate;
+
+/**
+ * Creates email message for notification "alerts".
+ *
+ * @since 3.5
+ */
+public class AlertsEmailTemplate extends EmailTemplate {
+
+  private EmailSettings configuration;
+
+  public AlertsEmailTemplate(EmailSettings configuration) {
+    this.configuration = configuration;
+  }
+
+  @Override
+  public EmailMessage format(Notification notification) {
+    if (!"alerts".equals(notification.getType())) {
+      return null;
+    }
+
+    // Retrieve useful values
+    String projectId = notification.getFieldValue("projectId");
+    String projectKey = notification.getFieldValue("projectKey");
+    String projectName = notification.getFieldValue("projectName");
+    String alertName = notification.getFieldValue("alertName");
+    String alertText = notification.getFieldValue("alertText");
+    String alertLevel = notification.getFieldValue("alertLevel");
+    boolean isNewAlert = Boolean.parseBoolean(notification.getFieldValue("isNewAlert"));
+
+    // Generate text
+    String subject = generateSubject(projectName, alertLevel, isNewAlert);
+    String messageBody = generateMessageBody(projectName, projectKey, alertName, alertText, isNewAlert);
+
+    // And finally return the email that will be sent
+    return new EmailMessage()
+      .setMessageId("alerts/" + projectId)
+      .setSubject(subject)
+      .setMessage(messageBody);
+  }
+
+  private static String generateSubject(String projectName, String alertLevel, boolean isNewAlert) {
+    StringBuilder subjectBuilder = new StringBuilder();
+    if (Metric.Level.OK.toString().equals(alertLevel)) {
+      subjectBuilder.append("\"").append(projectName).append("\" is back to green");
+    } else if (isNewAlert) {
+      subjectBuilder.append("New quality gate threshold reached on \"").append(projectName).append("\"");
+    } else {
+      subjectBuilder.append("Quality gate status changed on \"").append(projectName).append("\"");
+    }
+    return subjectBuilder.toString();
+  }
+
+  private String generateMessageBody(String projectName, String projectKey, String alertName, String alertText, boolean isNewAlert) {
+    StringBuilder messageBody = new StringBuilder();
+    messageBody.append("Project: ").append(projectName).append("\n");
+    messageBody.append("Quality gate status: ").append(alertName).append("\n\n");
+
+    String[] alerts = StringUtils.split(alertText, ",");
+    if (alerts.length > 0) {
+      if (isNewAlert) {
+        messageBody.append("New quality gate threshold");
+      } else {
+        messageBody.append("Quality gate threshold");
+      }
+      if (alerts.length == 1) {
+        messageBody.append(": ").append(alerts[0].trim()).append("\n");
+      } else {
+        messageBody.append("s:\n");
+        for (String alert : alerts) {
+          messageBody.append("  - ").append(alert.trim()).append("\n");
+        }
+      }
+    }
+
+    messageBody.append("\n").append("See it in SonarQube: ").append(configuration.getServerBaseURL()).append("/dashboard/index/").append(projectKey);
+
+    return messageBody.toString();
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notification/email/EmailNotificationChannel.java b/server/sonar-server/src/main/java/org/sonar/server/notification/email/EmailNotificationChannel.java
new file mode 100644 (file)
index 0000000..de7dc0f
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification.email;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.mail.EmailException;
+import org.apache.commons.mail.SimpleEmail;
+import org.sonar.api.config.EmailSettings;
+import org.sonar.api.database.model.User;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationChannel;
+import org.sonar.api.security.UserFinder;
+import org.sonar.api.utils.SonarException;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+import org.sonar.plugins.emailnotifications.api.EmailTemplate;
+
+/**
+ * References:
+ * <ul>
+ * <li><a href="http://tools.ietf.org/html/rfc4021">Registration of Mail and MIME Header Fields</a></li>
+ * <li><a href="http://tools.ietf.org/html/rfc2919">List-Id: A Structured Field and Namespace for the Identification of Mailing Lists</a></li>
+ * <li><a href="https://github.com/blog/798-threaded-email-notifications">GitHub: Threaded Email Notifications</a></li>
+ * </ul>
+ *
+ * @since 2.10
+ */
+public class EmailNotificationChannel extends NotificationChannel {
+
+  private static final Logger LOG = Loggers.get(EmailNotificationChannel.class);
+
+  /**
+   * @see org.apache.commons.mail.Email#setSocketConnectionTimeout(int)
+   * @see org.apache.commons.mail.Email#setSocketTimeout(int)
+   */
+  private static final int SOCKET_TIMEOUT = 30000;
+
+  /**
+   * Email Header Field: "List-ID".
+   * Value of this field should contain mailing list identifier as specified in <a href="http://tools.ietf.org/html/rfc2919">RFC 2919</a>.
+   */
+  private static final String LIST_ID_HEADER = "List-ID";
+
+  /**
+   * Email Header Field: "List-Archive".
+   * Value of this field should contain URL of mailing list archive as specified in <a href="http://tools.ietf.org/html/rfc2369">RFC 2369</a>.
+   */
+  private static final String LIST_ARCHIVE_HEADER = "List-Archive";
+
+  /**
+   * Email Header Field: "In-Reply-To".
+   * Value of this field should contain related message identifier as specified in <a href="http://tools.ietf.org/html/rfc2822">RFC 2822</a>.
+   */
+  private static final String IN_REPLY_TO_HEADER = "In-Reply-To";
+
+  /**
+   * Email Header Field: "References".
+   * Value of this field should contain related message identifier as specified in <a href="http://tools.ietf.org/html/rfc2822">RFC 2822</a>
+   */
+  private static final String REFERENCES_HEADER = "References";
+
+  private static final String FROM_NAME_DEFAULT = "SonarQube";
+  private static final String SUBJECT_DEFAULT = "Notification";
+
+  private EmailSettings configuration;
+  private EmailTemplate[] templates;
+  private UserFinder userFinder;
+
+  public EmailNotificationChannel(EmailSettings configuration, EmailTemplate[] templates, UserFinder userFinder) {
+    this.configuration = configuration;
+    this.templates = templates;
+    this.userFinder = userFinder;
+  }
+
+  @Override
+  public void deliver(Notification notification, String username) {
+    User user = userFinder.findByLogin(username);
+    if (StringUtils.isBlank(user.getEmail())) {
+      LOG.debug("Email not defined for user: " + username);
+      return;
+    }
+    EmailMessage emailMessage = format(notification);
+    if (emailMessage != null) {
+      emailMessage.setTo(user.getEmail());
+      deliver(emailMessage);
+    }
+  }
+
+  private EmailMessage format(Notification notification) {
+    for (EmailTemplate template : templates) {
+      EmailMessage email = template.format(notification);
+      if (email != null) {
+        return email;
+      }
+    }
+    LOG.warn("Email template not found for notification: {}", notification);
+    return null;
+  }
+
+  /**
+   * Visibility has been relaxed for tests.
+   */
+  void deliver(EmailMessage emailMessage) {
+    if (StringUtils.isBlank(configuration.getSmtpHost())) {
+      LOG.debug("SMTP host was not configured - email will not be sent");
+      return;
+    }
+    try {
+      send(emailMessage);
+    } catch (EmailException e) {
+      LOG.error("Unable to send email", e);
+    }
+  }
+
+  private void send(EmailMessage emailMessage) throws EmailException {
+    // Trick to correctly initilize javax.mail library
+    ClassLoader classloader = Thread.currentThread().getContextClassLoader();
+    Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+
+    try {
+      LOG.debug("Sending email: {}", emailMessage);
+      String host = null;
+      try {
+        host = new URL(configuration.getServerBaseURL()).getHost();
+      } catch (MalformedURLException e) {
+        // ignore
+      }
+
+      SimpleEmail email = new SimpleEmail();
+      if (StringUtils.isNotBlank(host)) {
+        /*
+         * Set headers for proper threading: GMail will not group messages, even if they have same subject, but don't have "In-Reply-To" and
+         * "References" headers. TODO investigate threading in other clients like KMail, Thunderbird, Outlook
+         */
+        if (StringUtils.isNotEmpty(emailMessage.getMessageId())) {
+          String messageId = "<" + emailMessage.getMessageId() + "@" + host + ">";
+          email.addHeader(IN_REPLY_TO_HEADER, messageId);
+          email.addHeader(REFERENCES_HEADER, messageId);
+        }
+        // Set headers for proper filtering
+        email.addHeader(LIST_ID_HEADER, "SonarQube <sonar." + host + ">");
+        email.addHeader(LIST_ARCHIVE_HEADER, configuration.getServerBaseURL());
+      }
+      // Set general information
+      email.setCharset("UTF-8");
+      String from = StringUtils.isBlank(emailMessage.getFrom()) ? FROM_NAME_DEFAULT : (emailMessage.getFrom() + " (SonarQube)");
+      email.setFrom(configuration.getFrom(), from);
+      email.addTo(emailMessage.getTo(), " ");
+      String subject = StringUtils.defaultIfBlank(StringUtils.trimToEmpty(configuration.getPrefix()) + " ", "")
+        + StringUtils.defaultString(emailMessage.getSubject(), SUBJECT_DEFAULT);
+      email.setSubject(subject);
+      email.setMsg(emailMessage.getMessage());
+      // Send
+      email.setHostName(configuration.getSmtpHost());
+      configureSecureConnection(email);
+      if (StringUtils.isNotBlank(configuration.getSmtpUsername()) || StringUtils.isNotBlank(configuration.getSmtpPassword())) {
+        email.setAuthentication(configuration.getSmtpUsername(), configuration.getSmtpPassword());
+      }
+      email.setSocketConnectionTimeout(SOCKET_TIMEOUT);
+      email.setSocketTimeout(SOCKET_TIMEOUT);
+      email.send();
+
+    } finally {
+      Thread.currentThread().setContextClassLoader(classloader);
+    }
+  }
+
+  private void configureSecureConnection(SimpleEmail email) {
+    if (StringUtils.equalsIgnoreCase(configuration.getSecureConnection(), "ssl")) {
+      email.setSSLOnConnect(true);
+      email.setSslSmtpPort(String.valueOf(configuration.getSmtpPort()));
+
+      // this port is not used except in EmailException message, that's why it's set with the same value than SSL port.
+      // It prevents from getting bad message.
+      email.setSmtpPort(configuration.getSmtpPort());
+    } else if (StringUtils.equalsIgnoreCase(configuration.getSecureConnection(), "starttls")) {
+      email.setStartTLSEnabled(true);
+      email.setStartTLSRequired(true);
+      email.setSmtpPort(configuration.getSmtpPort());
+    } else if (StringUtils.isBlank(configuration.getSecureConnection())) {
+      email.setSmtpPort(configuration.getSmtpPort());
+    } else {
+      throw new SonarException("Unknown type of SMTP secure connection: " + configuration.getSecureConnection());
+    }
+  }
+
+  /**
+   * Send test email. This method called from Ruby.
+   *
+   * @throws EmailException when unable to send
+   */
+  public void sendTestEmail(String toAddress, String subject, String message) throws EmailException {
+    try {
+      EmailMessage emailMessage = new EmailMessage();
+      emailMessage.setTo(toAddress);
+      emailMessage.setSubject(subject);
+      emailMessage.setMessage(message);
+      send(emailMessage);
+    } catch (EmailException e) {
+      LOG.error("Fail to send test email to: " + toAddress, e);
+      throw e;
+    }
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notification/email/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/notification/email/package-info.java
new file mode 100644 (file)
index 0000000..f583fa7
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.notification.email;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notification/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/notification/package-info.java
new file mode 100644 (file)
index 0000000..71e0748
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.notification;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notifications/NotificationCenter.java b/server/sonar-server/src/main/java/org/sonar/server/notifications/NotificationCenter.java
deleted file mode 100644 (file)
index cf43dca..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.server.notifications;
-
-import com.google.common.collect.Lists;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.core.notification.NotificationDispatcherMetadata;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-
-import javax.annotation.Nullable;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * @since 3.5
- */
-@ServerSide
-public class NotificationCenter {
-
-  private static final Logger LOG = Loggers.get(NotificationCenter.class);
-
-  private final NotificationDispatcherMetadata[] dispatchersMetadata;
-  private final NotificationChannel[] channels;
-
-  /**
-   * Constructor for {@link NotificationCenter}
-   */
-  public NotificationCenter(NotificationDispatcherMetadata[] metadata, NotificationChannel[] channels) {
-    this.dispatchersMetadata = metadata;
-    this.channels = channels;
-  }
-
-  /**
-   * Default constructor when no channels.
-   */
-  public NotificationCenter(NotificationDispatcherMetadata[] metadata) {
-    this(metadata, new NotificationChannel[0]);
-    LOG.warn("There is no notification channel - no notification will be delivered!");
-  }
-
-  /**
-   * Default constructor when no dispatcher metadata.
-   */
-  public NotificationCenter(NotificationChannel[] channels) {
-    this(new NotificationDispatcherMetadata[0], channels);
-  }
-
-  /**
-   * Default constructor.
-   */
-  public NotificationCenter() {
-    this(new NotificationDispatcherMetadata[0], new NotificationChannel[0]);
-    LOG.warn("There is no notification channel - no notification will be delivered!");
-  }
-
-  /**
-   * Returns all the available channels.
-   */
-  public List<NotificationChannel> getChannels() {
-    return Arrays.asList(channels);
-  }
-
-  /**
-   * Returns all the available dispatchers which metadata matches the given property and its value.
-   * <br/>
-   * If "propertyValue" is null, the verification is done on the existence of such a property (whatever the value).
-   */
-  public List<String> getDispatcherKeysForProperty(String propertyKey, @Nullable String propertyValue) {
-    List<String> keys = Lists.newArrayList();
-    for (NotificationDispatcherMetadata metadata : dispatchersMetadata) {
-      String dispatcherKey = metadata.getDispatcherKey();
-      String value = metadata.getProperty(propertyKey);
-      if (value != null && (propertyValue == null || value.equals(propertyValue))) {
-        keys.add(dispatcherKey);
-      }
-    }
-    return keys;
-  }
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notifications/NotificationService.java b/server/sonar-server/src/main/java/org/sonar/server/notifications/NotificationService.java
deleted file mode 100644 (file)
index e33af89..0000000
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.server.notifications;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableList;
-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.Set;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import org.picocontainer.Startable;
-import org.sonar.api.Properties;
-import org.sonar.api.Property;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.config.Settings;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.core.notification.NotificationDispatcher;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.core.notification.DefaultNotificationManager;
-import org.sonar.jpa.session.DatabaseSessionFactory;
-import org.sonar.server.db.DbClient;
-
-/**
- * @since 2.10
- */
-@Properties({
-  @Property(
-    key = NotificationService.PROPERTY_DELAY,
-    defaultValue = "60",
-    name = "Delay of notifications, in seconds",
-    project = false,
-    global = false),
-  @Property(
-    key = NotificationService.PROPERTY_DELAY_BEFORE_REPORTING_STATUS,
-    defaultValue = "600",
-    name = "Delay before reporting notification status, in seconds",
-    project = false,
-    global = false)
-})
-@ServerSide
-public class NotificationService implements Startable {
-
-  private static final Logger LOG = Loggers.get(NotificationService.class);
-
-  public static final String PROPERTY_DELAY = "sonar.notifications.delay";
-  public static final String PROPERTY_DELAY_BEFORE_REPORTING_STATUS = "sonar.notifications.runningDelayBeforeReportingStatus";
-
-  private final long delayInSeconds;
-  private final long delayBeforeReportingStatusInSeconds;
-  private final DatabaseSessionFactory databaseSessionFactory;
-  private final DefaultNotificationManager manager;
-  private final List<NotificationDispatcher> dispatchers;
-  private final DbClient dbClient;
-
-  private ScheduledExecutorService executorService;
-  private boolean stopping = false;
-
-  /**
-   * Constructor for {@link NotificationService}
-   */
-  public NotificationService(Settings settings, DefaultNotificationManager manager, DbClient dbClient,
-                             DatabaseSessionFactory databaseSessionFactory, NotificationDispatcher[] dispatchers) {
-    this.databaseSessionFactory = databaseSessionFactory;
-    this.delayInSeconds = settings.getLong(PROPERTY_DELAY);
-    this.delayBeforeReportingStatusInSeconds = settings.getLong(PROPERTY_DELAY_BEFORE_REPORTING_STATUS);
-    this.manager = manager;
-    this.dbClient = dbClient;
-    this.dispatchers = ImmutableList.copyOf(dispatchers);
-  }
-
-  /**
-   * Default constructor when no dispatchers.
-   */
-  public NotificationService(Settings settings, DefaultNotificationManager manager, DbClient dbClient,
-                             DatabaseSessionFactory databaseSessionFactory) {
-    this(settings, manager, dbClient, databaseSessionFactory, new NotificationDispatcher[0]);
-  }
-
-  @Override
-  public void start() {
-    executorService = Executors.newSingleThreadScheduledExecutor();
-    executorService.scheduleWithFixedDelay(new Runnable() {
-      @Override
-      public void run() {
-        try {
-          processQueue();
-        } catch (Exception e) {
-          LOG.error("Error in NotificationService", e);
-        } finally {
-          // Free Hibernate session
-          // See https://jira.sonarsource.com/browse/SONAR-6566
-          databaseSessionFactory.clear();
-        }
-      }
-    }, 0, delayInSeconds, TimeUnit.SECONDS);
-    LOG.info("Notification service started (delay {} sec.)", delayInSeconds);
-  }
-
-  @Override
-  public void stop() {
-    try {
-      stopping = true;
-      executorService.shutdown();
-      executorService.awaitTermination(5, TimeUnit.SECONDS);
-    } catch (InterruptedException e) {
-      LOG.error("Error during stop of notification service", e);
-    }
-    LOG.info("Notification service stopped");
-  }
-
-  @VisibleForTesting
-  synchronized void processQueue() {
-    long start = now();
-    long lastLog = start;
-    long notifSentCount = 0;
-
-    Notification notifToSend = manager.getFromQueue();
-    while (notifToSend != null) {
-      deliver(notifToSend);
-      notifSentCount++;
-      if (stopping) {
-        break;
-      }
-      long now = now();
-      if (now - lastLog > delayBeforeReportingStatusInSeconds * 1000) {
-        long remainingNotifCount = manager.count();
-        lastLog = now;
-        long spentTimeInMinutes = (now - start) / (60 * 1000);
-        log(notifSentCount, remainingNotifCount, spentTimeInMinutes);
-      }
-      notifToSend = manager.getFromQueue();
-    }
-  }
-
-  @VisibleForTesting
-  void log(long notifSentCount, long remainingNotifCount, long spentTimeInMinutes) {
-    LOG.info("{} notifications sent during the past {} minutes and {} still waiting to be sent",
-      new Object[] {notifSentCount, spentTimeInMinutes, remainingNotifCount});
-  }
-
-  @VisibleForTesting
-  long now() {
-    return System.currentTimeMillis();
-  }
-
-  public void deliver(Notification notification) {
-    final SetMultimap<String, NotificationChannel> recipients = HashMultimap.create();
-    for (NotificationDispatcher dispatcher : dispatchers) {
-      NotificationDispatcher.Context context = new NotificationDispatcher.Context() {
-        @Override
-        public void addUser(String username) {
-          // This method is not used anymore
-        }
-
-        @Override
-        public void addUser(String userLogin, NotificationChannel notificationChannel) {
-          if (userLogin != null) {
-            recipients.put(userLogin, notificationChannel);
-          }
-        }
-      };
-      try {
-        dispatcher.performDispatch(notification, context);
-      } catch (Exception e) {
-        // catch all exceptions in order to dispatch using other dispatchers
-        LOG.warn("Unable to dispatch notification " + notification + " using " + dispatcher, e);
-      }
-    }
-    dispatch(notification, recipients);
-  }
-
-  private void dispatch(Notification notification, SetMultimap<String, NotificationChannel> recipients) {
-    for (Map.Entry<String, Collection<NotificationChannel>> entry : recipients.asMap().entrySet()) {
-      String username = entry.getKey();
-      Collection<NotificationChannel> userChannels = entry.getValue();
-      LOG.debug("For user {} via {}", username, userChannels);
-      for (NotificationChannel channel : userChannels) {
-        try {
-          channel.deliver(notification, username);
-        } catch (Exception e) {
-          // catch all exceptions in order to deliver via other channels
-          LOG.warn("Unable to deliver notification " + notification + " for user " + username + " via " + channel, e);
-        }
-      }
-    }
-  }
-
-  @VisibleForTesting
-  protected List<NotificationDispatcher> getDispatchers() {
-    return dispatchers;
-  }
-
-  /**
-   * Returns true if at least one user is subscribed to at least one notifications with given types.
-   * Subscription can be globally 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());
-      }
-    }
-
-    return dbClient.propertiesDao().hasProjectNotificationSubscribersForDispatchers(projectUuid, dispatcherKeys);
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notifications/email/AlertsEmailTemplate.java b/server/sonar-server/src/main/java/org/sonar/server/notifications/email/AlertsEmailTemplate.java
deleted file mode 100644 (file)
index 0ea9c3c..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.server.notifications.email;
-
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.config.EmailSettings;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.notifications.Notification;
-import org.sonar.plugins.emailnotifications.api.EmailMessage;
-import org.sonar.plugins.emailnotifications.api.EmailTemplate;
-
-/**
- * Creates email message for notification "alerts".
- *
- * @since 3.5
- */
-public class AlertsEmailTemplate extends EmailTemplate {
-
-  private EmailSettings configuration;
-
-  public AlertsEmailTemplate(EmailSettings configuration) {
-    this.configuration = configuration;
-  }
-
-  @Override
-  public EmailMessage format(Notification notification) {
-    if (!"alerts".equals(notification.getType())) {
-      return null;
-    }
-
-    // Retrieve useful values
-    String projectId = notification.getFieldValue("projectId");
-    String projectKey = notification.getFieldValue("projectKey");
-    String projectName = notification.getFieldValue("projectName");
-    String alertName = notification.getFieldValue("alertName");
-    String alertText = notification.getFieldValue("alertText");
-    String alertLevel = notification.getFieldValue("alertLevel");
-    boolean isNewAlert = Boolean.parseBoolean(notification.getFieldValue("isNewAlert"));
-
-    // Generate text
-    String subject = generateSubject(projectName, alertLevel, isNewAlert);
-    String messageBody = generateMessageBody(projectName, projectKey, alertName, alertText, isNewAlert);
-
-    // And finally return the email that will be sent
-    return new EmailMessage()
-      .setMessageId("alerts/" + projectId)
-      .setSubject(subject)
-      .setMessage(messageBody);
-  }
-
-  private static String generateSubject(String projectName, String alertLevel, boolean isNewAlert) {
-    StringBuilder subjectBuilder = new StringBuilder();
-    if (Metric.Level.OK.toString().equals(alertLevel)) {
-      subjectBuilder.append("\"").append(projectName).append("\" is back to green");
-    } else if (isNewAlert) {
-      subjectBuilder.append("New quality gate threshold reached on \"").append(projectName).append("\"");
-    } else {
-      subjectBuilder.append("Quality gate status changed on \"").append(projectName).append("\"");
-    }
-    return subjectBuilder.toString();
-  }
-
-  private String generateMessageBody(String projectName, String projectKey, String alertName, String alertText, boolean isNewAlert) {
-    StringBuilder messageBody = new StringBuilder();
-    messageBody.append("Project: ").append(projectName).append("\n");
-    messageBody.append("Quality gate status: ").append(alertName).append("\n\n");
-
-    String[] alerts = StringUtils.split(alertText, ",");
-    if (alerts.length > 0) {
-      if (isNewAlert) {
-        messageBody.append("New quality gate threshold");
-      } else {
-        messageBody.append("Quality gate threshold");
-      }
-      if (alerts.length == 1) {
-        messageBody.append(": ").append(alerts[0].trim()).append("\n");
-      } else {
-        messageBody.append("s:\n");
-        for (String alert : alerts) {
-          messageBody.append("  - ").append(alert.trim()).append("\n");
-        }
-      }
-    }
-
-    messageBody.append("\n").append("See it in SonarQube: ").append(configuration.getServerBaseURL()).append("/dashboard/index/").append(projectKey);
-
-    return messageBody.toString();
-  }
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notifications/email/EmailNotificationChannel.java b/server/sonar-server/src/main/java/org/sonar/server/notifications/email/EmailNotificationChannel.java
deleted file mode 100644 (file)
index 3b96270..0000000
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.server.notifications.email;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import org.apache.commons.lang.StringUtils;
-import org.apache.commons.mail.EmailException;
-import org.apache.commons.mail.SimpleEmail;
-import org.sonar.api.config.EmailSettings;
-import org.sonar.api.database.model.User;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.api.security.UserFinder;
-import org.sonar.api.utils.SonarException;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.plugins.emailnotifications.api.EmailMessage;
-import org.sonar.plugins.emailnotifications.api.EmailTemplate;
-
-/**
- * References:
- * <ul>
- * <li><a href="http://tools.ietf.org/html/rfc4021">Registration of Mail and MIME Header Fields</a></li>
- * <li><a href="http://tools.ietf.org/html/rfc2919">List-Id: A Structured Field and Namespace for the Identification of Mailing Lists</a></li>
- * <li><a href="https://github.com/blog/798-threaded-email-notifications">GitHub: Threaded Email Notifications</a></li>
- * </ul>
- *
- * @since 2.10
- */
-public class EmailNotificationChannel extends NotificationChannel {
-
-  private static final Logger LOG = Loggers.get(EmailNotificationChannel.class);
-
-  /**
-   * @see org.apache.commons.mail.Email#setSocketConnectionTimeout(int)
-   * @see org.apache.commons.mail.Email#setSocketTimeout(int)
-   */
-  private static final int SOCKET_TIMEOUT = 30000;
-
-  /**
-   * Email Header Field: "List-ID".
-   * Value of this field should contain mailing list identifier as specified in <a href="http://tools.ietf.org/html/rfc2919">RFC 2919</a>.
-   */
-  private static final String LIST_ID_HEADER = "List-ID";
-
-  /**
-   * Email Header Field: "List-Archive".
-   * Value of this field should contain URL of mailing list archive as specified in <a href="http://tools.ietf.org/html/rfc2369">RFC 2369</a>.
-   */
-  private static final String LIST_ARCHIVE_HEADER = "List-Archive";
-
-  /**
-   * Email Header Field: "In-Reply-To".
-   * Value of this field should contain related message identifier as specified in <a href="http://tools.ietf.org/html/rfc2822">RFC 2822</a>.
-   */
-  private static final String IN_REPLY_TO_HEADER = "In-Reply-To";
-
-  /**
-   * Email Header Field: "References".
-   * Value of this field should contain related message identifier as specified in <a href="http://tools.ietf.org/html/rfc2822">RFC 2822</a>
-   */
-  private static final String REFERENCES_HEADER = "References";
-
-  private static final String FROM_NAME_DEFAULT = "SonarQube";
-  private static final String SUBJECT_DEFAULT = "Notification";
-
-  private EmailSettings configuration;
-  private EmailTemplate[] templates;
-  private UserFinder userFinder;
-
-  public EmailNotificationChannel(EmailSettings configuration, EmailTemplate[] templates, UserFinder userFinder) {
-    this.configuration = configuration;
-    this.templates = templates;
-    this.userFinder = userFinder;
-  }
-
-  @Override
-  public void deliver(Notification notification, String username) {
-    User user = userFinder.findByLogin(username);
-    if (StringUtils.isBlank(user.getEmail())) {
-      LOG.debug("Email not defined for user: " + username);
-      return;
-    }
-    EmailMessage emailMessage = format(notification);
-    if (emailMessage != null) {
-      emailMessage.setTo(user.getEmail());
-      deliver(emailMessage);
-    }
-  }
-
-  private EmailMessage format(Notification notification) {
-    for (EmailTemplate template : templates) {
-      EmailMessage email = template.format(notification);
-      if (email != null) {
-        return email;
-      }
-    }
-    LOG.warn("Email template not found for notification: {}", notification);
-    return null;
-  }
-
-  /**
-   * Visibility has been relaxed for tests.
-   */
-  void deliver(EmailMessage emailMessage) {
-    if (StringUtils.isBlank(configuration.getSmtpHost())) {
-      LOG.debug("SMTP host was not configured - email will not be sent");
-      return;
-    }
-    try {
-      send(emailMessage);
-    } catch (EmailException e) {
-      LOG.error("Unable to send email", e);
-    }
-  }
-
-  private void send(EmailMessage emailMessage) throws EmailException {
-    // Trick to correctly initilize javax.mail library
-    ClassLoader classloader = Thread.currentThread().getContextClassLoader();
-    Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
-
-    try {
-      LOG.debug("Sending email: {}", emailMessage);
-      String host = null;
-      try {
-        host = new URL(configuration.getServerBaseURL()).getHost();
-      } catch (MalformedURLException e) {
-        // ignore
-      }
-
-      SimpleEmail email = new SimpleEmail();
-      if (StringUtils.isNotBlank(host)) {
-        /*
-         * Set headers for proper threading: GMail will not group messages, even if they have same subject, but don't have "In-Reply-To" and
-         * "References" headers. TODO investigate threading in other clients like KMail, Thunderbird, Outlook
-         */
-        if (StringUtils.isNotEmpty(emailMessage.getMessageId())) {
-          String messageId = "<" + emailMessage.getMessageId() + "@" + host + ">";
-          email.addHeader(IN_REPLY_TO_HEADER, messageId);
-          email.addHeader(REFERENCES_HEADER, messageId);
-        }
-        // Set headers for proper filtering
-        email.addHeader(LIST_ID_HEADER, "SonarQube <sonar." + host + ">");
-        email.addHeader(LIST_ARCHIVE_HEADER, configuration.getServerBaseURL());
-      }
-      // Set general information
-      email.setCharset("UTF-8");
-      String from = StringUtils.isBlank(emailMessage.getFrom()) ? FROM_NAME_DEFAULT : (emailMessage.getFrom() + " (SonarQube)");
-      email.setFrom(configuration.getFrom(), from);
-      email.addTo(emailMessage.getTo(), " ");
-      String subject = StringUtils.defaultIfBlank(StringUtils.trimToEmpty(configuration.getPrefix()) + " ", "")
-        + StringUtils.defaultString(emailMessage.getSubject(), SUBJECT_DEFAULT);
-      email.setSubject(subject);
-      email.setMsg(emailMessage.getMessage());
-      // Send
-      email.setHostName(configuration.getSmtpHost());
-      configureSecureConnection(email);
-      if (StringUtils.isNotBlank(configuration.getSmtpUsername()) || StringUtils.isNotBlank(configuration.getSmtpPassword())) {
-        email.setAuthentication(configuration.getSmtpUsername(), configuration.getSmtpPassword());
-      }
-      email.setSocketConnectionTimeout(SOCKET_TIMEOUT);
-      email.setSocketTimeout(SOCKET_TIMEOUT);
-      email.send();
-
-    } finally {
-      Thread.currentThread().setContextClassLoader(classloader);
-    }
-  }
-
-  private void configureSecureConnection(SimpleEmail email) {
-    if (StringUtils.equalsIgnoreCase(configuration.getSecureConnection(), "ssl")) {
-      email.setSSLOnConnect(true);
-      email.setSslSmtpPort(String.valueOf(configuration.getSmtpPort()));
-
-      // this port is not used except in EmailException message, that's why it's set with the same value than SSL port.
-      // It prevents from getting bad message.
-      email.setSmtpPort(configuration.getSmtpPort());
-    } else if (StringUtils.equalsIgnoreCase(configuration.getSecureConnection(), "starttls")) {
-      email.setStartTLSEnabled(true);
-      email.setStartTLSRequired(true);
-      email.setSmtpPort(configuration.getSmtpPort());
-    } else if (StringUtils.isBlank(configuration.getSecureConnection())) {
-      email.setSmtpPort(configuration.getSmtpPort());
-    } else {
-      throw new SonarException("Unknown type of SMTP secure connection: " + configuration.getSecureConnection());
-    }
-  }
-
-  /**
-   * Send test email. This method called from Ruby.
-   *
-   * @throws EmailException when unable to send
-   */
-  public void sendTestEmail(String toAddress, String subject, String message) throws EmailException {
-    try {
-      EmailMessage emailMessage = new EmailMessage();
-      emailMessage.setTo(toAddress);
-      emailMessage.setSubject(subject);
-      emailMessage.setMessage(message);
-      send(emailMessage);
-    } catch (EmailException e) {
-      LOG.error("Fail to send test email to: " + toAddress, e);
-      throw e;
-    }
-  }
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notifications/email/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/notifications/email/package-info.java
deleted file mode 100644 (file)
index a81d297..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.server.notifications.email;
-
-import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/notifications/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/notifications/package-info.java
deleted file mode 100644 (file)
index 3426204..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.
- */
-
-@ParametersAreNonnullByDefault
-package org.sonar.server.notifications;
-
-import javax.annotation.ParametersAreNonnullByDefault;
index ca0260e5dea60e57ffceb1270b7501c25004df88..21f69f49d6526dabfd662280b6fbb20d18eba087 100644 (file)
@@ -38,7 +38,7 @@ import org.sonar.core.issue.IssueUpdater;
 import org.sonar.core.issue.workflow.FunctionExecutor;
 import org.sonar.core.issue.workflow.IssueWorkflow;
 import org.sonar.core.metric.DefaultMetricFinder;
-import org.sonar.core.notification.DefaultNotificationManager;
+import org.sonar.server.notification.DefaultNotificationManager;
 import org.sonar.core.permission.PermissionFacade;
 import org.sonar.core.qualitygate.db.ProjectQgateAssociationDao;
 import org.sonar.core.qualitygate.db.QualityGateConditionDao;
@@ -171,10 +171,10 @@ import org.sonar.server.measure.ws.ManualMeasuresWs;
 import org.sonar.server.measure.ws.TimeMachineWs;
 import org.sonar.server.metric.CoreCustomMetrics;
 import org.sonar.server.metric.ws.MetricsWsModule;
-import org.sonar.server.notifications.NotificationCenter;
-import org.sonar.server.notifications.NotificationService;
-import org.sonar.server.notifications.email.AlertsEmailTemplate;
-import org.sonar.server.notifications.email.EmailNotificationChannel;
+import org.sonar.server.notification.NotificationCenter;
+import org.sonar.server.notification.NotificationService;
+import org.sonar.server.notification.email.AlertsEmailTemplate;
+import org.sonar.server.notification.email.EmailNotificationChannel;
 import org.sonar.server.permission.InternalPermissionService;
 import org.sonar.server.permission.InternalPermissionTemplateService;
 import org.sonar.server.permission.PermissionFinder;
index 7fb81786e6c91f08cdd0f5a05bb3bd49fffe1adf..a66e2c4085e2b4d963bd75fad3f7a02ea899f4d8 100644 (file)
@@ -40,7 +40,7 @@ import org.sonar.server.computation.issue.RuleCache;
 import org.sonar.server.issue.notification.IssueChangeNotification;
 import org.sonar.server.issue.notification.NewIssuesNotification;
 import org.sonar.server.issue.notification.NewIssuesNotificationFactory;
-import org.sonar.server.notifications.NotificationService;
+import org.sonar.server.notification.NotificationService;
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeastOnce;
index 49bb6f7e6f02d93a5cf54e528899f360e2ae76ea..d2b2f653eb9e6b9d5229473c2a7a7b7c51af5df8 100644 (file)
@@ -24,8 +24,8 @@ import com.google.common.collect.Multimap;
 import org.junit.Test;
 import org.sonar.api.notifications.Notification;
 import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.core.notification.NotificationDispatcher;
-import org.sonar.core.notification.NotificationManager;
+import org.sonar.server.notification.NotificationDispatcher;
+import org.sonar.server.notification.NotificationManager;
 
 import static org.mockito.Mockito.*;
 
index fdcefadb1bae5e82219b5d85b54d21f6cb5d81f2..a11fd69465c53b4f53a1a03d13e231f5cd7f1e28 100644 (file)
@@ -27,9 +27,9 @@ import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.api.notifications.*;
-import org.sonar.core.notification.NotificationDispatcher;
-import org.sonar.core.notification.NotificationDispatcherMetadata;
-import org.sonar.core.notification.NotificationManager;
+import org.sonar.server.notification.NotificationDispatcher;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.NotificationManager;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.*;
index 4f42809dfb7c91ddcb238db8e5426140cdb6668e..91061b4cb7e0a9f934f52aa8b552bc2ca62fef9e 100644 (file)
@@ -25,9 +25,9 @@ import org.junit.Test;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.notifications.Notification;
 import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.core.notification.NotificationDispatcher;
-import org.sonar.core.notification.NotificationDispatcherMetadata;
-import org.sonar.core.notification.NotificationManager;
+import org.sonar.server.notification.NotificationDispatcher;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.NotificationManager;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.*;
index 9173ef7227c86f1f08e008a1a33c2215f2a71bd8..b697cdfe159c0167f8e18f1cb8b135fed47f6b01 100644 (file)
@@ -26,8 +26,8 @@ import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.notifications.Notification;
 import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.core.notification.NotificationDispatcher;
-import org.sonar.core.notification.NotificationManager;
+import org.sonar.server.notification.NotificationDispatcher;
+import org.sonar.server.notification.NotificationManager;
 
 import static org.mockito.Mockito.*;
 
index 656d3bb4231a487561e7a25b94fe80cc8f55ec46..ec975b529661601fa259b18e45db0a265f72a42f 100644 (file)
@@ -25,8 +25,8 @@ import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.notifications.Notification;
 import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.core.notification.NotificationDispatcher;
-import org.sonar.core.notification.NotificationManager;
+import org.sonar.server.notification.NotificationDispatcher;
+import org.sonar.server.notification.NotificationManager;
 
 import static org.mockito.Mockito.*;
 
diff --git a/server/sonar-server/src/test/java/org/sonar/server/notification/DefaultNotificationManagerTest.java b/server/sonar-server/src/test/java/org/sonar/server/notification/DefaultNotificationManagerTest.java
new file mode 100644 (file)
index 0000000..e534646
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationChannel;
+import org.sonar.core.notification.db.NotificationQueueDao;
+import org.sonar.core.notification.db.NotificationQueueDto;
+import org.sonar.core.properties.PropertiesDao;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import java.io.InvalidClassException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DefaultNotificationManagerTest extends AbstractDbUnitTestCase {
+
+  private DefaultNotificationManager manager;
+
+  @Mock
+  private PropertiesDao propertiesDao;
+
+  @Mock
+  private NotificationDispatcher dispatcher;
+
+  @Mock
+  private NotificationChannel emailChannel;
+
+  @Mock
+  private NotificationChannel twitterChannel;
+
+  @Mock
+  private NotificationQueueDao notificationQueueDao;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    when(dispatcher.getKey()).thenReturn("NewViolations");
+    when(emailChannel.getKey()).thenReturn("Email");
+    when(twitterChannel.getKey()).thenReturn("Twitter");
+
+    manager = new DefaultNotificationManager(new NotificationChannel[] {emailChannel, twitterChannel}, notificationQueueDao, propertiesDao);
+  }
+
+  @Test
+  public void shouldProvideChannelList() {
+    assertThat(manager.getChannels()).containsOnly(emailChannel, twitterChannel);
+
+    manager = new DefaultNotificationManager(notificationQueueDao, propertiesDao);
+    assertThat(manager.getChannels()).hasSize(0);
+  }
+
+  @Test
+  public void shouldPersist() {
+    Notification notification = new Notification("test");
+    manager.scheduleForSending(notification);
+
+    verify(notificationQueueDao, only()).insert(any(List.class));
+  }
+
+  @Test
+  public void shouldGetFromQueueAndDelete() {
+    Notification notification = new Notification("test");
+    NotificationQueueDto dto = NotificationQueueDto.toNotificationQueueDto(notification);
+    List<NotificationQueueDto> dtos = Arrays.asList(dto);
+    when(notificationQueueDao.findOldest(1)).thenReturn(dtos);
+
+    assertThat(manager.getFromQueue()).isNotNull();
+
+    InOrder inOrder = inOrder(notificationQueueDao);
+    inOrder.verify(notificationQueueDao).findOldest(1);
+    inOrder.verify(notificationQueueDao).delete(dtos);
+  }
+
+  // SONAR-4739
+  @Test
+  public void shouldNotFailWhenUnableToDeserialize() throws Exception {
+    NotificationQueueDto dto1 = mock(NotificationQueueDto.class);
+    when(dto1.toNotification()).thenThrow(new InvalidClassException("Pouet"));
+    List<NotificationQueueDto> dtos = Arrays.asList(dto1);
+    when(notificationQueueDao.findOldest(1)).thenReturn(dtos);
+
+    manager = spy(manager);
+    assertThat(manager.getFromQueue()).isNull();
+    assertThat(manager.getFromQueue()).isNull();
+
+    verify(manager, times(1)).logDeserializationIssue();
+  }
+
+  @Test
+  public void shouldFindNoRecipient() {
+    assertThat(manager.findSubscribedRecipientsForDispatcher(dispatcher, 45).asMap().entrySet()).hasSize(0);
+  }
+
+  @Test
+  public void shouldFindSubscribedRecipientForGivenResource() {
+    when(propertiesDao.findUsersForNotification("NewViolations", "Email", 45L)).thenReturn(Lists.newArrayList("user1", "user2"));
+    when(propertiesDao.findUsersForNotification("NewViolations", "Email", null)).thenReturn(Lists.newArrayList("user1", "user3"));
+    when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", 56L)).thenReturn(Lists.newArrayList("user2"));
+    when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", null)).thenReturn(Lists.newArrayList("user3"));
+    when(propertiesDao.findUsersForNotification("NewAlerts", "Twitter", null)).thenReturn(Lists.newArrayList("user4"));
+
+    Multimap<String, NotificationChannel> multiMap = manager.findSubscribedRecipientsForDispatcher(dispatcher, 45);
+    assertThat(multiMap.entries()).hasSize(4);
+
+    Map<String, Collection<NotificationChannel>> map = multiMap.asMap();
+    assertThat(map.get("user1")).containsOnly(emailChannel);
+    assertThat(map.get("user2")).containsOnly(emailChannel);
+    assertThat(map.get("user3")).containsOnly(emailChannel, twitterChannel);
+    assertThat(map.get("user4")).isNull();
+  }
+
+  @Test
+  public void shouldFindSubscribedRecipientForNoResource() {
+    when(propertiesDao.findUsersForNotification("NewViolations", "Email", 45L)).thenReturn(Lists.newArrayList("user1", "user2"));
+    when(propertiesDao.findUsersForNotification("NewViolations", "Email", null)).thenReturn(Lists.newArrayList("user1", "user3"));
+    when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", 56L)).thenReturn(Lists.newArrayList("user2"));
+    when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", null)).thenReturn(Lists.newArrayList("user3"));
+    when(propertiesDao.findUsersForNotification("NewAlerts", "Twitter", null)).thenReturn(Lists.newArrayList("user4"));
+
+    Multimap<String, NotificationChannel> multiMap = manager.findSubscribedRecipientsForDispatcher(dispatcher, (Integer) null);
+    assertThat(multiMap.entries()).hasSize(3);
+
+    Map<String, Collection<NotificationChannel>> map = multiMap.asMap();
+    assertThat(map.get("user1")).containsOnly(emailChannel);
+    assertThat(map.get("user3")).containsOnly(emailChannel, twitterChannel);
+    assertThat(map.get("user2")).isNull();
+    assertThat(map.get("user4")).isNull();
+  }
+
+  @Test
+  public void findNotificationSubscribers() {
+    when(propertiesDao.findNotificationSubscribers("NewViolations", "Email", "struts")).thenReturn(Lists.newArrayList("user1", "user2"));
+    when(propertiesDao.findNotificationSubscribers("NewViolations", "Twitter", "struts")).thenReturn(Lists.newArrayList("user2"));
+
+    Multimap<String, NotificationChannel> multiMap = manager.findNotificationSubscribers(dispatcher, "struts");
+    assertThat(multiMap.entries()).hasSize(3);
+
+    Map<String, Collection<NotificationChannel>> map = multiMap.asMap();
+    assertThat(map.get("user1")).containsOnly(emailChannel);
+    assertThat(map.get("user2")).containsOnly(emailChannel, twitterChannel);
+    assertThat(map.get("other")).isNull();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationCenterTest.java b/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationCenterTest.java
new file mode 100644 (file)
index 0000000..e4d7b28
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.api.notifications.NotificationChannel;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class NotificationCenterTest {
+
+  @Mock
+  private NotificationChannel emailChannel;
+
+  @Mock
+  private NotificationChannel gtalkChannel;
+
+  private NotificationCenter notificationCenter;
+
+  @Before
+  public void init() {
+    MockitoAnnotations.initMocks(this);
+
+    NotificationDispatcherMetadata metadata1 = NotificationDispatcherMetadata.create("Dispatcher1").setProperty("global", "true").setProperty("on-project", "true");
+    NotificationDispatcherMetadata metadata2 = NotificationDispatcherMetadata.create("Dispatcher2").setProperty("global", "true");
+    NotificationDispatcherMetadata metadata3 = NotificationDispatcherMetadata.create("Dispatcher3").setProperty("global", "FOO").setProperty("on-project", "BAR");
+
+    notificationCenter = new NotificationCenter(
+        new NotificationDispatcherMetadata[] {metadata1, metadata2, metadata3},
+        new NotificationChannel[] {emailChannel, gtalkChannel}
+        );
+  }
+
+  @Test
+  public void shouldReturnChannels() {
+    assertThat(notificationCenter.getChannels()).containsOnly(emailChannel, gtalkChannel);
+  }
+
+  @Test
+  public void shouldReturnDispatcherKeysForSpecificPropertyValue() {
+    assertThat(notificationCenter.getDispatcherKeysForProperty("global", "true")).containsOnly("Dispatcher1", "Dispatcher2");
+  }
+
+  @Test
+  public void shouldReturnDispatcherKeysForExistenceOfProperty() {
+    assertThat(notificationCenter.getDispatcherKeysForProperty("on-project", null)).containsOnly("Dispatcher1", "Dispatcher3");
+  }
+
+  @Test
+  public void testDefaultConstructors() {
+    notificationCenter = new NotificationCenter(new NotificationChannel[] {emailChannel});
+    assertThat(notificationCenter.getChannels()).hasSize(1);
+
+    notificationCenter = new NotificationCenter();
+    assertThat(notificationCenter.getChannels()).hasSize(0);
+
+    notificationCenter = new NotificationCenter(new NotificationDispatcherMetadata[] {NotificationDispatcherMetadata.create("Dispatcher1").setProperty("global", "true")});
+    assertThat(notificationCenter.getChannels()).hasSize(0);
+    assertThat(notificationCenter.getDispatcherKeysForProperty("global", null)).hasSize(1);
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationChannelTest.java b/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationChannelTest.java
new file mode 100644 (file)
index 0000000..e0a0c82
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationChannel;
+
+public class NotificationChannelTest {
+
+  @Test
+  public void defaultMethods() {
+    NotificationChannel channel = new FakeNotificationChannel();
+    assertThat(channel.getKey(), is("FakeNotificationChannel"));
+    assertThat(channel.toString(), is("FakeNotificationChannel"));
+  }
+
+  class FakeNotificationChannel extends NotificationChannel {
+    @Override
+    public void deliver(Notification notification, String username) {
+    }
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationDispatcherMetadataTest.java b/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationDispatcherMetadataTest.java
new file mode 100644 (file)
index 0000000..7874ab5
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class NotificationDispatcherMetadataTest {
+
+  private NotificationDispatcherMetadata metadata;
+
+  @Before
+  public void init() {
+    metadata = NotificationDispatcherMetadata.create("NewViolations").setProperty("global", "true");
+  }
+
+  @Test
+  public void shouldReturnDispatcherKey() {
+    assertThat(metadata.getDispatcherKey()).isEqualTo("NewViolations");
+  }
+
+  @Test
+  public void shouldReturnProperty() {
+    assertThat(metadata.getProperty("global")).isEqualTo("true");
+    assertThat(metadata.getProperty("per-project")).isNull();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationDispatcherTest.java b/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationDispatcherTest.java
new file mode 100644 (file)
index 0000000..e6ec1a7
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationChannel;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class NotificationDispatcherTest {
+
+  @Mock
+  private NotificationChannel channel;
+
+  @Mock
+  private Notification notification;
+
+  @Mock
+  private NotificationDispatcher.Context context;
+
+  @Before
+  public void init() {
+    MockitoAnnotations.initMocks(this);
+    when(notification.getType()).thenReturn("event1");
+  }
+
+  @Test
+  public void defaultMethods() {
+    NotificationDispatcher dispatcher = new FakeGenericNotificationDispatcher();
+    assertThat(dispatcher.getKey(), is("FakeGenericNotificationDispatcher"));
+    assertThat(dispatcher.toString(), is("FakeGenericNotificationDispatcher"));
+  }
+
+  @Test
+  public void shouldAlwaysRunDispatchForGenericDispatcher() {
+    NotificationDispatcher dispatcher = new FakeGenericNotificationDispatcher();
+    dispatcher.performDispatch(notification, context);
+
+    verify(context, times(1)).addUser("user1", channel);
+  }
+
+  @Test
+  public void shouldNotAlwaysRunDispatchForSpecificDispatcher() {
+    NotificationDispatcher dispatcher = new FakeSpecificNotificationDispatcher();
+
+    // a "event1" notif is sent
+    dispatcher.performDispatch(notification, context);
+    verify(context, never()).addUser("user1", channel);
+
+    // now, a "specific-event" notif is sent
+    when(notification.getType()).thenReturn("specific-event");
+    dispatcher.performDispatch(notification, context);
+    verify(context, times(1)).addUser("user1", channel);
+  }
+
+  class FakeGenericNotificationDispatcher extends NotificationDispatcher {
+    @Override
+    public void dispatch(Notification notification, Context context) {
+      context.addUser("user1", channel);
+    }
+  }
+
+  class FakeSpecificNotificationDispatcher extends NotificationDispatcher {
+
+    public FakeSpecificNotificationDispatcher() {
+      super("specific-event");
+    }
+
+    @Override
+    public void dispatch(Notification notification, Context context) {
+      context.addUser("user1", channel);
+    }
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationServiceTest.java
new file mode 100644 (file)
index 0000000..515c427
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification;
+
+import com.google.common.collect.Sets;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.config.Settings;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationChannel;
+import org.sonar.core.properties.PropertiesDao;
+import org.sonar.jpa.session.DatabaseSessionFactory;
+import org.sonar.server.db.DbClient;
+
+import java.util.Arrays;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+public class NotificationServiceTest {
+  private static String CREATOR_SIMON = "simon";
+  private static String CREATOR_EVGENY = "evgeny";
+  private static String ASSIGNEE_SIMON = "simon";
+
+  DefaultNotificationManager manager = mock(DefaultNotificationManager.class);
+  Notification notification = mock(Notification.class);
+  NotificationChannel emailChannel = mock(NotificationChannel.class);
+  NotificationChannel gtalkChannel = mock(NotificationChannel.class);
+  NotificationDispatcher commentOnIssueAssignedToMe = mock(NotificationDispatcher.class);
+  NotificationDispatcher commentOnIssueCreatedByMe = mock(NotificationDispatcher.class);
+  NotificationDispatcher qualityGateChange = mock(NotificationDispatcher.class);
+  DbClient dbClient = mock(DbClient.class);
+
+  private NotificationService service;
+
+  private void setUpMocks() {
+    when(emailChannel.getKey()).thenReturn("email");
+    when(gtalkChannel.getKey()).thenReturn("gtalk");
+    when(commentOnIssueAssignedToMe.getKey()).thenReturn("CommentOnIssueAssignedToMe");
+    when(commentOnIssueAssignedToMe.getType()).thenReturn("issue-changes");
+    when(commentOnIssueCreatedByMe.getKey()).thenReturn("CommentOnIssueCreatedByMe");
+    when(commentOnIssueCreatedByMe.getType()).thenReturn("issue-changes");
+    when(qualityGateChange.getKey()).thenReturn("QGateChange");
+    when(qualityGateChange.getType()).thenReturn("qgate-changes");
+    when(manager.getFromQueue()).thenReturn(notification).thenReturn(null);
+
+    Settings settings = new Settings().setProperty("sonar.notifications.delay", 1L);
+
+    service = new NotificationService(settings, manager,
+      dbClient, mock(DatabaseSessionFactory.class),
+      new NotificationDispatcher[] {commentOnIssueAssignedToMe, commentOnIssueCreatedByMe, qualityGateChange});
+  }
+
+  /**
+   * Given:
+   * Simon wants to receive notifications by email on comments for reviews assigned to him or created by him.
+   *
+   * When:
+   * Freddy adds comment to review created by Simon and assigned to Simon.
+   *
+   * Then:
+   * Only one notification should be delivered to Simon by Email.
+   */
+  @Test
+  public void scenario1() {
+    setUpMocks();
+    doAnswer(addUser(ASSIGNEE_SIMON, emailChannel)).when(commentOnIssueAssignedToMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
+    doAnswer(addUser(CREATOR_SIMON, emailChannel)).when(commentOnIssueCreatedByMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
+
+    service.start();
+    verify(emailChannel, timeout(2000)).deliver(notification, ASSIGNEE_SIMON);
+    service.stop();
+
+    verify(gtalkChannel, never()).deliver(notification, ASSIGNEE_SIMON);
+  }
+
+  /**
+   * Given:
+   * Evgeny wants to receive notification by GTalk on comments for reviews created by him.
+   * Simon wants to receive notification by Email on comments for reviews assigned to him.
+   *
+   * When:
+   * Freddy adds comment to review created by Evgeny and assigned to Simon.
+   *
+   * Then:
+   * Two notifications should be delivered - one to Simon by Email and another to Evgeny by GTalk.
+   */
+  @Test
+  public void scenario2() {
+    setUpMocks();
+    doAnswer(addUser(ASSIGNEE_SIMON, emailChannel)).when(commentOnIssueAssignedToMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
+    doAnswer(addUser(CREATOR_EVGENY, gtalkChannel)).when(commentOnIssueCreatedByMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
+
+    service.start();
+    verify(emailChannel, timeout(2000)).deliver(notification, ASSIGNEE_SIMON);
+    verify(gtalkChannel, timeout(2000)).deliver(notification, CREATOR_EVGENY);
+    service.stop();
+
+    verify(emailChannel, never()).deliver(notification, CREATOR_EVGENY);
+    verify(gtalkChannel, never()).deliver(notification, ASSIGNEE_SIMON);
+  }
+
+  /**
+   * Given:
+   * Simon wants to receive notifications by Email and GTLak on comments for reviews assigned to him.
+   *
+   * When:
+   * Freddy adds comment to review created by Evgeny and assigned to Simon.
+   *
+   * Then:
+   * Two notifications should be delivered to Simon - one by Email and another by GTalk.
+   */
+  @Test
+  public void scenario3() {
+    setUpMocks();
+    doAnswer(addUser(ASSIGNEE_SIMON, new NotificationChannel[] {emailChannel, gtalkChannel}))
+      .when(commentOnIssueAssignedToMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
+
+    service.start();
+    verify(emailChannel, timeout(2000)).deliver(notification, ASSIGNEE_SIMON);
+    verify(gtalkChannel, timeout(2000)).deliver(notification, ASSIGNEE_SIMON);
+    service.stop();
+
+    verify(emailChannel, never()).deliver(notification, CREATOR_EVGENY);
+    verify(gtalkChannel, never()).deliver(notification, CREATOR_EVGENY);
+  }
+
+  /**
+   * Given:
+   * Nobody wants to receive notifications.
+   *
+   * When:
+   * Freddy adds comment to review created by Evgeny and assigned to Simon.
+   *
+   * Then:
+   * No notifications.
+   */
+  @Test
+  public void scenario4() {
+    setUpMocks();
+
+    service.start();
+    service.stop();
+
+    verify(emailChannel, never()).deliver(any(Notification.class), anyString());
+    verify(gtalkChannel, never()).deliver(any(Notification.class), anyString());
+  }
+
+  // SONAR-4548
+  @Test
+  public void shouldNotStopWhenException() {
+    setUpMocks();
+    when(manager.getFromQueue()).thenThrow(new RuntimeException("Unexpected exception")).thenReturn(notification).thenReturn(null);
+    doAnswer(addUser(ASSIGNEE_SIMON, emailChannel)).when(commentOnIssueAssignedToMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
+    doAnswer(addUser(CREATOR_SIMON, emailChannel)).when(commentOnIssueCreatedByMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
+
+    service.start();
+    verify(emailChannel, timeout(2000)).deliver(notification, ASSIGNEE_SIMON);
+    service.stop();
+
+    verify(gtalkChannel, never()).deliver(notification, ASSIGNEE_SIMON);
+  }
+
+  @Test
+  public void shouldNotAddNullAsUser() {
+    setUpMocks();
+    doAnswer(addUser(null, gtalkChannel)).when(commentOnIssueCreatedByMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
+
+    service.start();
+    service.stop();
+
+    verify(emailChannel, never()).deliver(any(Notification.class), anyString());
+    verify(gtalkChannel, never()).deliver(any(Notification.class), anyString());
+  }
+
+  @Test
+  public void getDispatchers() {
+    setUpMocks();
+
+    assertThat(service.getDispatchers()).containsOnly(commentOnIssueAssignedToMe, commentOnIssueCreatedByMe, qualityGateChange);
+  }
+
+  @Test
+  public void getDispatchers_empty() {
+    Settings settings = new Settings().setProperty("sonar.notifications.delay", 1L);
+
+    service = new NotificationService(settings, manager, dbClient, mock(DatabaseSessionFactory.class));
+    assertThat(service.getDispatchers()).hasSize(0);
+  }
+
+  @Test
+  public void shouldLogEvery10Minutes() {
+    setUpMocks();
+    // Emulate 2 notifications in DB
+    when(manager.getFromQueue()).thenReturn(notification).thenReturn(notification).thenReturn(null);
+    when(manager.count()).thenReturn(1L).thenReturn(0L);
+    service = spy(service);
+    // Emulate processing of each notification take 10 min to have a log each time
+    when(service.now()).thenReturn(0L).thenReturn(10 * 60 * 1000 + 1L).thenReturn(20 * 60 * 1000 + 2L);
+    service.start();
+    verify(service, timeout(200)).log(1, 1, 10);
+    verify(service, timeout(200)).log(2, 0, 20);
+    service.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});
+  }
+
+  private static Answer<Object> addUser(final String user, final NotificationChannel[] channels) {
+    return new Answer<Object>() {
+      public Object answer(InvocationOnMock invocation) {
+        for (NotificationChannel channel : channels) {
+          ((NotificationDispatcher.Context) invocation.getArguments()[1]).addUser(user, channel);
+        }
+        return null;
+      }
+    };
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationTest.java b/server/sonar-server/src/test/java/org/sonar/server/notification/NotificationTest.java
new file mode 100644 (file)
index 0000000..d90b000
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.notifications.Notification;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class NotificationTest {
+
+  private Notification notification;
+
+  @Before
+  public void init() {
+    notification = new Notification("alerts").setDefaultMessage("There are new alerts").setFieldValue("alertCount", "42");
+  }
+
+  @Test
+  public void shouldReturnType() {
+    assertThat(notification.getType()).isEqualTo("alerts");
+  }
+
+  @Test
+  public void shouldReturnDefaultMessage() {
+    assertThat(notification.getDefaultMessage()).isEqualTo("There are new alerts");
+  }
+
+  @Test
+  public void shouldReturnToStringIfDefaultMessageNotSet() {
+    notification = new Notification("alerts").setFieldValue("alertCount", "42");
+    System.out.println(notification);
+    assertThat(notification.getDefaultMessage()).contains("type='alerts'");
+    assertThat(notification.getDefaultMessage()).contains("fields={alertCount=42}");
+  }
+
+  @Test
+  public void shouldReturnField() {
+    assertThat(notification.getFieldValue("alertCount")).isEqualTo("42");
+    assertThat(notification.getFieldValue("fake")).isNull();
+
+    // default message is stored as field as well
+    assertThat(notification.getFieldValue("default_message")).isEqualTo("There are new alerts");
+  }
+
+  @Test
+  public void shouldEqual() {
+    assertThat(notification.equals("")).isFalse();
+    assertThat(notification.equals(null)).isFalse();
+    assertThat(notification.equals(notification)).isTrue();
+
+    Notification otherNotif = new Notification("alerts").setDefaultMessage("There are new alerts").setFieldValue("alertCount", "42");
+    assertThat(otherNotif).isEqualTo(notification);
+
+    otherNotif = new Notification("alerts").setDefaultMessage("There are new alerts").setFieldValue("alertCount", "15000");
+    assertThat(otherNotif).isNotEqualTo(notification);
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/notification/email/AlertsEmailTemplateTest.java b/server/sonar-server/src/test/java/org/sonar/server/notification/email/AlertsEmailTemplateTest.java
new file mode 100644 (file)
index 0000000..3d81a49
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification.email;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.config.EmailSettings;
+import org.sonar.api.notifications.Notification;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class AlertsEmailTemplateTest {
+
+  private AlertsEmailTemplate template;
+
+  @Before
+  public void setUp() {
+    EmailSettings configuration = mock(EmailSettings.class);
+    when(configuration.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org");
+    template = new AlertsEmailTemplate(configuration);
+  }
+
+  @Test
+  public void shouldNotFormatIfNotCorrectNotification() {
+    Notification notification = new Notification("other-notif");
+    EmailMessage message = template.format(notification);
+    assertThat(message, nullValue());
+  }
+
+  @Test
+  public void shouldFormatAlertWithSeveralMessages() {
+    Notification notification = createNotification("Orange (was Red)", "violations > 4, coverage < 75%", "WARN", "false");
+
+    EmailMessage message = template.format(notification);
+    assertThat(message.getMessageId(), is("alerts/45"));
+    assertThat(message.getSubject(), is("Quality gate status changed on \"Foo\""));
+    assertThat(message.getMessage(), is("" +
+      "Project: Foo\n" +
+      "Quality gate status: Orange (was Red)\n" +
+      "\n" +
+      "Quality gate thresholds:\n" +
+      "  - violations > 4\n" +
+      "  - coverage < 75%\n" +
+      "\n" +
+      "See it in SonarQube: http://nemo.sonarsource.org/dashboard/index/org.sonar.foo:foo"));
+  }
+
+  @Test
+  public void shouldFormatNewAlertWithSeveralMessages() {
+    Notification notification = createNotification("Orange (was Red)", "violations > 4, coverage < 75%", "WARN", "true");
+
+    EmailMessage message = template.format(notification);
+    assertThat(message.getMessageId(), is("alerts/45"));
+    assertThat(message.getSubject(), is("New quality gate threshold reached on \"Foo\""));
+    assertThat(message.getMessage(), is("" +
+      "Project: Foo\n" +
+      "Quality gate status: Orange (was Red)\n" +
+      "\n" +
+      "New quality gate thresholds:\n" +
+      "  - violations > 4\n" +
+      "  - coverage < 75%\n" +
+      "\n" +
+      "See it in SonarQube: http://nemo.sonarsource.org/dashboard/index/org.sonar.foo:foo"));
+  }
+
+  @Test
+  public void shouldFormatNewAlertWithOneMessage() {
+    Notification notification = createNotification("Orange (was Red)", "violations > 4", "WARN", "true");
+
+    EmailMessage message = template.format(notification);
+    assertThat(message.getMessageId(), is("alerts/45"));
+    assertThat(message.getSubject(), is("New quality gate threshold reached on \"Foo\""));
+    assertThat(message.getMessage(), is("" +
+      "Project: Foo\n" +
+      "Quality gate status: Orange (was Red)\n" +
+      "\n" +
+      "New quality gate threshold: violations > 4\n" +
+      "\n" +
+      "See it in SonarQube: http://nemo.sonarsource.org/dashboard/index/org.sonar.foo:foo"));
+  }
+
+  @Test
+  public void shouldFormatBackToGreenMessage() {
+    Notification notification = createNotification("Green (was Red)", "", "OK", "false");
+
+    EmailMessage message = template.format(notification);
+    assertThat(message.getMessageId(), is("alerts/45"));
+    assertThat(message.getSubject(), is("\"Foo\" is back to green"));
+    assertThat(message.getMessage(), is("" +
+      "Project: Foo\n" +
+      "Quality gate status: Green (was Red)\n" +
+      "\n" +
+      "\n" +
+      "See it in SonarQube: http://nemo.sonarsource.org/dashboard/index/org.sonar.foo:foo"));
+  }
+
+  private Notification createNotification(String alertName, String alertText, String alertLevel, String isNewAlert) {
+    Notification notification = new Notification("alerts")
+        .setFieldValue("projectName", "Foo")
+        .setFieldValue("projectKey", "org.sonar.foo:foo")
+        .setFieldValue("projectId", "45")
+        .setFieldValue("alertName", alertName)
+        .setFieldValue("alertText", alertText)
+        .setFieldValue("alertLevel", alertLevel)
+        .setFieldValue("isNewAlert", isNewAlert);
+    return notification;
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/notification/email/EmailNotificationChannelTest.java b/server/sonar-server/src/test/java/org/sonar/server/notification/email/EmailNotificationChannelTest.java
new file mode 100644 (file)
index 0000000..e7cbcce
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.notification.email;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.List;
+import javax.mail.internet.MimeMessage;
+import org.apache.commons.mail.EmailException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.EmailSettings;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+import org.subethamail.wiser.Wiser;
+import org.subethamail.wiser.WiserMessage;
+
+import static junit.framework.Assert.fail;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class EmailNotificationChannelTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  private int port;
+  private Wiser server;
+  private EmailSettings configuration;
+  private EmailNotificationChannel channel;
+
+  private static int getNextAvailablePort() {
+    try {
+      ServerSocket socket = new ServerSocket(0);
+      int unusedPort = socket.getLocalPort();
+      socket.close();
+      return unusedPort;
+    } catch (IOException e) {
+      throw new RuntimeException("Error getting an available port from system", e);
+    }
+  }
+
+  @Before
+  public void setUp() {
+    port = getNextAvailablePort();
+    server = new Wiser();
+    server.setPort(port);
+    server.start();
+
+    configuration = mock(EmailSettings.class);
+    channel = new EmailNotificationChannel(configuration, null, null);
+  }
+
+  @After
+  public void tearDown() {
+    server.stop();
+  }
+
+  @Test
+  public void shouldSendTestEmail() throws Exception {
+    configure();
+    channel.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a test message from SonarQube.");
+
+    List<WiserMessage> messages = server.getMessages();
+    assertThat(messages).hasSize(1);
+
+    MimeMessage email = messages.get(0).getMimeMessage();
+    assertThat(email.getHeader("Content-Type", null)).isEqualTo("text/plain; charset=UTF-8");
+    assertThat(email.getHeader("From", ",")).isEqualTo("SonarQube <server@nowhere>");
+    assertThat(email.getHeader("To", null)).isEqualTo("<user@nowhere>");
+    assertThat(email.getHeader("Subject", null)).isEqualTo("[SONARQUBE] Test Message from SonarQube");
+    assertThat((String) email.getContent()).startsWith("This is a test message from SonarQube.");
+  }
+
+  @Test
+  public void shouldThrowAnExceptionWhenUnableToSendTestEmail() {
+    configure();
+    server.stop();
+
+    try {
+      channel.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a test message from SonarQube.");
+      fail();
+    } catch (EmailException e) {
+      // expected
+    }
+  }
+
+  @Test
+  public void shouldNotSendEmailWhenHostnameNotConfigured() {
+    EmailMessage emailMessage = new EmailMessage()
+      .setTo("user@nowhere")
+      .setSubject("Foo")
+      .setMessage("Bar");
+    channel.deliver(emailMessage);
+    assertThat(server.getMessages()).isEmpty();
+  }
+
+  @Test
+  public void shouldSendThreadedEmail() throws Exception {
+    configure();
+    EmailMessage emailMessage = new EmailMessage()
+      .setMessageId("reviews/view/1")
+      .setFrom("Full Username")
+      .setTo("user@nowhere")
+      .setSubject("Review #3")
+      .setMessage("I'll take care of this violation.");
+    channel.deliver(emailMessage);
+
+    List<WiserMessage> messages = server.getMessages();
+    assertThat(messages).hasSize(1);
+
+    MimeMessage email = messages.get(0).getMimeMessage();
+
+    assertThat(email.getHeader("Content-Type", null)).isEqualTo("text/plain; charset=UTF-8");
+
+    assertThat(email.getHeader("In-Reply-To", null)).isEqualTo("<reviews/view/1@nemo.sonarsource.org>");
+    assertThat(email.getHeader("References", null)).isEqualTo("<reviews/view/1@nemo.sonarsource.org>");
+
+    assertThat(email.getHeader("List-ID", null)).isEqualTo("SonarQube <sonar.nemo.sonarsource.org>");
+    assertThat(email.getHeader("List-Archive", null)).isEqualTo("http://nemo.sonarsource.org");
+
+    assertThat(email.getHeader("From", ",")).isEqualTo("\"Full Username (SonarQube)\" <server@nowhere>");
+    assertThat(email.getHeader("To", null)).isEqualTo("<user@nowhere>");
+    assertThat(email.getHeader("Subject", null)).isEqualTo("[SONARQUBE] Review #3");
+    assertThat((String) email.getContent()).startsWith("I'll take care of this violation.");
+  }
+
+  @Test
+  public void shouldSendNonThreadedEmail() throws Exception {
+    configure();
+    EmailMessage emailMessage = new EmailMessage()
+      .setTo("user@nowhere")
+      .setSubject("Foo")
+      .setMessage("Bar");
+    channel.deliver(emailMessage);
+
+    List<WiserMessage> messages = server.getMessages();
+    assertThat(messages).hasSize(1);
+
+    MimeMessage email = messages.get(0).getMimeMessage();
+
+    assertThat(email.getHeader("Content-Type", null)).isEqualTo("text/plain; charset=UTF-8");
+
+    assertThat(email.getHeader("In-Reply-To", null)).isNull();
+    assertThat(email.getHeader("References", null)).isNull();
+
+    assertThat(email.getHeader("List-ID", null)).isEqualTo("SonarQube <sonar.nemo.sonarsource.org>");
+    assertThat(email.getHeader("List-Archive", null)).isEqualTo("http://nemo.sonarsource.org");
+
+    assertThat(email.getHeader("From", null)).isEqualTo("SonarQube <server@nowhere>");
+    assertThat(email.getHeader("To", null)).isEqualTo("<user@nowhere>");
+    assertThat(email.getHeader("Subject", null)).isEqualTo("[SONARQUBE] Foo");
+    assertThat((String) email.getContent()).startsWith("Bar");
+  }
+
+  @Test
+  public void shouldNotThrowAnExceptionWhenUnableToSendEmail() {
+    configure();
+    server.stop();
+
+    EmailMessage emailMessage = new EmailMessage()
+      .setTo("user@nowhere")
+      .setSubject("Foo")
+      .setMessage("Bar");
+    channel.deliver(emailMessage);
+  }
+
+  @Test
+  public void shouldSendTestEmailWithSTARTTLS() {
+    server.getServer().setEnableTLS(true);
+    server.getServer().setRequireTLS(true);
+    configure();
+    when(configuration.getSecureConnection()).thenReturn("STARTTLS");
+
+    try {
+      channel.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a test message from SonarQube.");
+      fail("An SSL exception was expected a a proof that STARTTLS is enabled");
+    } catch (EmailException e) {
+      // We don't have a SSL certificate so we are expecting a SSL error
+      assertThat(e.getCause().getMessage()).isEqualTo("Could not convert socket to TLS");
+    }
+  }
+
+  private void configure() {
+    when(configuration.getSmtpHost()).thenReturn("localhost");
+    when(configuration.getSmtpPort()).thenReturn(port);
+    when(configuration.getFrom()).thenReturn("server@nowhere");
+    when(configuration.getPrefix()).thenReturn("[SONARQUBE]");
+    when(configuration.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org");
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/notifications/NotificationCenterTest.java b/server/sonar-server/src/test/java/org/sonar/server/notifications/NotificationCenterTest.java
deleted file mode 100644 (file)
index ecd6ef5..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.server.notifications;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.core.notification.NotificationDispatcherMetadata;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class NotificationCenterTest {
-
-  @Mock
-  private NotificationChannel emailChannel;
-
-  @Mock
-  private NotificationChannel gtalkChannel;
-
-  private NotificationCenter notificationCenter;
-
-  @Before
-  public void init() {
-    MockitoAnnotations.initMocks(this);
-
-    NotificationDispatcherMetadata metadata1 = NotificationDispatcherMetadata.create("Dispatcher1").setProperty("global", "true").setProperty("on-project", "true");
-    NotificationDispatcherMetadata metadata2 = NotificationDispatcherMetadata.create("Dispatcher2").setProperty("global", "true");
-    NotificationDispatcherMetadata metadata3 = NotificationDispatcherMetadata.create("Dispatcher3").setProperty("global", "FOO").setProperty("on-project", "BAR");
-
-    notificationCenter = new NotificationCenter(
-        new NotificationDispatcherMetadata[] {metadata1, metadata2, metadata3},
-        new NotificationChannel[] {emailChannel, gtalkChannel}
-        );
-  }
-
-  @Test
-  public void shouldReturnChannels() {
-    assertThat(notificationCenter.getChannels()).containsOnly(emailChannel, gtalkChannel);
-  }
-
-  @Test
-  public void shouldReturnDispatcherKeysForSpecificPropertyValue() {
-    assertThat(notificationCenter.getDispatcherKeysForProperty("global", "true")).containsOnly("Dispatcher1", "Dispatcher2");
-  }
-
-  @Test
-  public void shouldReturnDispatcherKeysForExistenceOfProperty() {
-    assertThat(notificationCenter.getDispatcherKeysForProperty("on-project", null)).containsOnly("Dispatcher1", "Dispatcher3");
-  }
-
-  @Test
-  public void testDefaultConstructors() {
-    notificationCenter = new NotificationCenter(new NotificationChannel[] {emailChannel});
-    assertThat(notificationCenter.getChannels()).hasSize(1);
-
-    notificationCenter = new NotificationCenter();
-    assertThat(notificationCenter.getChannels()).hasSize(0);
-
-    notificationCenter = new NotificationCenter(new NotificationDispatcherMetadata[] {NotificationDispatcherMetadata.create("Dispatcher1").setProperty("global", "true")});
-    assertThat(notificationCenter.getChannels()).hasSize(0);
-    assertThat(notificationCenter.getDispatcherKeysForProperty("global", null)).hasSize(1);
-  }
-
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/notifications/NotificationServiceTest.java b/server/sonar-server/src/test/java/org/sonar/server/notifications/NotificationServiceTest.java
deleted file mode 100644 (file)
index c818dee..0000000
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.server.notifications;
-
-import com.google.common.collect.Sets;
-import org.junit.Test;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.sonar.api.config.Settings;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.core.notification.NotificationDispatcher;
-import org.sonar.core.notification.DefaultNotificationManager;
-import org.sonar.core.properties.PropertiesDao;
-import org.sonar.jpa.session.DatabaseSessionFactory;
-import org.sonar.server.db.DbClient;
-
-import java.util.Arrays;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.*;
-
-public class NotificationServiceTest {
-  private static String CREATOR_SIMON = "simon";
-  private static String CREATOR_EVGENY = "evgeny";
-  private static String ASSIGNEE_SIMON = "simon";
-
-  DefaultNotificationManager manager = mock(DefaultNotificationManager.class);
-  Notification notification = mock(Notification.class);
-  NotificationChannel emailChannel = mock(NotificationChannel.class);
-  NotificationChannel gtalkChannel = mock(NotificationChannel.class);
-  NotificationDispatcher commentOnIssueAssignedToMe = mock(NotificationDispatcher.class);
-  NotificationDispatcher commentOnIssueCreatedByMe = mock(NotificationDispatcher.class);
-  NotificationDispatcher qualityGateChange = mock(NotificationDispatcher.class);
-  DbClient dbClient = mock(DbClient.class);
-
-  private NotificationService service;
-
-  private void setUpMocks() {
-    when(emailChannel.getKey()).thenReturn("email");
-    when(gtalkChannel.getKey()).thenReturn("gtalk");
-    when(commentOnIssueAssignedToMe.getKey()).thenReturn("CommentOnIssueAssignedToMe");
-    when(commentOnIssueAssignedToMe.getType()).thenReturn("issue-changes");
-    when(commentOnIssueCreatedByMe.getKey()).thenReturn("CommentOnIssueCreatedByMe");
-    when(commentOnIssueCreatedByMe.getType()).thenReturn("issue-changes");
-    when(qualityGateChange.getKey()).thenReturn("QGateChange");
-    when(qualityGateChange.getType()).thenReturn("qgate-changes");
-    when(manager.getFromQueue()).thenReturn(notification).thenReturn(null);
-
-    Settings settings = new Settings().setProperty("sonar.notifications.delay", 1L);
-
-    service = new NotificationService(settings, manager,
-      dbClient, mock(DatabaseSessionFactory.class),
-      new NotificationDispatcher[] {commentOnIssueAssignedToMe, commentOnIssueCreatedByMe, qualityGateChange});
-  }
-
-  /**
-   * Given:
-   * Simon wants to receive notifications by email on comments for reviews assigned to him or created by him.
-   *
-   * When:
-   * Freddy adds comment to review created by Simon and assigned to Simon.
-   *
-   * Then:
-   * Only one notification should be delivered to Simon by Email.
-   */
-  @Test
-  public void scenario1() {
-    setUpMocks();
-    doAnswer(addUser(ASSIGNEE_SIMON, emailChannel)).when(commentOnIssueAssignedToMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
-    doAnswer(addUser(CREATOR_SIMON, emailChannel)).when(commentOnIssueCreatedByMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
-
-    service.start();
-    verify(emailChannel, timeout(2000)).deliver(notification, ASSIGNEE_SIMON);
-    service.stop();
-
-    verify(gtalkChannel, never()).deliver(notification, ASSIGNEE_SIMON);
-  }
-
-  /**
-   * Given:
-   * Evgeny wants to receive notification by GTalk on comments for reviews created by him.
-   * Simon wants to receive notification by Email on comments for reviews assigned to him.
-   *
-   * When:
-   * Freddy adds comment to review created by Evgeny and assigned to Simon.
-   *
-   * Then:
-   * Two notifications should be delivered - one to Simon by Email and another to Evgeny by GTalk.
-   */
-  @Test
-  public void scenario2() {
-    setUpMocks();
-    doAnswer(addUser(ASSIGNEE_SIMON, emailChannel)).when(commentOnIssueAssignedToMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
-    doAnswer(addUser(CREATOR_EVGENY, gtalkChannel)).when(commentOnIssueCreatedByMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
-
-    service.start();
-    verify(emailChannel, timeout(2000)).deliver(notification, ASSIGNEE_SIMON);
-    verify(gtalkChannel, timeout(2000)).deliver(notification, CREATOR_EVGENY);
-    service.stop();
-
-    verify(emailChannel, never()).deliver(notification, CREATOR_EVGENY);
-    verify(gtalkChannel, never()).deliver(notification, ASSIGNEE_SIMON);
-  }
-
-  /**
-   * Given:
-   * Simon wants to receive notifications by Email and GTLak on comments for reviews assigned to him.
-   *
-   * When:
-   * Freddy adds comment to review created by Evgeny and assigned to Simon.
-   *
-   * Then:
-   * Two notifications should be delivered to Simon - one by Email and another by GTalk.
-   */
-  @Test
-  public void scenario3() {
-    setUpMocks();
-    doAnswer(addUser(ASSIGNEE_SIMON, new NotificationChannel[] {emailChannel, gtalkChannel}))
-      .when(commentOnIssueAssignedToMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
-
-    service.start();
-    verify(emailChannel, timeout(2000)).deliver(notification, ASSIGNEE_SIMON);
-    verify(gtalkChannel, timeout(2000)).deliver(notification, ASSIGNEE_SIMON);
-    service.stop();
-
-    verify(emailChannel, never()).deliver(notification, CREATOR_EVGENY);
-    verify(gtalkChannel, never()).deliver(notification, CREATOR_EVGENY);
-  }
-
-  /**
-   * Given:
-   * Nobody wants to receive notifications.
-   *
-   * When:
-   * Freddy adds comment to review created by Evgeny and assigned to Simon.
-   *
-   * Then:
-   * No notifications.
-   */
-  @Test
-  public void scenario4() {
-    setUpMocks();
-
-    service.start();
-    service.stop();
-
-    verify(emailChannel, never()).deliver(any(Notification.class), anyString());
-    verify(gtalkChannel, never()).deliver(any(Notification.class), anyString());
-  }
-
-  // SONAR-4548
-  @Test
-  public void shouldNotStopWhenException() {
-    setUpMocks();
-    when(manager.getFromQueue()).thenThrow(new RuntimeException("Unexpected exception")).thenReturn(notification).thenReturn(null);
-    doAnswer(addUser(ASSIGNEE_SIMON, emailChannel)).when(commentOnIssueAssignedToMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
-    doAnswer(addUser(CREATOR_SIMON, emailChannel)).when(commentOnIssueCreatedByMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
-
-    service.start();
-    verify(emailChannel, timeout(2000)).deliver(notification, ASSIGNEE_SIMON);
-    service.stop();
-
-    verify(gtalkChannel, never()).deliver(notification, ASSIGNEE_SIMON);
-  }
-
-  @Test
-  public void shouldNotAddNullAsUser() {
-    setUpMocks();
-    doAnswer(addUser(null, gtalkChannel)).when(commentOnIssueCreatedByMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
-
-    service.start();
-    service.stop();
-
-    verify(emailChannel, never()).deliver(any(Notification.class), anyString());
-    verify(gtalkChannel, never()).deliver(any(Notification.class), anyString());
-  }
-
-  @Test
-  public void getDispatchers() {
-    setUpMocks();
-
-    assertThat(service.getDispatchers()).containsOnly(commentOnIssueAssignedToMe, commentOnIssueCreatedByMe, qualityGateChange);
-  }
-
-  @Test
-  public void getDispatchers_empty() {
-    Settings settings = new Settings().setProperty("sonar.notifications.delay", 1L);
-
-    service = new NotificationService(settings, manager, dbClient, mock(DatabaseSessionFactory.class));
-    assertThat(service.getDispatchers()).hasSize(0);
-  }
-
-  @Test
-  public void shouldLogEvery10Minutes() {
-    setUpMocks();
-    // Emulate 2 notifications in DB
-    when(manager.getFromQueue()).thenReturn(notification).thenReturn(notification).thenReturn(null);
-    when(manager.count()).thenReturn(1L).thenReturn(0L);
-    service = spy(service);
-    // Emulate processing of each notification take 10 min to have a log each time
-    when(service.now()).thenReturn(0L).thenReturn(10 * 60 * 1000 + 1L).thenReturn(20 * 60 * 1000 + 2L);
-    service.start();
-    verify(service, timeout(200)).log(1, 1, 10);
-    verify(service, timeout(200)).log(2, 0, 20);
-    service.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});
-  }
-
-  private static Answer<Object> addUser(final String user, final NotificationChannel[] channels) {
-    return new Answer<Object>() {
-      public Object answer(InvocationOnMock invocation) {
-        for (NotificationChannel channel : channels) {
-          ((NotificationDispatcher.Context) invocation.getArguments()[1]).addUser(user, channel);
-        }
-        return null;
-      }
-    };
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/notifications/NotificationTest.java b/server/sonar-server/src/test/java/org/sonar/server/notifications/NotificationTest.java
deleted file mode 100644 (file)
index 2c7c919..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.server.notifications;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.notifications.Notification;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class NotificationTest {
-
-  private Notification notification;
-
-  @Before
-  public void init() {
-    notification = new Notification("alerts").setDefaultMessage("There are new alerts").setFieldValue("alertCount", "42");
-  }
-
-  @Test
-  public void shouldReturnType() {
-    assertThat(notification.getType()).isEqualTo("alerts");
-  }
-
-  @Test
-  public void shouldReturnDefaultMessage() {
-    assertThat(notification.getDefaultMessage()).isEqualTo("There are new alerts");
-  }
-
-  @Test
-  public void shouldReturnToStringIfDefaultMessageNotSet() {
-    notification = new Notification("alerts").setFieldValue("alertCount", "42");
-    System.out.println(notification);
-    assertThat(notification.getDefaultMessage()).contains("type='alerts'");
-    assertThat(notification.getDefaultMessage()).contains("fields={alertCount=42}");
-  }
-
-  @Test
-  public void shouldReturnField() {
-    assertThat(notification.getFieldValue("alertCount")).isEqualTo("42");
-    assertThat(notification.getFieldValue("fake")).isNull();
-
-    // default message is stored as field as well
-    assertThat(notification.getFieldValue("default_message")).isEqualTo("There are new alerts");
-  }
-
-  @Test
-  public void shouldEqual() {
-    assertThat(notification.equals("")).isFalse();
-    assertThat(notification.equals(null)).isFalse();
-    assertThat(notification.equals(notification)).isTrue();
-
-    Notification otherNotif = new Notification("alerts").setDefaultMessage("There are new alerts").setFieldValue("alertCount", "42");
-    assertThat(otherNotif).isEqualTo(notification);
-
-    otherNotif = new Notification("alerts").setDefaultMessage("There are new alerts").setFieldValue("alertCount", "15000");
-    assertThat(otherNotif).isNotEqualTo(notification);
-  }
-
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/notifications/email/AlertsEmailTemplateTest.java b/server/sonar-server/src/test/java/org/sonar/server/notifications/email/AlertsEmailTemplateTest.java
deleted file mode 100644 (file)
index 3834df8..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.server.notifications.email;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.config.EmailSettings;
-import org.sonar.api.notifications.Notification;
-import org.sonar.plugins.emailnotifications.api.EmailMessage;
-
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.nullValue;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class AlertsEmailTemplateTest {
-
-  private AlertsEmailTemplate template;
-
-  @Before
-  public void setUp() {
-    EmailSettings configuration = mock(EmailSettings.class);
-    when(configuration.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org");
-    template = new AlertsEmailTemplate(configuration);
-  }
-
-  @Test
-  public void shouldNotFormatIfNotCorrectNotification() {
-    Notification notification = new Notification("other-notif");
-    EmailMessage message = template.format(notification);
-    assertThat(message, nullValue());
-  }
-
-  @Test
-  public void shouldFormatAlertWithSeveralMessages() {
-    Notification notification = createNotification("Orange (was Red)", "violations > 4, coverage < 75%", "WARN", "false");
-
-    EmailMessage message = template.format(notification);
-    assertThat(message.getMessageId(), is("alerts/45"));
-    assertThat(message.getSubject(), is("Quality gate status changed on \"Foo\""));
-    assertThat(message.getMessage(), is("" +
-      "Project: Foo\n" +
-      "Quality gate status: Orange (was Red)\n" +
-      "\n" +
-      "Quality gate thresholds:\n" +
-      "  - violations > 4\n" +
-      "  - coverage < 75%\n" +
-      "\n" +
-      "See it in SonarQube: http://nemo.sonarsource.org/dashboard/index/org.sonar.foo:foo"));
-  }
-
-  @Test
-  public void shouldFormatNewAlertWithSeveralMessages() {
-    Notification notification = createNotification("Orange (was Red)", "violations > 4, coverage < 75%", "WARN", "true");
-
-    EmailMessage message = template.format(notification);
-    assertThat(message.getMessageId(), is("alerts/45"));
-    assertThat(message.getSubject(), is("New quality gate threshold reached on \"Foo\""));
-    assertThat(message.getMessage(), is("" +
-      "Project: Foo\n" +
-      "Quality gate status: Orange (was Red)\n" +
-      "\n" +
-      "New quality gate thresholds:\n" +
-      "  - violations > 4\n" +
-      "  - coverage < 75%\n" +
-      "\n" +
-      "See it in SonarQube: http://nemo.sonarsource.org/dashboard/index/org.sonar.foo:foo"));
-  }
-
-  @Test
-  public void shouldFormatNewAlertWithOneMessage() {
-    Notification notification = createNotification("Orange (was Red)", "violations > 4", "WARN", "true");
-
-    EmailMessage message = template.format(notification);
-    assertThat(message.getMessageId(), is("alerts/45"));
-    assertThat(message.getSubject(), is("New quality gate threshold reached on \"Foo\""));
-    assertThat(message.getMessage(), is("" +
-      "Project: Foo\n" +
-      "Quality gate status: Orange (was Red)\n" +
-      "\n" +
-      "New quality gate threshold: violations > 4\n" +
-      "\n" +
-      "See it in SonarQube: http://nemo.sonarsource.org/dashboard/index/org.sonar.foo:foo"));
-  }
-
-  @Test
-  public void shouldFormatBackToGreenMessage() {
-    Notification notification = createNotification("Green (was Red)", "", "OK", "false");
-
-    EmailMessage message = template.format(notification);
-    assertThat(message.getMessageId(), is("alerts/45"));
-    assertThat(message.getSubject(), is("\"Foo\" is back to green"));
-    assertThat(message.getMessage(), is("" +
-      "Project: Foo\n" +
-      "Quality gate status: Green (was Red)\n" +
-      "\n" +
-      "\n" +
-      "See it in SonarQube: http://nemo.sonarsource.org/dashboard/index/org.sonar.foo:foo"));
-  }
-
-  private Notification createNotification(String alertName, String alertText, String alertLevel, String isNewAlert) {
-    Notification notification = new Notification("alerts")
-        .setFieldValue("projectName", "Foo")
-        .setFieldValue("projectKey", "org.sonar.foo:foo")
-        .setFieldValue("projectId", "45")
-        .setFieldValue("alertName", alertName)
-        .setFieldValue("alertText", alertText)
-        .setFieldValue("alertLevel", alertLevel)
-        .setFieldValue("isNewAlert", isNewAlert);
-    return notification;
-  }
-
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/notifications/email/EmailNotificationChannelTest.java b/server/sonar-server/src/test/java/org/sonar/server/notifications/email/EmailNotificationChannelTest.java
deleted file mode 100644 (file)
index 3c5e945..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.server.notifications.email;
-
-import java.io.IOException;
-import java.net.ServerSocket;
-import java.util.List;
-import javax.mail.internet.MimeMessage;
-import org.apache.commons.mail.EmailException;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.config.EmailSettings;
-import org.sonar.plugins.emailnotifications.api.EmailMessage;
-import org.subethamail.wiser.Wiser;
-import org.subethamail.wiser.WiserMessage;
-
-import static junit.framework.Assert.fail;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class EmailNotificationChannelTest {
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  private int port;
-  private Wiser server;
-  private EmailSettings configuration;
-  private EmailNotificationChannel channel;
-
-  private static int getNextAvailablePort() {
-    try {
-      ServerSocket socket = new ServerSocket(0);
-      int unusedPort = socket.getLocalPort();
-      socket.close();
-      return unusedPort;
-    } catch (IOException e) {
-      throw new RuntimeException("Error getting an available port from system", e);
-    }
-  }
-
-  @Before
-  public void setUp() {
-    port = getNextAvailablePort();
-    server = new Wiser();
-    server.setPort(port);
-    server.start();
-
-    configuration = mock(EmailSettings.class);
-    channel = new EmailNotificationChannel(configuration, null, null);
-  }
-
-  @After
-  public void tearDown() {
-    server.stop();
-  }
-
-  @Test
-  public void shouldSendTestEmail() throws Exception {
-    configure();
-    channel.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a test message from SonarQube.");
-
-    List<WiserMessage> messages = server.getMessages();
-    assertThat(messages).hasSize(1);
-
-    MimeMessage email = messages.get(0).getMimeMessage();
-    assertThat(email.getHeader("Content-Type", null)).isEqualTo("text/plain; charset=UTF-8");
-    assertThat(email.getHeader("From", ",")).isEqualTo("SonarQube <server@nowhere>");
-    assertThat(email.getHeader("To", null)).isEqualTo("<user@nowhere>");
-    assertThat(email.getHeader("Subject", null)).isEqualTo("[SONARQUBE] Test Message from SonarQube");
-    assertThat((String) email.getContent()).startsWith("This is a test message from SonarQube.");
-  }
-
-  @Test
-  public void shouldThrowAnExceptionWhenUnableToSendTestEmail() {
-    configure();
-    server.stop();
-
-    try {
-      channel.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a test message from SonarQube.");
-      fail();
-    } catch (EmailException e) {
-      // expected
-    }
-  }
-
-  @Test
-  public void shouldNotSendEmailWhenHostnameNotConfigured() {
-    EmailMessage emailMessage = new EmailMessage()
-      .setTo("user@nowhere")
-      .setSubject("Foo")
-      .setMessage("Bar");
-    channel.deliver(emailMessage);
-    assertThat(server.getMessages()).isEmpty();
-  }
-
-  @Test
-  public void shouldSendThreadedEmail() throws Exception {
-    configure();
-    EmailMessage emailMessage = new EmailMessage()
-      .setMessageId("reviews/view/1")
-      .setFrom("Full Username")
-      .setTo("user@nowhere")
-      .setSubject("Review #3")
-      .setMessage("I'll take care of this violation.");
-    channel.deliver(emailMessage);
-
-    List<WiserMessage> messages = server.getMessages();
-    assertThat(messages).hasSize(1);
-
-    MimeMessage email = messages.get(0).getMimeMessage();
-
-    assertThat(email.getHeader("Content-Type", null)).isEqualTo("text/plain; charset=UTF-8");
-
-    assertThat(email.getHeader("In-Reply-To", null)).isEqualTo("<reviews/view/1@nemo.sonarsource.org>");
-    assertThat(email.getHeader("References", null)).isEqualTo("<reviews/view/1@nemo.sonarsource.org>");
-
-    assertThat(email.getHeader("List-ID", null)).isEqualTo("SonarQube <sonar.nemo.sonarsource.org>");
-    assertThat(email.getHeader("List-Archive", null)).isEqualTo("http://nemo.sonarsource.org");
-
-    assertThat(email.getHeader("From", ",")).isEqualTo("\"Full Username (SonarQube)\" <server@nowhere>");
-    assertThat(email.getHeader("To", null)).isEqualTo("<user@nowhere>");
-    assertThat(email.getHeader("Subject", null)).isEqualTo("[SONARQUBE] Review #3");
-    assertThat((String) email.getContent()).startsWith("I'll take care of this violation.");
-  }
-
-  @Test
-  public void shouldSendNonThreadedEmail() throws Exception {
-    configure();
-    EmailMessage emailMessage = new EmailMessage()
-      .setTo("user@nowhere")
-      .setSubject("Foo")
-      .setMessage("Bar");
-    channel.deliver(emailMessage);
-
-    List<WiserMessage> messages = server.getMessages();
-    assertThat(messages).hasSize(1);
-
-    MimeMessage email = messages.get(0).getMimeMessage();
-
-    assertThat(email.getHeader("Content-Type", null)).isEqualTo("text/plain; charset=UTF-8");
-
-    assertThat(email.getHeader("In-Reply-To", null)).isNull();
-    assertThat(email.getHeader("References", null)).isNull();
-
-    assertThat(email.getHeader("List-ID", null)).isEqualTo("SonarQube <sonar.nemo.sonarsource.org>");
-    assertThat(email.getHeader("List-Archive", null)).isEqualTo("http://nemo.sonarsource.org");
-
-    assertThat(email.getHeader("From", null)).isEqualTo("SonarQube <server@nowhere>");
-    assertThat(email.getHeader("To", null)).isEqualTo("<user@nowhere>");
-    assertThat(email.getHeader("Subject", null)).isEqualTo("[SONARQUBE] Foo");
-    assertThat((String) email.getContent()).startsWith("Bar");
-  }
-
-  @Test
-  public void shouldNotThrowAnExceptionWhenUnableToSendEmail() {
-    configure();
-    server.stop();
-
-    EmailMessage emailMessage = new EmailMessage()
-      .setTo("user@nowhere")
-      .setSubject("Foo")
-      .setMessage("Bar");
-    channel.deliver(emailMessage);
-  }
-
-  @Test
-  public void shouldSendTestEmailWithSTARTTLS() {
-    server.getServer().setEnableTLS(true);
-    server.getServer().setRequireTLS(true);
-    configure();
-    when(configuration.getSecureConnection()).thenReturn("STARTTLS");
-
-    try {
-      channel.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a test message from SonarQube.");
-      fail("An SSL exception was expected a a proof that STARTTLS is enabled");
-    } catch (EmailException e) {
-      // We don't have a SSL certificate so we are expecting a SSL error
-      assertThat(e.getCause().getMessage()).isEqualTo("Could not convert socket to TLS");
-    }
-  }
-
-  private void configure() {
-    when(configuration.getSmtpHost()).thenReturn("localhost");
-    when(configuration.getSmtpPort()).thenReturn(port);
-    when(configuration.getFrom()).thenReturn("server@nowhere");
-    when(configuration.getPrefix()).thenReturn("[SONARQUBE]");
-    when(configuration.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org");
-  }
-
-}
index ac15c254939a0e4d844f16743d8f707d8af7c4c3..1773cd1e7a6e9c13a1caa137c7c702e8698768e1 100644 (file)
@@ -69,7 +69,6 @@ import org.sonar.batch.scm.ScmSensor;
 import org.sonar.batch.source.CodeColorizerSensor;
 import org.sonar.batch.source.LinesSensor;
 import org.sonar.core.config.CorePropertyDefinitions;
-import org.sonar.core.notification.DefaultNotificationManager;
 import org.sonar.core.resource.DefaultResourceTypes;
 
 public class BatchComponents {
@@ -108,8 +107,6 @@ public class BatchComponents {
       SqaleRatingDecorator.class,
       SqaleRatingSettings.class,
 
-      DefaultNotificationManager.class,
-
       // Quality Gate
       QualityGateVerifier.class,
 
diff --git a/sonar-core/src/main/java/org/sonar/core/notification/DefaultNotificationManager.java b/sonar-core/src/main/java/org/sonar/core/notification/DefaultNotificationManager.java
deleted file mode 100644 (file)
index 6857590..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.core.notification;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.SetMultimap;
-import java.io.IOException;
-import java.io.InvalidClassException;
-import java.util.Arrays;
-import java.util.List;
-import javax.annotation.Nullable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.batch.RequiresDB;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.api.utils.SonarException;
-import org.sonar.core.notification.db.NotificationQueueDao;
-import org.sonar.core.notification.db.NotificationQueueDto;
-import org.sonar.core.properties.PropertiesDao;
-
-/**
- * @since 2.10
- */
-@RequiresDB
-public class DefaultNotificationManager implements NotificationManager {
-
-  private static final Logger LOG = LoggerFactory.getLogger(DefaultNotificationManager.class);
-
-  private static final String UNABLE_TO_READ_NOTIFICATION = "Unable to read notification";
-
-  private NotificationChannel[] notificationChannels;
-  private NotificationQueueDao notificationQueueDao;
-  private PropertiesDao propertiesDao;
-
-  private boolean alreadyLoggedDeserializationIssue = false;
-
-  /**
-   * Default constructor used by Pico
-   */
-  public DefaultNotificationManager(NotificationChannel[] channels, NotificationQueueDao notificationQueueDao, PropertiesDao propertiesDao) {
-    this.notificationChannels = channels;
-    this.notificationQueueDao = notificationQueueDao;
-    this.propertiesDao = propertiesDao;
-  }
-
-  /**
-   * Constructor if no notification channel
-   */
-  public DefaultNotificationManager(NotificationQueueDao notificationQueueDao, PropertiesDao propertiesDao) {
-    this(new NotificationChannel[0], notificationQueueDao, propertiesDao);
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public void scheduleForSending(Notification notification) {
-    NotificationQueueDto dto = NotificationQueueDto.toNotificationQueueDto(notification);
-    notificationQueueDao.insert(Arrays.asList(dto));
-  }
-
-  @Override
-  public void scheduleForSending(List<Notification> notification) {
-    notificationQueueDao.insert(Lists.transform(notification, new Function<Notification, NotificationQueueDto>() {
-      @Override
-      public NotificationQueueDto apply(Notification notification) {
-        return NotificationQueueDto.toNotificationQueueDto(notification);
-      }
-    }));
-  }
-
-  /**
-   * Give the notification queue so that it can be processed
-   */
-  public Notification getFromQueue() {
-    int batchSize = 1;
-    List<NotificationQueueDto> notificationDtos = notificationQueueDao.findOldest(batchSize);
-    if (notificationDtos.isEmpty()) {
-      return null;
-    }
-    notificationQueueDao.delete(notificationDtos);
-
-    return convertToNotification(notificationDtos);
-  }
-
-  private Notification convertToNotification(List<NotificationQueueDto> notifications) {
-    try {
-      // If batchSize is increased then we should return a list instead of a single element
-      return notifications.get(0).toNotification();
-    } catch (InvalidClassException e) {
-      // SONAR-4739
-      if (!alreadyLoggedDeserializationIssue) {
-        logDeserializationIssue();
-        alreadyLoggedDeserializationIssue = true;
-      }
-      return null;
-    } catch (IOException e) {
-      throw new SonarException(UNABLE_TO_READ_NOTIFICATION, e);
-
-    } catch (ClassNotFoundException e) {
-      throw new SonarException(UNABLE_TO_READ_NOTIFICATION, e);
-    }
-  }
-
-  @VisibleForTesting
-  void logDeserializationIssue() {
-    LOG.warn("It is impossible to send pending notifications which existed prior to the upgrade of SonarQube. They will be ignored.");
-  }
-
-  public long count() {
-    return notificationQueueDao.count();
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public Multimap<String, NotificationChannel> findSubscribedRecipientsForDispatcher(NotificationDispatcher dispatcher, @Nullable Integer resourceId) {
-    String dispatcherKey = dispatcher.getKey();
-
-    SetMultimap<String, NotificationChannel> recipients = HashMultimap.create();
-    for (NotificationChannel channel : notificationChannels) {
-      String channelKey = channel.getKey();
-
-      // Find users subscribed globally to the dispatcher (i.e. not on a specific project)
-      addUsersToRecipientListForChannel(propertiesDao.findUsersForNotification(dispatcherKey, channelKey, null), recipients, channel);
-
-      if (resourceId != null) {
-        // Find users subscribed to the dispatcher specifically for the resource
-        addUsersToRecipientListForChannel(propertiesDao.findUsersForNotification(dispatcherKey, channelKey, resourceId.longValue()), recipients, channel);
-      }
-    }
-
-    return recipients;
-  }
-
-  @Override
-  public Multimap<String, NotificationChannel> findNotificationSubscribers(NotificationDispatcher dispatcher, @Nullable String componentKey) {
-    String dispatcherKey = dispatcher.getKey();
-
-    SetMultimap<String, NotificationChannel> recipients = HashMultimap.create();
-    for (NotificationChannel channel : notificationChannels) {
-      addUsersToRecipientListForChannel(propertiesDao.findNotificationSubscribers(dispatcherKey, channel.getKey(), componentKey), recipients, channel);
-    }
-
-    return recipients;
-  }
-
-  @VisibleForTesting
-  protected List<NotificationChannel> getChannels() {
-    return Arrays.asList(notificationChannels);
-  }
-
-  private static void addUsersToRecipientListForChannel(List<String> users, SetMultimap<String, NotificationChannel> recipients, NotificationChannel channel) {
-    for (String username : users) {
-      recipients.put(username, channel);
-    }
-  }
-
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/notification/NotificationDispatcher.java b/sonar-core/src/main/java/org/sonar/core/notification/NotificationDispatcher.java
deleted file mode 100644 (file)
index 33a127a..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.core.notification;
-
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.ExtensionPoint;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.api.server.ServerSide;
-
-/**
- * <p>
- * Plugins should extend this class to provide logic to determine which users are interested in receiving notifications,
- * along with which delivery channels they selected.
- * </p>
- * For example:
- * <ul>
- * <li>notify me by email when someone comments an issue reported by me</li>
- * <li>notify me by twitter when someone comments an issue assigned to me</li>
- * <li>notify me by Jabber when someone mentions me in an issue comment</li>
- * <li>send me by SMS when there are system notifications (like password reset, account creation, ...)</li>
- * </ul> 
- * 
- * @since 2.10
- */
-@ServerSide
-@ExtensionPoint
-public abstract class NotificationDispatcher {
-
-  private final String notificationType;
-
-  /**
-   * Additional information related to the notification, which will be used
-   * to know who should receive the notification.
-   */
-  public interface Context {
-    /**
-     * This method is not used any longer. Calling it will result in an {@link UnsupportedOperationException}.
-     * 
-     * @deprecated Use {@link #addUser(String, NotificationChannel)} instead.
-     */
-    @Deprecated
-    void addUser(String userLogin);
-
-    /**
-     * Adds a user that will be notified through the given notification channel.
-     * 
-     * @param userLogin the user login
-     * @param notificationChannel the notification channel to use for this user
-     */
-    void addUser(String userLogin, NotificationChannel notificationChannel);
-  }
-
-  /**
-   * Creates a new dispatcher for notifications of the given type.
-   * 
-   * @param notificationType the type of notifications handled by this dispatcher
-   */
-  public NotificationDispatcher(String notificationType) {
-    this.notificationType = notificationType;
-  }
-
-  /**
-   * Creates a new generic dispatcher, used for any kind of notification. 
-   * <p/>
-   * Should be avoided and replaced by the other constructor - as it is easier to understand that a
-   * dispatcher listens for a specific type of notification.
-   */
-  public NotificationDispatcher() {
-    this("");
-  }
-
-  /**
-   * The unique key of this dispatcher. By default it's the class name without the package prefix.
-   * <p/>
-   * The related label in l10n bundles is 'notification.dispatcher.<key>', for example 'notification.dispatcher.NewFalsePositive'.
-   */
-  public String getKey() {
-    return getClass().getSimpleName();
-  }
-
-  /**
-   * @since 5.1
-   */
-  public String getType() {
-    return notificationType;
-  }
-
-  /**
-   * <p>
-   * Performs the dispatch.
-   * </p>
-   */
-  public final void performDispatch(Notification notification, Context context) {
-    if (StringUtils.equals(notification.getType(), notificationType) || StringUtils.equals("", notificationType)) {
-      dispatch(notification, context);
-    }
-  }
-
-  /**
-   * <p>
-   * Implements the logic that defines which users will receive the notification.
-   * </p>
-   * The purpose of this method is to populate the context object with users, based on the type of notification and the content of the notification.
-   */
-  public abstract void dispatch(Notification notification, Context context);
-
-  @Override
-  public String toString() {
-    return getKey();
-  }
-
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/notification/NotificationDispatcherMetadata.java b/sonar-core/src/main/java/org/sonar/core/notification/NotificationDispatcherMetadata.java
deleted file mode 100644 (file)
index 2eb04a5..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.core.notification;
-
-import com.google.common.collect.Maps;
-import java.util.Map;
-import org.sonar.api.server.ServerSide;
-
-/**
- * <p>
- * Notification dispatchers (see {@link NotificationDispatcher}) can define their own metadata class in order
- * to tell more about them.
- * <p/>
- * Instances of these classes must be declared in {@link org.sonar.api.SonarPlugin#getExtensions()}.
- *
- * @since 3.5
- */
-@ServerSide
-public final class NotificationDispatcherMetadata {
-
-  public static final String GLOBAL_NOTIFICATION = "globalNotification";
-  public static final String PER_PROJECT_NOTIFICATION = "perProjectNotification";
-
-  private String dispatcherKey;
-  private Map<String, String> properties;
-
-  private NotificationDispatcherMetadata(String dispatcherKey) {
-    this.dispatcherKey = dispatcherKey;
-    this.properties = Maps.newHashMap();
-  }
-
-  /**
-   * Creates a new metadata instance for the given dispatcher.
-   * <p/>
-   * By default the key is the class name without package. It can be changed by overriding
-   * {@link NotificationDispatcher#getKey()}.
-   */
-  public static NotificationDispatcherMetadata create(String dispatcherKey) {
-    return new NotificationDispatcherMetadata(dispatcherKey);
-  }
-
-  /**
-   * Sets a property on this metadata object.
-   */
-  public NotificationDispatcherMetadata setProperty(String key, String value) {
-    properties.put(key, value);
-    return this;
-  }
-
-  /**
-   * Gives the property for the given key.
-   */
-  public String getProperty(String key) {
-    return properties.get(key);
-  }
-
-  /**
-   * Returns the unique key of the dispatcher.
-   */
-  public String getDispatcherKey() {
-    return dispatcherKey;
-  }
-
-  @Override
-  public String toString() {
-    return dispatcherKey;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    NotificationDispatcherMetadata that = (NotificationDispatcherMetadata) o;
-    return dispatcherKey.equals(that.dispatcherKey);
-  }
-
-  @Override
-  public int hashCode() {
-    return dispatcherKey.hashCode();
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/notification/NotificationManager.java b/sonar-core/src/main/java/org/sonar/core/notification/NotificationManager.java
deleted file mode 100644 (file)
index f9b5e0a..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.core.notification;
-
-import com.google.common.collect.Multimap;
-import org.sonar.api.batch.BatchSide;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.batch.InstantiationStrategy;
-
-import javax.annotation.Nullable;
-
-import java.util.List;
-
-/**
- * <p>
- * The notification manager receives notifications and is in charge of storing them so that they are processed by the notification service.
- * </p>
- * <p>
- * Pico provides an instance of this class, and plugins just need to create notifications and pass them to this manager with
- * the {@link NotificationManager#scheduleForSending(Notification)} method.
- * </p>
- *
- * @since 2.10
- */
-@ServerSide
-@BatchSide
-@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
-public interface NotificationManager {
-
-  /**
-   * Receives a notification and stores it so that it is processed by the notification service.
-   *
-   * @param notification the notification.
-   */
-  void scheduleForSending(Notification notification);
-
-  /**
-   * Receives notifications and stores them so that they are processed by the notification service.
-   *
-   * @param notifications the notifications.
-   * @since 3.7.1
-   */
-  void scheduleForSending(List<Notification> notifications);
-
-  /**
-   * <p>
-   * Returns the list of users who subscribed to the given dispatcher, along with the notification channels (email, twitter, ...) that they choose
-   * for this dispatcher.
-   * </p>
-   * <p>
-   * The resource ID can be null in case of notifications that have nothing to do with a specific project (like system notifications).
-   * </p>
-   *
-   * @param dispatcher the dispatcher for which this list of users is requested
-   * @param resourceId the optional resource which is concerned by this request
-   * @return the list of user login along with the subscribed channels
-   */
-  Multimap<String, NotificationChannel> findSubscribedRecipientsForDispatcher(NotificationDispatcher dispatcher, @Nullable Integer resourceId);
-
-  Multimap<String, NotificationChannel> findNotificationSubscribers(NotificationDispatcher dispatcher, @Nullable String componentKey);
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/notification/DefaultNotificationManagerTest.java b/sonar-core/src/test/java/org/sonar/core/notification/DefaultNotificationManagerTest.java
deleted file mode 100644 (file)
index c5e60e1..0000000
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.core.notification;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Multimap;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.notifications.NotificationChannel;
-import org.sonar.core.notification.db.NotificationQueueDao;
-import org.sonar.core.notification.db.NotificationQueueDto;
-import org.sonar.core.properties.PropertiesDao;
-import org.sonar.jpa.test.AbstractDbUnitTestCase;
-
-import java.io.InvalidClassException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.only;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class DefaultNotificationManagerTest extends AbstractDbUnitTestCase {
-
-  private DefaultNotificationManager manager;
-
-  @Mock
-  private PropertiesDao propertiesDao;
-
-  @Mock
-  private NotificationDispatcher dispatcher;
-
-  @Mock
-  private NotificationChannel emailChannel;
-
-  @Mock
-  private NotificationChannel twitterChannel;
-
-  @Mock
-  private NotificationQueueDao notificationQueueDao;
-
-  @Before
-  public void setUp() {
-    MockitoAnnotations.initMocks(this);
-    when(dispatcher.getKey()).thenReturn("NewViolations");
-    when(emailChannel.getKey()).thenReturn("Email");
-    when(twitterChannel.getKey()).thenReturn("Twitter");
-
-    manager = new DefaultNotificationManager(new NotificationChannel[] {emailChannel, twitterChannel}, notificationQueueDao, propertiesDao);
-  }
-
-  @Test
-  public void shouldProvideChannelList() {
-    assertThat(manager.getChannels()).containsOnly(emailChannel, twitterChannel);
-
-    manager = new DefaultNotificationManager(notificationQueueDao, propertiesDao);
-    assertThat(manager.getChannels()).hasSize(0);
-  }
-
-  @Test
-  public void shouldPersist() {
-    Notification notification = new Notification("test");
-    manager.scheduleForSending(notification);
-
-    verify(notificationQueueDao, only()).insert(any(List.class));
-  }
-
-  @Test
-  public void shouldGetFromQueueAndDelete() {
-    Notification notification = new Notification("test");
-    NotificationQueueDto dto = NotificationQueueDto.toNotificationQueueDto(notification);
-    List<NotificationQueueDto> dtos = Arrays.asList(dto);
-    when(notificationQueueDao.findOldest(1)).thenReturn(dtos);
-
-    assertThat(manager.getFromQueue()).isNotNull();
-
-    InOrder inOrder = inOrder(notificationQueueDao);
-    inOrder.verify(notificationQueueDao).findOldest(1);
-    inOrder.verify(notificationQueueDao).delete(dtos);
-  }
-
-  // SONAR-4739
-  @Test
-  public void shouldNotFailWhenUnableToDeserialize() throws Exception {
-    NotificationQueueDto dto1 = mock(NotificationQueueDto.class);
-    when(dto1.toNotification()).thenThrow(new InvalidClassException("Pouet"));
-    List<NotificationQueueDto> dtos = Arrays.asList(dto1);
-    when(notificationQueueDao.findOldest(1)).thenReturn(dtos);
-
-    manager = spy(manager);
-    assertThat(manager.getFromQueue()).isNull();
-    assertThat(manager.getFromQueue()).isNull();
-
-    verify(manager, times(1)).logDeserializationIssue();
-  }
-
-  @Test
-  public void shouldFindNoRecipient() {
-    assertThat(manager.findSubscribedRecipientsForDispatcher(dispatcher, 45).asMap().entrySet()).hasSize(0);
-  }
-
-  @Test
-  public void shouldFindSubscribedRecipientForGivenResource() {
-    when(propertiesDao.findUsersForNotification("NewViolations", "Email", 45L)).thenReturn(Lists.newArrayList("user1", "user2"));
-    when(propertiesDao.findUsersForNotification("NewViolations", "Email", null)).thenReturn(Lists.newArrayList("user1", "user3"));
-    when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", 56L)).thenReturn(Lists.newArrayList("user2"));
-    when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", null)).thenReturn(Lists.newArrayList("user3"));
-    when(propertiesDao.findUsersForNotification("NewAlerts", "Twitter", null)).thenReturn(Lists.newArrayList("user4"));
-
-    Multimap<String, NotificationChannel> multiMap = manager.findSubscribedRecipientsForDispatcher(dispatcher, 45);
-    assertThat(multiMap.entries()).hasSize(4);
-
-    Map<String, Collection<NotificationChannel>> map = multiMap.asMap();
-    assertThat(map.get("user1")).containsOnly(emailChannel);
-    assertThat(map.get("user2")).containsOnly(emailChannel);
-    assertThat(map.get("user3")).containsOnly(emailChannel, twitterChannel);
-    assertThat(map.get("user4")).isNull();
-  }
-
-  @Test
-  public void shouldFindSubscribedRecipientForNoResource() {
-    when(propertiesDao.findUsersForNotification("NewViolations", "Email", 45L)).thenReturn(Lists.newArrayList("user1", "user2"));
-    when(propertiesDao.findUsersForNotification("NewViolations", "Email", null)).thenReturn(Lists.newArrayList("user1", "user3"));
-    when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", 56L)).thenReturn(Lists.newArrayList("user2"));
-    when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", null)).thenReturn(Lists.newArrayList("user3"));
-    when(propertiesDao.findUsersForNotification("NewAlerts", "Twitter", null)).thenReturn(Lists.newArrayList("user4"));
-
-    Multimap<String, NotificationChannel> multiMap = manager.findSubscribedRecipientsForDispatcher(dispatcher, (Integer) null);
-    assertThat(multiMap.entries()).hasSize(3);
-
-    Map<String, Collection<NotificationChannel>> map = multiMap.asMap();
-    assertThat(map.get("user1")).containsOnly(emailChannel);
-    assertThat(map.get("user3")).containsOnly(emailChannel, twitterChannel);
-    assertThat(map.get("user2")).isNull();
-    assertThat(map.get("user4")).isNull();
-  }
-
-  @Test
-  public void findNotificationSubscribers() {
-    when(propertiesDao.findNotificationSubscribers("NewViolations", "Email", "struts")).thenReturn(Lists.newArrayList("user1", "user2"));
-    when(propertiesDao.findNotificationSubscribers("NewViolations", "Twitter", "struts")).thenReturn(Lists.newArrayList("user2"));
-
-    Multimap<String, NotificationChannel> multiMap = manager.findNotificationSubscribers(dispatcher, "struts");
-    assertThat(multiMap.entries()).hasSize(3);
-
-    Map<String, Collection<NotificationChannel>> map = multiMap.asMap();
-    assertThat(map.get("user1")).containsOnly(emailChannel);
-    assertThat(map.get("user2")).containsOnly(emailChannel, twitterChannel);
-    assertThat(map.get("other")).isNull();
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/notification/NotificationChannelTest.java b/sonar-core/src/test/java/org/sonar/core/notification/NotificationChannelTest.java
deleted file mode 100644 (file)
index dcd80b4..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.core.notification;
-
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
-
-import org.junit.Test;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.notifications.NotificationChannel;
-
-public class NotificationChannelTest {
-
-  @Test
-  public void defaultMethods() {
-    NotificationChannel channel = new FakeNotificationChannel();
-    assertThat(channel.getKey(), is("FakeNotificationChannel"));
-    assertThat(channel.toString(), is("FakeNotificationChannel"));
-  }
-
-  class FakeNotificationChannel extends NotificationChannel {
-    @Override
-    public void deliver(Notification notification, String username) {
-    }
-  }
-
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/notification/NotificationDispatcherMetadataTest.java b/sonar-core/src/test/java/org/sonar/core/notification/NotificationDispatcherMetadataTest.java
deleted file mode 100644 (file)
index c19e545..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.core.notification;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class NotificationDispatcherMetadataTest {
-
-  private NotificationDispatcherMetadata metadata;
-
-  @Before
-  public void init() {
-    metadata = NotificationDispatcherMetadata.create("NewViolations").setProperty("global", "true");
-  }
-
-  @Test
-  public void shouldReturnDispatcherKey() {
-    assertThat(metadata.getDispatcherKey()).isEqualTo("NewViolations");
-  }
-
-  @Test
-  public void shouldReturnProperty() {
-    assertThat(metadata.getProperty("global")).isEqualTo("true");
-    assertThat(metadata.getProperty("per-project")).isNull();
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/notification/NotificationDispatcherTest.java b/sonar-core/src/test/java/org/sonar/core/notification/NotificationDispatcherTest.java
deleted file mode 100644 (file)
index 8929612..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.core.notification;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.notifications.NotificationChannel;
-
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class NotificationDispatcherTest {
-
-  @Mock
-  private NotificationChannel channel;
-
-  @Mock
-  private Notification notification;
-
-  @Mock
-  private NotificationDispatcher.Context context;
-
-  @Before
-  public void init() {
-    MockitoAnnotations.initMocks(this);
-    when(notification.getType()).thenReturn("event1");
-  }
-
-  @Test
-  public void defaultMethods() {
-    NotificationDispatcher dispatcher = new FakeGenericNotificationDispatcher();
-    assertThat(dispatcher.getKey(), is("FakeGenericNotificationDispatcher"));
-    assertThat(dispatcher.toString(), is("FakeGenericNotificationDispatcher"));
-  }
-
-  @Test
-  public void shouldAlwaysRunDispatchForGenericDispatcher() {
-    NotificationDispatcher dispatcher = new FakeGenericNotificationDispatcher();
-    dispatcher.performDispatch(notification, context);
-
-    verify(context, times(1)).addUser("user1", channel);
-  }
-
-  @Test
-  public void shouldNotAlwaysRunDispatchForSpecificDispatcher() {
-    NotificationDispatcher dispatcher = new FakeSpecificNotificationDispatcher();
-
-    // a "event1" notif is sent
-    dispatcher.performDispatch(notification, context);
-    verify(context, never()).addUser("user1", channel);
-
-    // now, a "specific-event" notif is sent
-    when(notification.getType()).thenReturn("specific-event");
-    dispatcher.performDispatch(notification, context);
-    verify(context, times(1)).addUser("user1", channel);
-  }
-
-  class FakeGenericNotificationDispatcher extends NotificationDispatcher {
-    @Override
-    public void dispatch(Notification notification, Context context) {
-      context.addUser("user1", channel);
-    }
-  }
-
-  class FakeSpecificNotificationDispatcher extends NotificationDispatcher {
-
-    public FakeSpecificNotificationDispatcher() {
-      super("specific-event");
-    }
-
-    @Override
-    public void dispatch(Notification notification, Context context) {
-      context.addUser("user1", channel);
-    }
-  }
-
-}