aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sonar-core/src/main/java/org/sonar/jpa/entity/Review.java19
-rw-r--r--sonar-server/src/main/java/org/sonar/server/notifications/Notification.java28
-rw-r--r--sonar-server/src/main/java/org/sonar/server/notifications/NotificationChannel.java50
-rw-r--r--sonar-server/src/main/java/org/sonar/server/notifications/NotificationDispatcher.java55
-rw-r--r--sonar-server/src/main/java/org/sonar/server/notifications/NotificationManager.java85
-rw-r--r--sonar-server/src/main/java/org/sonar/server/notifications/NotificationQueue.java52
-rw-r--r--sonar-server/src/main/java/org/sonar/server/notifications/NotificationService.java109
-rw-r--r--sonar-server/src/main/java/org/sonar/server/notifications/email/EmailMessage.java110
-rw-r--r--sonar-server/src/main/java/org/sonar/server/notifications/email/EmailMessageTemplate.java32
-rw-r--r--sonar-server/src/main/java/org/sonar/server/notifications/email/EmailNotificationChannel.java111
-rw-r--r--sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewAssignedToMe.java43
-rw-r--r--sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewCreatedByMe.java41
-rw-r--r--sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewEmailTemplate.java45
-rw-r--r--sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewNotification.java53
-rw-r--r--sonar-server/src/main/java/org/sonar/server/notifications/reviews/ReviewsNotificationManager.java62
-rw-r--r--sonar-server/src/main/java/org/sonar/server/platform/Platform.java27
-rw-r--r--sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java5
-rw-r--r--sonar-server/src/main/webapp/WEB-INF/app/models/review.rb4
-rw-r--r--sonar-server/src/test/java/org/sonar/server/notifications/NotificationManagerTest.java190
-rw-r--r--sonar-server/src/test/java/org/sonar/server/notifications/NotificationServiceTest.java51
-rw-r--r--sonar-server/src/test/java/org/sonar/server/notifications/email/EmailNotificationChannelTest.java82
-rw-r--r--sonar-server/src/test/java/org/sonar/server/notifications/reviews/CommentOnReviewAssignedToMeTest.java61
-rw-r--r--sonar-server/src/test/java/org/sonar/server/notifications/reviews/CommentOnReviewCreatedByMeTest.java53
-rw-r--r--sonar-server/src/test/java/org/sonar/server/notifications/reviews/ReviewsNotificationManagerTest.java57
-rw-r--r--sonar-server/src/test/resources/org/sonar/server/notifications/reviews/fixture.xml15
25 files changed, 1437 insertions, 3 deletions
diff --git a/sonar-core/src/main/java/org/sonar/jpa/entity/Review.java b/sonar-core/src/main/java/org/sonar/jpa/entity/Review.java
index 54984c4baf5..77dce349146 100644
--- a/sonar-core/src/main/java/org/sonar/jpa/entity/Review.java
+++ b/sonar-core/src/main/java/org/sonar/jpa/entity/Review.java
@@ -46,6 +46,10 @@ public final class Review {
return id;
}
+ public void setId(Long id) {
+ this.id = id;
+ }
+
/**
* @return id of user, who created this review
*/
@@ -53,6 +57,11 @@ public final class Review {
return userId;
}
+ public Review setUserId(Integer userId) {
+ this.userId = userId;
+ return this;
+ }
+
/**
* @return id of assigned user or null, if not assigned
*/
@@ -60,8 +69,18 @@ public final class Review {
return assigneeId;
}
+ public Review setAssigneeId(Integer assigneeId) {
+ this.assigneeId = assigneeId;
+ return this;
+ }
+
public String getTitle() {
return title;
}
+ public Review setTitle(String title) {
+ this.title = title;
+ return this;
+ }
+
}
diff --git a/sonar-server/src/main/java/org/sonar/server/notifications/Notification.java b/sonar-server/src/main/java/org/sonar/server/notifications/Notification.java
new file mode 100644
index 00000000000..395f90af602
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/notifications/Notification.java
@@ -0,0 +1,28 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications;
+
+/**
+ * Marker interface for data model objects that represents notifications.
+ *
+ * @since 2.10
+ */
+public interface Notification {
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/notifications/NotificationChannel.java b/sonar-server/src/main/java/org/sonar/server/notifications/NotificationChannel.java
new file mode 100644
index 00000000000..1b2726768b1
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/notifications/NotificationChannel.java
@@ -0,0 +1,50 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications;
+
+import org.sonar.api.ServerExtension;
+
+import java.io.Serializable;
+
+/**
+ * Provides logic to deliver notification.
+ * For example:
+ * <ul>
+ * <li>email - sends email as soon as possible</li>
+ * <li>email (digest) - collects notifications and sends them together once a day</li>
+ * <li>gtalk - sends a chat message as soon as possible</li>
+ * </ul>
+ *
+ * @since 2.10
+ */
+public abstract class NotificationChannel implements ServerExtension {
+
+ /**
+ * @return unique key of this channel
+ */
+ public String getKey() {
+ return getClass().getSimpleName();
+ }
+
+ public abstract Serializable createDataForPersistance(Notification notification, Integer userId);
+
+ public abstract void deliver(Serializable notificationData);
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/notifications/NotificationDispatcher.java b/sonar-server/src/main/java/org/sonar/server/notifications/NotificationDispatcher.java
new file mode 100644
index 00000000000..a82ee0004b1
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/notifications/NotificationDispatcher.java
@@ -0,0 +1,55 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications;
+
+import org.sonar.api.ServerExtension;
+
+/**
+ * Provides logic to determine which users are interested in receiving notification.
+ * Has no knowledge about the way of delivery.
+ * For example:
+ * <ul>
+ * <li>notify me when someone comments on review created by me</li>
+ * <li>notify me when someone comments on review assigned to me</li>
+ * <li>notify me when someone mentions me in comment for review</li>
+ * <li>send me system notifications (like password reset, account creation, ...)</li>
+ * </ul>
+ *
+ * @since 2.10
+ */
+public abstract class NotificationDispatcher implements ServerExtension {
+
+ public interface Context {
+ void addUser(Integer userId);
+ }
+
+ /**
+ * @return unique key of this dispatcher
+ */
+ public String getKey() {
+ return getClass().getSimpleName();
+ }
+
+ /**
+ * @return recipients
+ */
+ public abstract void dispatch(Notification notification, Context context);
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/notifications/NotificationManager.java b/sonar-server/src/main/java/org/sonar/server/notifications/NotificationManager.java
new file mode 100644
index 00000000000..304f3c664b8
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/notifications/NotificationManager.java
@@ -0,0 +1,85 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.Sets;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.utils.TimeProfiler;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @since 2.10
+ */
+public class NotificationManager implements ServerComponent { // TODO should be available on batch side too
+
+ private static final Logger LOG = LoggerFactory.getLogger(NotificationManager.class);
+ private static final TimeProfiler TIME_PROFILER = new TimeProfiler().setLogger(LOG);
+
+ private NotificationQueue queue;
+ private NotificationDispatcher[] dispatchers;
+ private NotificationChannel[] channels;
+
+ public NotificationManager(NotificationQueue queue, NotificationDispatcher[] dispatchers, NotificationChannel[] channels) {
+ this.queue = queue;
+ this.dispatchers = dispatchers;
+ this.channels = channels;
+ }
+
+ public void scheduleForSending(Notification notification) {
+ TIME_PROFILER.start("Scheduling " + notification);
+ SetMultimap<Integer, NotificationChannel> recipients = HashMultimap.create();
+ for (NotificationChannel channel : channels) {
+ for (NotificationDispatcher dispatcher : dispatchers) {
+ final Set<Integer> possibleRecipients = Sets.newHashSet();
+ NotificationDispatcher.Context context = new NotificationDispatcher.Context() {
+ public void addUser(Integer userId) {
+ possibleRecipients.add(userId);
+ }
+ };
+ dispatcher.dispatch(notification, context);
+ for (Integer userId : possibleRecipients) {
+ if (isEnabled(userId, channel, dispatcher)) {
+ recipients.put(userId, channel);
+ }
+ }
+ }
+ }
+ for (Map.Entry<Integer, NotificationChannel> entry : recipients.entries()) {
+ Integer userId = entry.getKey();
+ NotificationChannel channel = entry.getValue();
+ LOG.info("For user {} via {}", userId, channel.getKey());
+ Serializable notificationData = channel.createDataForPersistance(notification, userId);
+ queue.add(notificationData, channel);
+ }
+ TIME_PROFILER.stop();
+ }
+
+ boolean isEnabled(Integer userId, NotificationChannel channel, NotificationDispatcher dispatcher) {
+ return true; // FIXME for the moment we will accept everything
+ }
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/notifications/NotificationQueue.java b/sonar-server/src/main/java/org/sonar/server/notifications/NotificationQueue.java
new file mode 100644
index 00000000000..bf02333d9a7
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/notifications/NotificationQueue.java
@@ -0,0 +1,52 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications;
+
+import org.sonar.api.ServerComponent;
+
+import java.io.Serializable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * @since 2.10
+ */
+public class NotificationQueue implements ServerComponent {
+
+ private ConcurrentLinkedQueue<Element> queue = new ConcurrentLinkedQueue<Element>();
+
+ public static class Element {
+ String channelKey;
+ Serializable notificationData;
+
+ public Element(String channelKey, Serializable notificationData) {
+ this.channelKey = channelKey;
+ this.notificationData = notificationData;
+ }
+ }
+
+ public Element get() {
+ return queue.poll();
+ }
+
+ public void add(Serializable notificationData, NotificationChannel channel) {
+ queue.add(new Element(channel.getKey(), notificationData));
+ }
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/notifications/NotificationService.java b/sonar-server/src/main/java/org/sonar/server/notifications/NotificationService.java
new file mode 100644
index 00000000000..6faab4f133f
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/notifications/NotificationService.java
@@ -0,0 +1,109 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications;
+
+import com.google.common.collect.Maps;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.ServerComponent;
+import org.sonar.server.notifications.NotificationQueue.Element;
+
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @since 2.10
+ */
+public class NotificationService implements ServerComponent {
+
+ private static Logger LOG = LoggerFactory.getLogger(NotificationService.class);
+
+ private ScheduledExecutorService executorService;
+ private long period = 10; // FIXME small value just for tests
+ private NotificationQueue queue;
+
+ private Map<String, NotificationChannel> channels = Maps.newHashMap();
+
+ /**
+ * Default constructor when no channels.
+ */
+ public NotificationService(NotificationQueue queue) {
+ this(queue, new NotificationChannel[0]);
+ LOG.warn("There is no channels - all notifications would be ignored!");
+ }
+
+ public NotificationService(NotificationQueue queue, NotificationChannel[] channels) {
+ this.queue = queue;
+ for (NotificationChannel channel : channels) {
+ this.channels.put(channel.getKey(), channel);
+ }
+ }
+
+ /**
+ * Visibility has been relaxed for tests.
+ */
+ void setPeriod(long milliseconds) {
+ this.period = milliseconds;
+ }
+
+ public void start() {
+ executorService = Executors.newSingleThreadScheduledExecutor();
+ executorService.scheduleWithFixedDelay(new Runnable() {
+ public void run() {
+ processQueue();
+ }
+ }, 0, period, TimeUnit.MILLISECONDS);
+ LOG.info("Notification service started");
+ }
+
+ public void stop() {
+ try {
+ executorService.awaitTermination(period, TimeUnit.MILLISECONDS);
+ executorService.shutdown();
+ } catch (InterruptedException e) {
+ LOG.error("Error during stop of notification service", e);
+ }
+ LOG.info("Notification service stopped");
+ }
+
+ /**
+ * Visibility has been relaxed for tests.
+ */
+ void processQueue() {
+ NotificationQueue.Element element = queue.get();
+ while (element != null) {
+ deliver(element);
+ element = queue.get();
+ }
+ }
+
+ /**
+ * Visibility has been relaxed for tests.
+ */
+ void deliver(Element element) {
+ NotificationChannel channel = channels.get(element.channelKey);
+ if (channel != null) {
+ channel.deliver(element.notificationData);
+ }
+ }
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/notifications/email/EmailMessage.java b/sonar-server/src/main/java/org/sonar/server/notifications/email/EmailMessage.java
new file mode 100644
index 00000000000..c64b3b3e175
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/notifications/email/EmailMessage.java
@@ -0,0 +1,110 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications.email;
+
+import java.io.Serializable;
+
+/**
+ * @since 2.10
+ */
+public class EmailMessage implements Serializable {
+
+ private String from;
+ private String to;
+ private String subject;
+ private String message;
+ private String messageId;
+
+ /**
+ * @param from full name of user, who initiated this message or null, if message was initiated by Sonar
+ */
+ public EmailMessage setFrom(String from) {
+ this.from = from;
+ return this;
+ }
+
+ /**
+ * @see #setFrom(String)
+ */
+ public String getFrom() {
+ return from;
+ }
+
+ /**
+ * @param to email address where to send this message
+ */
+ public EmailMessage setTo(String to) {
+ this.to = to;
+ return this;
+ }
+
+ /**
+ * @see #setTo(String)
+ */
+ public String getTo() {
+ return to;
+ }
+
+ /**
+ * @param subject message subject
+ */
+ public EmailMessage setSubject(String subject) {
+ this.subject = subject;
+ return this;
+ }
+
+ /**
+ * @see #setSubject(String)
+ */
+ public String getSubject() {
+ return subject;
+ }
+
+ /**
+ * @param message message body
+ */
+ public EmailMessage setMessage(String message) {
+ this.message = message;
+ return this;
+ }
+
+ /**
+ * @see #setMessage(String)
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * @param messageId id of message for threading
+ */
+ public EmailMessage setMessageId(String messageId) {
+ this.messageId = messageId;
+ return this;
+ }
+
+ /**
+ * @see #setMessageId(String)
+ */
+ public String getMessageId() {
+ return messageId;
+ }
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/notifications/email/EmailMessageTemplate.java b/sonar-server/src/main/java/org/sonar/server/notifications/email/EmailMessageTemplate.java
new file mode 100644
index 00000000000..ed465c724cd
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/notifications/email/EmailMessageTemplate.java
@@ -0,0 +1,32 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications.email;
+
+import org.sonar.api.ServerExtension;
+import org.sonar.server.notifications.Notification;
+
+/**
+ * @since 2.10
+ */
+public abstract class EmailMessageTemplate implements ServerExtension {
+
+ public abstract EmailMessage format(Notification notification);
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/notifications/email/EmailNotificationChannel.java b/sonar-server/src/main/java/org/sonar/server/notifications/email/EmailNotificationChannel.java
new file mode 100644
index 00000000000..a0b12e14a13
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/notifications/email/EmailNotificationChannel.java
@@ -0,0 +1,111 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications.email;
+
+import org.codehaus.plexus.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.server.notifications.Notification;
+import org.sonar.server.notifications.NotificationChannel;
+
+import java.io.Serializable;
+
+/**
+ * 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 = LoggerFactory.getLogger(EmailNotificationChannel.class);
+
+ private static final String FROM_DEFAULT = "Sonar";
+ private static final String SUBJECT_PREFIX = "[Sonar]";
+ private static final String SUBJECT_DEFAULT = "Notification";
+
+ private EmailMessageTemplate[] templates;
+
+ public EmailNotificationChannel(EmailMessageTemplate[] templates) {
+ this.templates = templates;
+ }
+
+ @Override
+ public Serializable createDataForPersistance(Notification notification, Integer userId) {
+ for (EmailMessageTemplate template : templates) {
+ EmailMessage email = template.format(notification);
+ if (email != null) {
+ email.setTo(userId.toString()); // TODO should be valid email@address
+ return email;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void deliver(Serializable notificationData) {
+ EmailMessage email = (EmailMessage) notificationData;
+ LOG.info("Email:\n{}", create(email));
+ }
+
+ /**
+ * Visibility has been relaxed for tests.
+ */
+ String create(EmailMessage email) {
+ // TODO
+ String serverUrl = "http://nemo.sonarsource.org";
+ String domain = "nemo.sonarsource.org";
+ String listId = "<sonar." + domain + ">";
+ String from = StringUtils.defaultString(email.getFrom(), FROM_DEFAULT) + " <noreply@" + domain + ">";
+ String subject = SUBJECT_PREFIX + " " + StringUtils.defaultString(email.getSubject(), SUBJECT_DEFAULT);
+ String permalink = null;
+ StringBuilder sb = new StringBuilder();
+ if (StringUtils.isNotEmpty(email.getMessageId())) {
+ subject = "Re: " + subject;
+ String messageId = "<" + email.getMessageId() + "@" + domain + ">";
+ appendHeader(sb, "Message-Id", messageId);
+ appendHeader(sb, "In-Reply-To", messageId);
+ appendHeader(sb, "References", messageId);
+ permalink = serverUrl + '/' + email.getMessageId();
+ }
+ appendHeader(sb, "List-Id", listId);
+ appendHeader(sb, "List-Archive", serverUrl);
+ appendHeader(sb, "From", from);
+ appendHeader(sb, "To", email.getTo());
+ appendHeader(sb, "Subject", subject);
+ sb.append('\n')
+ .append(email.getMessage()).append('\n');
+ if (permalink != null) {
+ sb.append('\n')
+ .append("--\n")
+ .append("View it in Sonar: ").append(permalink);
+ }
+ return sb.toString();
+ }
+
+ private void appendHeader(StringBuilder sb, String header, String value) {
+ sb.append(header).append(": ").append(value).append('\n');
+ }
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewAssignedToMe.java b/sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewAssignedToMe.java
new file mode 100644
index 00000000000..51af872a075
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewAssignedToMe.java
@@ -0,0 +1,43 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications.reviews;
+
+import org.sonar.jpa.entity.Review;
+import org.sonar.server.notifications.Notification;
+import org.sonar.server.notifications.NotificationDispatcher;
+
+/**
+ * This dispatcher means: "notify me when when someone comments on review assigned to me".
+ *
+ * @since 2.10
+ */
+public class CommentOnReviewAssignedToMe extends NotificationDispatcher {
+
+ @Override
+ public void dispatch(Notification notification, Context context) {
+ if (notification instanceof CommentOnReviewNotification) {
+ Review review = ((CommentOnReviewNotification) notification).getReview();
+ if (review.getAssigneeId() != null) {
+ context.addUser(review.getAssigneeId());
+ }
+ }
+ }
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewCreatedByMe.java b/sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewCreatedByMe.java
new file mode 100644
index 00000000000..868e6286108
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewCreatedByMe.java
@@ -0,0 +1,41 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications.reviews;
+
+import org.sonar.jpa.entity.Review;
+import org.sonar.server.notifications.Notification;
+import org.sonar.server.notifications.NotificationDispatcher;
+
+/**
+ * This dispatcher means: "notify me when when someone comments on review created by me".
+ *
+ * @since 2.10
+ */
+public class CommentOnReviewCreatedByMe extends NotificationDispatcher {
+
+ @Override
+ public void dispatch(Notification notification, Context context) {
+ if (notification instanceof CommentOnReviewNotification) {
+ Review review = ((CommentOnReviewNotification) notification).getReview();
+ context.addUser(review.getUserId());
+ }
+ }
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewEmailTemplate.java b/sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewEmailTemplate.java
new file mode 100644
index 00000000000..eb982500128
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewEmailTemplate.java
@@ -0,0 +1,45 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications.reviews;
+
+import org.sonar.server.notifications.Notification;
+import org.sonar.server.notifications.email.EmailMessage;
+import org.sonar.server.notifications.email.EmailMessageTemplate;
+
+/**
+ * Email template for {@link CommentOnReviewNotification}.
+ */
+public class CommentOnReviewEmailTemplate extends EmailMessageTemplate {
+
+ @Override
+ public EmailMessage format(Notification notification) {
+ if (notification instanceof CommentOnReviewNotification) {
+ CommentOnReviewNotification event = (CommentOnReviewNotification) notification;
+ EmailMessage email = new EmailMessage()
+ .setFrom(event.getAuthor().getName())
+ .setMessageId("review/" + event.getReview().getId())
+ .setSubject(event.getReview().getTitle())
+ .setMessage(event.getComment());
+ return email;
+ }
+ return null;
+ }
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewNotification.java b/sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewNotification.java
new file mode 100644
index 00000000000..48a89b36c40
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/notifications/reviews/CommentOnReviewNotification.java
@@ -0,0 +1,53 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications.reviews;
+
+import org.sonar.api.database.model.User;
+import org.sonar.jpa.entity.Review;
+import org.sonar.server.notifications.Notification;
+
+/**
+ * @since 2.10
+ */
+public class CommentOnReviewNotification implements Notification {
+
+ private Review review;
+ private User author;
+ private String comment;
+
+ public CommentOnReviewNotification(Review review, User author, String comment) {
+ this.review = review;
+ this.author = author;
+ this.comment = comment;
+ }
+
+ public Review getReview() {
+ return review;
+ }
+
+ public User getAuthor() {
+ return author;
+ }
+
+ public String getComment() {
+ return comment;
+ }
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/notifications/reviews/ReviewsNotificationManager.java b/sonar-server/src/main/java/org/sonar/server/notifications/reviews/ReviewsNotificationManager.java
new file mode 100644
index 00000000000..602f4e51791
--- /dev/null
+++ b/sonar-server/src/main/java/org/sonar/server/notifications/reviews/ReviewsNotificationManager.java
@@ -0,0 +1,62 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications.reviews;
+
+import org.sonar.api.ServerComponent;
+import org.sonar.api.database.model.User;
+import org.sonar.jpa.entity.Review;
+import org.sonar.jpa.session.DatabaseSessionFactory;
+import org.sonar.server.notifications.NotificationManager;
+
+/**
+ * @since 2.10
+ */
+public class ReviewsNotificationManager implements ServerComponent {
+
+ private DatabaseSessionFactory sessionFactory;
+ private NotificationManager notificationManager;
+
+ public ReviewsNotificationManager(DatabaseSessionFactory sessionFactory, NotificationManager notificationManager) {
+ this.sessionFactory = sessionFactory;
+ this.notificationManager = notificationManager;
+ }
+
+ /**
+ * Visibility has been relaxed for tests.
+ */
+ User getUserById(Integer id) {
+ return sessionFactory.getSession().getEntity(User.class, id);
+ }
+
+ /**
+ * Visibility has been relaxed for tests.
+ */
+ Review getReviewById(Long id) {
+ return sessionFactory.getSession().getEntity(Review.class, id);
+ }
+
+ public void notifyCommentAdded(Long reviewId, Integer userId, String comment) {
+ Review review = getReviewById(reviewId);
+ User author = getUserById(userId);
+ CommentOnReviewNotification notification = new CommentOnReviewNotification(review, author, comment);
+ notificationManager.scheduleForSending(notification);
+ }
+
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
index 6afe5f7d449..160602d419e 100644
--- a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
+++ b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
@@ -40,7 +40,10 @@ import org.sonar.api.utils.TimeProfiler;
import org.sonar.core.components.DefaultMetricFinder;
import org.sonar.core.components.DefaultModelFinder;
import org.sonar.core.components.DefaultRuleFinder;
-import org.sonar.jpa.dao.*;
+import org.sonar.jpa.dao.DaoFacade;
+import org.sonar.jpa.dao.MeasuresDao;
+import org.sonar.jpa.dao.ProfilesDao;
+import org.sonar.jpa.dao.RulesDao;
import org.sonar.jpa.session.DatabaseSessionFactory;
import org.sonar.jpa.session.DatabaseSessionProvider;
import org.sonar.jpa.session.ThreadLocalDatabaseSessionFactory;
@@ -52,9 +55,18 @@ import org.sonar.server.database.EmbeddedDatabaseFactory;
import org.sonar.server.database.JndiDatabaseConnector;
import org.sonar.server.filters.FilterExecutor;
import org.sonar.server.mavendeployer.MavenRepository;
+import org.sonar.server.notifications.NotificationManager;
+import org.sonar.server.notifications.NotificationQueue;
+import org.sonar.server.notifications.NotificationService;
+import org.sonar.server.notifications.email.EmailNotificationChannel;
+import org.sonar.server.notifications.reviews.CommentOnReviewAssignedToMe;
+import org.sonar.server.notifications.reviews.CommentOnReviewCreatedByMe;
+import org.sonar.server.notifications.reviews.CommentOnReviewEmailTemplate;
+import org.sonar.server.notifications.reviews.ReviewsNotificationManager;
import org.sonar.server.plugins.*;
import org.sonar.server.qualitymodel.DefaultModelManager;
-import org.sonar.server.rules.*;
+import org.sonar.server.rules.ProfilesConsole;
+import org.sonar.server.rules.RulesConsole;
import org.sonar.server.startup.*;
import org.sonar.server.ui.AuthenticatorFactory;
import org.sonar.server.ui.CodeColorizers;
@@ -179,6 +191,17 @@ public final class Platform {
servicesContainer.as(Characteristics.CACHE).addComponent(RulesConsole.class);
servicesContainer.as(Characteristics.CACHE).addComponent(JRubyI18n.class);
+ // Notifications
+ servicesContainer.as(Characteristics.CACHE).addComponent(NotificationQueue.class);
+ servicesContainer.as(Characteristics.CACHE).addComponent(NotificationService.class);
+ servicesContainer.as(Characteristics.CACHE).addComponent(NotificationManager.class);
+ servicesContainer.as(Characteristics.CACHE).addComponent(ReviewsNotificationManager.class);
+ // FIXME next four lines here just for tests:
+ servicesContainer.as(Characteristics.CACHE).addComponent(EmailNotificationChannel.class);
+ servicesContainer.as(Characteristics.CACHE).addComponent(CommentOnReviewEmailTemplate.class);
+ servicesContainer.as(Characteristics.CACHE).addComponent(CommentOnReviewAssignedToMe.class);
+ servicesContainer.as(Characteristics.CACHE).addComponent(CommentOnReviewCreatedByMe.class);
+
servicesContainer.start();
}
diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
index 8c60ad0fab7..473bc679f65 100644
--- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
+++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
@@ -41,6 +41,7 @@ import org.sonar.server.configuration.ProfilesManager;
import org.sonar.server.filters.Filter;
import org.sonar.server.filters.FilterExecutor;
import org.sonar.server.filters.FilterResult;
+import org.sonar.server.notifications.reviews.ReviewsNotificationManager;
import org.sonar.server.platform.Platform;
import org.sonar.server.plugins.*;
import org.sonar.server.rules.ProfilesConsole;
@@ -308,6 +309,10 @@ public final class JRubyFacade {
return i18n.message(rubyLocale, key, defaultValue, parameters);
}
+ public ReviewsNotificationManager getReviewsNotificationManager() {
+ return getContainer().getComponent(ReviewsNotificationManager.class);
+ }
+
public PicoContainer getContainer() {
return Platform.getInstance().getContainer();
}
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/review.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/review.rb
index 9d820401d0d..94df7aed6d6 100644
--- a/sonar-server/src/main/webapp/WEB-INF/app/models/review.rb
+++ b/sonar-server/src/main/webapp/WEB-INF/app/models/review.rb
@@ -59,8 +59,10 @@ class Review < ActiveRecord::Base
# - :user
# - :text
def create_comment(params={})
- comments.create!(params)
+ comment = comments.create!(params)
touch
+
+ Java::OrgSonarServerUi::JRubyFacade.getInstance().getReviewsNotificationManager().notifyCommentAdded(id.to_i, comment.user.id.to_i, comment.text.to_java)
end
def edit_comment(comment_id, comment_text)
diff --git a/sonar-server/src/test/java/org/sonar/server/notifications/NotificationManagerTest.java b/sonar-server/src/test/java/org/sonar/server/notifications/NotificationManagerTest.java
new file mode 100644
index 00000000000..b1a00deb603
--- /dev/null
+++ b/sonar-server/src/test/java/org/sonar/server/notifications/NotificationManagerTest.java
@@ -0,0 +1,190 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.Serializable;
+
+public class NotificationManagerTest {
+
+ private static Integer USER_SIMON = 1;
+ private static Integer USER_EVGENY = 2;
+
+ private NotificationChannel emailChannel;
+ private NotificationChannel gtalkChannel;
+
+ private NotificationDispatcher commentOnReviewAssignedToMe;
+ private Integer assignee;
+ private NotificationDispatcher commentOnReviewCreatedByMe;
+ private Integer creator;
+
+ private NotificationQueue queue;
+ private NotificationManager manager;
+
+ @Before
+ public void setUp() {
+ emailChannel = mock(NotificationChannel.class);
+ when(emailChannel.getKey()).thenReturn("email");
+ doAnswer(new Answer<Serializable>() {
+ public Serializable answer(InvocationOnMock invocation) throws Throwable {
+ return (Serializable) invocation.getArguments()[1];
+ }
+ }).when(emailChannel).createDataForPersistance(any(Notification.class), any(Integer.class));
+
+ gtalkChannel = mock(NotificationChannel.class);
+ when(gtalkChannel.getKey()).thenReturn("gtalk");
+ doAnswer(new Answer<Serializable>() {
+ public Serializable answer(InvocationOnMock invocation) throws Throwable {
+ return (Serializable) invocation.getArguments()[1];
+ }
+ }).when(gtalkChannel).createDataForPersistance(any(Notification.class), any(Integer.class));
+
+ commentOnReviewAssignedToMe = mock(NotificationDispatcher.class);
+ when(commentOnReviewAssignedToMe.getKey()).thenReturn("comment on review assigned to me");
+ doAnswer(new Answer<Object>() {
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ ((NotificationDispatcher.Context) invocation.getArguments()[1]).addUser(assignee);
+ return null;
+ }
+ }).when(commentOnReviewAssignedToMe).dispatch(any(Notification.class), any(NotificationDispatcher.Context.class));
+
+ commentOnReviewCreatedByMe = mock(NotificationDispatcher.class);
+ when(commentOnReviewCreatedByMe.getKey()).thenReturn("comment on review created by me");
+ doAnswer(new Answer<Object>() {
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ ((NotificationDispatcher.Context) invocation.getArguments()[1]).addUser(creator);
+ return null;
+ }
+ }).when(commentOnReviewCreatedByMe).dispatch(any(Notification.class), any(NotificationDispatcher.Context.class));
+
+ NotificationDispatcher[] dispatchers = new NotificationDispatcher[] { commentOnReviewAssignedToMe, commentOnReviewCreatedByMe };
+ NotificationChannel[] channels = new NotificationChannel[] { emailChannel, gtalkChannel };
+ queue = mock(NotificationQueue.class);
+ manager = spy(new NotificationManager(queue, dispatchers, channels));
+ doReturn(false).when(manager).isEnabled(any(Integer.class), any(NotificationChannel.class), any(NotificationDispatcher.class));
+ }
+
+ /**
+ * 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() {
+ doReturn(true).when(manager).isEnabled(USER_SIMON, emailChannel, commentOnReviewAssignedToMe);
+ doReturn(true).when(manager).isEnabled(USER_SIMON, emailChannel, commentOnReviewCreatedByMe);
+
+ Notification notification = mock(Notification.class);
+ creator = USER_SIMON;
+ assignee = USER_SIMON;
+
+ manager.scheduleForSending(notification);
+
+ verify(queue).add(USER_SIMON, emailChannel);
+ verifyNoMoreInteractions(queue);
+ }
+
+ /**
+ * 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() {
+ doReturn(true).when(manager).isEnabled(USER_EVGENY, gtalkChannel, commentOnReviewCreatedByMe);
+ doReturn(true).when(manager).isEnabled(USER_SIMON, emailChannel, commentOnReviewAssignedToMe);
+
+ Notification notification = mock(Notification.class);
+ creator = USER_EVGENY;
+ assignee = USER_SIMON;
+
+ manager.scheduleForSending(notification);
+
+ verify(queue).add(USER_EVGENY, gtalkChannel);
+ verify(queue).add(USER_SIMON, emailChannel);
+ verifyNoMoreInteractions(queue);
+ }
+
+ /**
+ * 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() {
+ doReturn(true).when(manager).isEnabled(USER_SIMON, emailChannel, commentOnReviewAssignedToMe);
+ doReturn(true).when(manager).isEnabled(USER_SIMON, gtalkChannel, commentOnReviewAssignedToMe);
+
+ Notification notification = mock(Notification.class);
+ creator = USER_EVGENY;
+ assignee = USER_SIMON;
+
+ manager.scheduleForSending(notification);
+
+ verify(queue).add(USER_SIMON, gtalkChannel);
+ verify(queue).add(USER_SIMON, emailChannel);
+ verifyNoMoreInteractions(queue);
+ }
+
+ /**
+ * 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() {
+ Notification notification = mock(Notification.class);
+ creator = USER_EVGENY;
+ assignee = USER_SIMON;
+
+ manager.scheduleForSending(notification);
+
+ verifyNoMoreInteractions(queue);
+ }
+
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/notifications/NotificationServiceTest.java b/sonar-server/src/test/java/org/sonar/server/notifications/NotificationServiceTest.java
new file mode 100644
index 00000000000..7c1c0b03615
--- /dev/null
+++ b/sonar-server/src/test/java/org/sonar/server/notifications/NotificationServiceTest.java
@@ -0,0 +1,51 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications;
+
+import static org.mockito.Mockito.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class NotificationServiceTest {
+
+ private NotificationQueue queue;
+ private NotificationService service;
+
+ @Before
+ public void setUp() {
+ queue = mock(NotificationQueue.class);
+ service = spy(new NotificationService(queue));
+ service.setPeriod(10);
+ }
+
+ @Test
+ public void shouldPeriodicallyProcessQueue() throws Exception {
+ service.start();
+
+ NotificationQueue.Element element = mock(NotificationQueue.Element.class);
+ when(queue.get()).thenReturn(element);
+ Thread.sleep(50);
+
+ verify(service, atLeastOnce()).deliver(element);
+
+ service.stop();
+ }
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/notifications/email/EmailNotificationChannelTest.java b/sonar-server/src/test/java/org/sonar/server/notifications/email/EmailNotificationChannelTest.java
new file mode 100644
index 00000000000..4c9c3544f4d
--- /dev/null
+++ b/sonar-server/src/test/java/org/sonar/server/notifications/email/EmailNotificationChannelTest.java
@@ -0,0 +1,82 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications.email;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class EmailNotificationChannelTest {
+
+ private EmailNotificationChannel channel;
+
+ @Before
+ public void setUp() {
+ channel = new EmailNotificationChannel(null);
+ }
+
+ @Test
+ public void shouldCreateEmail() {
+ EmailMessage email = new EmailMessage()
+ .setMessageId("reviews/view/1")
+ .setFrom("Evgeny Mandrikov")
+ .setTo("simon.brandhof@sonarcource.com")
+ .setSubject("Review #3")
+ .setMessage("I'll take care of this violation.");
+ String expected = "" +
+ "Message-Id: <reviews/view/1@nemo.sonarsource.org>\n" +
+ "In-Reply-To: <reviews/view/1@nemo.sonarsource.org>\n" +
+ "References: <reviews/view/1@nemo.sonarsource.org>\n" +
+ "List-Id: <sonar.nemo.sonarsource.org>\n" +
+ "List-Archive: http://nemo.sonarsource.org\n" +
+ "From: Evgeny Mandrikov <noreply@nemo.sonarsource.org>\n" +
+ "To: simon.brandhof@sonarcource.com\n" +
+ "Subject: Re: [Sonar] Review #3\n" +
+ "\n" +
+ "I'll take care of this violation.\n" +
+ "\n" +
+ "--\n" +
+ "View it in Sonar: http://nemo.sonarsource.org/reviews/view/1";
+ String message = channel.create(email);
+ System.out.println(message);
+ assertThat(message, is(expected));
+ }
+
+ @Test
+ public void shouldCreateDefaultEmail() {
+ EmailMessage email = new EmailMessage()
+ .setTo("simon.brandhof@sonarcource.com")
+ .setMessage("Message");
+ String expected = "" +
+ "List-Id: <sonar.nemo.sonarsource.org>\n" +
+ "List-Archive: http://nemo.sonarsource.org\n" +
+ "From: Sonar <noreply@nemo.sonarsource.org>\n" +
+ "To: simon.brandhof@sonarcource.com\n" +
+ "Subject: [Sonar] Notification\n" +
+ "\n" +
+ "Message\n";
+ String message = channel.create(email);
+ System.out.println(message);
+ assertThat(message, is(expected));
+ }
+
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/notifications/reviews/CommentOnReviewAssignedToMeTest.java b/sonar-server/src/test/java/org/sonar/server/notifications/reviews/CommentOnReviewAssignedToMeTest.java
new file mode 100644
index 00000000000..62c375c500c
--- /dev/null
+++ b/sonar-server/src/test/java/org/sonar/server/notifications/reviews/CommentOnReviewAssignedToMeTest.java
@@ -0,0 +1,61 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications.reviews;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.database.model.User;
+import org.sonar.jpa.entity.Review;
+import org.sonar.server.notifications.NotificationDispatcher;
+
+public class CommentOnReviewAssignedToMeTest {
+
+ private NotificationDispatcher.Context context;
+ private CommentOnReviewAssignedToMe dispatcher;
+
+ @Before
+ public void setUp() {
+ context = mock(NotificationDispatcher.Context.class);
+ dispatcher = new CommentOnReviewAssignedToMe();
+ }
+
+ @Test
+ public void shouldDispatchToAssignee() {
+ CommentOnReviewNotification notification = new CommentOnReviewNotification(new Review().setAssigneeId(1), new User(), "comment");
+ dispatcher.dispatch(notification, context);
+ verify(context).addUser(1);
+
+ notification = new CommentOnReviewNotification(new Review().setAssigneeId(2), new User(), "comment");
+ dispatcher.dispatch(notification, context);
+ verify(context).addUser(2);
+ }
+
+ @Test
+ public void shouldNotDispatchWhenNotAssigned() {
+ CommentOnReviewNotification notification = new CommentOnReviewNotification(new Review(), new User(), "comment");
+ dispatcher.dispatch(notification, context);
+ verifyNoMoreInteractions(context);
+ }
+
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/notifications/reviews/CommentOnReviewCreatedByMeTest.java b/sonar-server/src/test/java/org/sonar/server/notifications/reviews/CommentOnReviewCreatedByMeTest.java
new file mode 100644
index 00000000000..4f1c1766153
--- /dev/null
+++ b/sonar-server/src/test/java/org/sonar/server/notifications/reviews/CommentOnReviewCreatedByMeTest.java
@@ -0,0 +1,53 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications.reviews;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.database.model.User;
+import org.sonar.jpa.entity.Review;
+import org.sonar.server.notifications.NotificationDispatcher;
+
+public class CommentOnReviewCreatedByMeTest {
+
+ private NotificationDispatcher.Context context;
+ private CommentOnReviewCreatedByMe dispatcher;
+
+ @Before
+ public void setUp() {
+ context = mock(NotificationDispatcher.Context.class);
+ dispatcher = new CommentOnReviewCreatedByMe();
+ }
+
+ @Test
+ public void shouldDispatchToCreator() {
+ CommentOnReviewNotification notification = new CommentOnReviewNotification(new Review().setUserId(1), new User(), "comment");
+ dispatcher.dispatch(notification, context);
+ verify(context).addUser(1);
+
+ notification = new CommentOnReviewNotification(new Review().setUserId(2), new User(), "comment");
+ dispatcher.dispatch(notification, context);
+ verify(context).addUser(2);
+ }
+
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/notifications/reviews/ReviewsNotificationManagerTest.java b/sonar-server/src/test/java/org/sonar/server/notifications/reviews/ReviewsNotificationManagerTest.java
new file mode 100644
index 00000000000..3dd2e3e6f3b
--- /dev/null
+++ b/sonar-server/src/test/java/org/sonar/server/notifications/reviews/ReviewsNotificationManagerTest.java
@@ -0,0 +1,57 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.server.notifications.reviews;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.database.model.User;
+import org.sonar.jpa.entity.Review;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+public class ReviewsNotificationManagerTest extends AbstractDbUnitTestCase {
+
+ private ReviewsNotificationManager manager;
+
+ @Before
+ public void setUp() {
+ setupData(getClass().getResourceAsStream("fixture.xml"));
+ manager = new ReviewsNotificationManager(getSessionFactory(), null);
+ }
+
+ @Test
+ public void shouldGetReviewById() {
+ Review review = manager.getReviewById(3L);
+ assertThat(review.getUserId(), is(1));
+ assertThat(review.getAssigneeId(), is(2));
+ assertThat(review.getTitle(), is("Review #3"));
+ }
+
+ @Test
+ public void shouldGetUserById() {
+ User user = manager.getUserById(1);
+ assertThat(user.getLogin(), is("simon"));
+ assertThat(user.getName(), is("Simon Brandhof"));
+ assertThat(user.getEmail(), is("simon.brandhof@sonarsource.com"));
+ }
+
+}
diff --git a/sonar-server/src/test/resources/org/sonar/server/notifications/reviews/fixture.xml b/sonar-server/src/test/resources/org/sonar/server/notifications/reviews/fixture.xml
new file mode 100644
index 00000000000..d9a64d90337
--- /dev/null
+++ b/sonar-server/src/test/resources/org/sonar/server/notifications/reviews/fixture.xml
@@ -0,0 +1,15 @@
+<dataset>
+
+ <users id="1" login="simon" name="Simon Brandhof" email="simon.brandhof@sonarsource.com" />
+ <users id="2" login="godin" name="Evgeny Mandrikov" email="evgeny.mandrikov@sonarsource.com" />
+
+ <!-- Review created by Simon and assigned to him -->
+ <reviews id="1" user_id="1" assignee_id="1" title="Review #1" />
+
+ <!-- Review created by Evgeny and not assigned -->
+ <reviews id="2" user_id="2" assignee_id="[null]" title="Review #2" />
+
+ <!-- Review created by Simon and assigned to Evgeny -->
+ <reviews id="3" user_id="1" assignee_id="2" title="Review #3" />
+
+</dataset> \ No newline at end of file