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 {
/**
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".
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;
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;
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".
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".
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"
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"
--- /dev/null
+/*
+ * 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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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;
--- /dev/null
+/*
+ * 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;
+++ /dev/null
-/*
- * 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;
- }
-
-}
+++ /dev/null
-/*
- * 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);
- }
-}
+++ /dev/null
-/*
- * 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();
- }
-
-}
+++ /dev/null
-/*
- * 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;
- }
- }
-
-}
+++ /dev/null
-/*
- * 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;
+++ /dev/null
-/*
- * 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;
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;
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;
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;
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.*;
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.*;
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.*;
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.*;
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.*;
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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) {
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+ };
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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");
+ }
+
+}
+++ /dev/null
-/*
- * 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);
- }
-
-}
+++ /dev/null
-/*
- * 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;
- }
- };
- }
-}
+++ /dev/null
-/*
- * 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);
- }
-
-}
+++ /dev/null
-/*
- * 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;
- }
-
-}
+++ /dev/null
-/*
- * 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");
- }
-
-}
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 {
SqaleRatingDecorator.class,
SqaleRatingSettings.class,
- DefaultNotificationManager.class,
-
// Quality Gate
QualityGateVerifier.class,
+++ /dev/null
-/*
- * 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);
- }
- }
-
-}
+++ /dev/null
-/*
- * 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();
- }
-
-}
+++ /dev/null
-/*
- * 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();
- }
-}
+++ /dev/null
-/*
- * 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);
-}
+++ /dev/null
-/*
- * 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();
- }
-}
+++ /dev/null
-/*
- * 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) {
- }
- }
-
-}
+++ /dev/null
-/*
- * 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();
- }
-}
+++ /dev/null
-/*
- * 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);
- }
- }
-
-}