]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4524 Migrated Notifications to MyBatis
authorJulien HENRY <julien.henry@sonarsource.com>
Mon, 26 Aug 2013 15:25:48 +0000 (17:25 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Mon, 26 Aug 2013 15:25:48 +0000 (17:25 +0200)
18 files changed:
sonar-core/src/main/java/org/sonar/core/notification/DefaultNotificationManager.java
sonar-core/src/main/java/org/sonar/core/notification/NotificationQueueElement.java [deleted file]
sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueDao.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueDto.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueMapper.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/notification/db/package-info.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java
sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java
sonar-core/src/main/resources/META-INF/persistence.xml
sonar-core/src/main/resources/org/sonar/core/notification/db/NotificationQueueMapper.xml [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/notification/DefaultNotificationManagerTest.java
sonar-core/src/test/java/org/sonar/core/notification/db/NotificationQueueDaoTest.java [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_delete_notification-result.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_delete_notification.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_findOldest.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_insert_new_notification_queue-result.xml [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/notifications/NotificationService.java
sonar-server/src/test/java/org/sonar/server/notifications/NotificationServiceTest.java

index 0a344a83a07cfc95ace6f68679869da776013246..6d8b17921a2f5bee533012f470cdd1eebfbb9de6 100644 (file)
@@ -23,17 +23,17 @@ import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.SetMultimap;
-import org.sonar.api.database.DatabaseSession;
 import org.sonar.api.notifications.Notification;
 import org.sonar.api.notifications.NotificationChannel;
 import org.sonar.api.notifications.NotificationDispatcher;
 import org.sonar.api.notifications.NotificationManager;
+import org.sonar.core.notification.db.NotificationQueueDao;
+import org.sonar.core.notification.db.NotificationQueueDto;
 import org.sonar.core.properties.PropertiesDao;
-import org.sonar.jpa.session.DatabaseSessionFactory;
 
 import javax.annotation.Nullable;
+
 import java.util.Arrays;
-import java.util.Date;
 import java.util.List;
 
 /**
@@ -42,56 +42,46 @@ import java.util.List;
 public class DefaultNotificationManager implements NotificationManager {
 
   private NotificationChannel[] notificationChannels;
-  private DatabaseSessionFactory sessionFactory;
+  private NotificationQueueDao notificationQueueDao;
   private PropertiesDao propertiesDao;
 
   /**
    * Default constructor used by Pico
    */
-  public DefaultNotificationManager(NotificationChannel[] channels, DatabaseSessionFactory sessionFactory, PropertiesDao propertiesDao) {
+  public DefaultNotificationManager(NotificationChannel[] channels, NotificationQueueDao notificationQueueDao, PropertiesDao propertiesDao) {
     this.notificationChannels = channels;
-    this.sessionFactory = sessionFactory;
+    this.notificationQueueDao = notificationQueueDao;
     this.propertiesDao = propertiesDao;
   }
 
   /**
    * Constructor if no notification channel
    */
-  public DefaultNotificationManager(DatabaseSessionFactory sessionFactory, PropertiesDao propertiesDao) {
-    this(new NotificationChannel[0], sessionFactory, propertiesDao);
+  public DefaultNotificationManager(NotificationQueueDao notificationQueueDao, PropertiesDao propertiesDao) {
+    this(new NotificationChannel[0], notificationQueueDao, propertiesDao);
   }
 
   /**
    * {@inheritDoc}
    */
   public void scheduleForSending(Notification notification) {
-    NotificationQueueElement notificationQueueElement = new NotificationQueueElement();
-    notificationQueueElement.setCreatedAt(new Date());
-    notificationQueueElement.setNotification(notification);
-    DatabaseSession session = sessionFactory.getSession();
-    session.save(notificationQueueElement);
-    session.commit();
+    NotificationQueueDto dto = NotificationQueueDto.toNotificationQueueDto(notification);
+    notificationQueueDao.insert(dto);
   }
 
   /**
    * Give the notification queue so that it can be processed
    */
-  public NotificationQueueElement getFromQueue() {
-    DatabaseSession session = sessionFactory.getSession();
-    String hql = "FROM " + NotificationQueueElement.class.getSimpleName() + " ORDER BY createdAt ASC";
-    List<NotificationQueueElement> notifications = session.createQuery(hql).setMaxResults(1).getResultList();
+  public Notification getFromQueue() {
+    int batchSize = 1;
+    List<NotificationQueueDto> notifications = notificationQueueDao.findOldest(batchSize);
     if (notifications.isEmpty()) {
-      // UGLY - waiting for a clean way to manage JDBC connections without Hibernate - myBatis is coming soon
-      // This code is highly coupled to org.sonar.server.notifications.NotificationService, which periodically executes
-      // several times the methods getFromQueue() and isEnabled(). The session is closed only at the end of the task -
-      // when there are no more notifications to process - to ensure "better" performances.
-      sessionFactory.clear();
       return null;
     }
-    NotificationQueueElement notification = notifications.get(0);
-    session.removeWithoutFlush(notification);
-    session.commit();
-    return notification;
+    notificationQueueDao.delete(notifications);
+
+    // If batchSize is increased then we should return a list instead of a single element
+    return notifications.get(0).toNotification();
 
   }
 
diff --git a/sonar-core/src/main/java/org/sonar/core/notification/NotificationQueueElement.java b/sonar-core/src/main/java/org/sonar/core/notification/NotificationQueueElement.java
deleted file mode 100644 (file)
index fcc90f8..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.io.IOUtils;
-import org.apache.commons.lang.builder.ReflectionToStringBuilder;
-import org.apache.commons.lang.builder.ToStringStyle;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.utils.SonarException;
-
-import javax.persistence.*;
-import java.io.*;
-import java.util.Date;
-
-@Entity
-@Table(name = "notifications")
-public class NotificationQueueElement {
-
-  @Id
-  @Column(name = "id")
-  @GeneratedValue
-  private Integer id;
-
-  @Column(name = "created_at")
-  private Date createdAt;
-
-  @Column(name = "data", updatable = true, nullable = true, length = 167772150)
-  private byte[] data;
-
-  public Integer getId() {
-    return id;
-  }
-
-  public void setId(Integer id) {
-    this.id = id;
-  }
-
-  public Date getCreatedAt() {
-    return createdAt;
-  }
-
-  public void setCreatedAt(Date createdAt) {
-    this.createdAt = createdAt;
-  }
-
-  public void setNotification(Notification notification) {
-    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-    try {
-      ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
-      objectOutputStream.writeObject(notification);
-      objectOutputStream.close();
-      this.data = byteArrayOutputStream.toByteArray();
-
-    } catch (IOException e) {
-      throw new SonarException(e);
-
-    } finally {
-      IOUtils.closeQuietly(byteArrayOutputStream);
-    }
-  }
-
-  public Notification getNotification() {
-    if (this.data == null) {
-      return null;
-    }
-    ByteArrayInputStream byteArrayInputStream = null;
-    try {
-      byteArrayInputStream = new ByteArrayInputStream(this.data);
-      ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
-      Object result = objectInputStream.readObject();
-      objectInputStream.close();
-      return (Notification) result;
-
-    } catch (IOException e) {
-      throw new SonarException(e);
-
-    } catch (ClassNotFoundException e) {
-      throw new SonarException(e);
-      
-    } finally {
-      IOUtils.closeQuietly(byteArrayInputStream);
-    }
-  }
-
-  @Override
-  public String toString() {
-    return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueDao.java b/sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueDao.java
new file mode 100644 (file)
index 0000000..8bb11e1
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.db;
+
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.ServerComponent;
+import org.sonar.core.persistence.MyBatis;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @since 4.0
+ */
+public class NotificationQueueDao implements BatchComponent, ServerComponent {
+
+  private final MyBatis mybatis;
+
+  public NotificationQueueDao(MyBatis mybatis) {
+    this.mybatis = mybatis;
+  }
+
+  public void insert(NotificationQueueDto dto) {
+    SqlSession session = mybatis.openSession();
+    try {
+      session.getMapper(NotificationQueueMapper.class).insert(dto);
+      session.commit();
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  public void delete(List<NotificationQueueDto> dtos) {
+    SqlSession session = mybatis.openBatchSession();
+    try {
+      for (NotificationQueueDto dto : dtos) {
+        session.getMapper(NotificationQueueMapper.class).delete(dto.getId());
+      }
+      session.commit();
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  public List<NotificationQueueDto> findOldest(int count) {
+    if (count < 1) {
+      return Collections.emptyList();
+    }
+    SqlSession session = mybatis.openSession();
+    try {
+      return session.getMapper(NotificationQueueMapper.class).findOldest(count);
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueDto.java b/sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueDto.java
new file mode 100644 (file)
index 0000000..4e9668a
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.db;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.utils.SonarException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Date;
+
+/**
+ * @since 4.0
+ */
+public class NotificationQueueDto {
+
+  private Long id;
+  private Date createdAt;
+  private byte[] data;
+
+  public Long getId() {
+    return id;
+  }
+
+  public NotificationQueueDto setId(Long id) {
+    this.id = id;
+    return this;
+  }
+
+  public Date getCreatedAt() {
+    return createdAt;
+  }
+
+  public NotificationQueueDto setCreatedAt(Date createdAt) {
+    this.createdAt = createdAt;
+    return this;
+  }
+
+  public byte[] getData() {
+    return data;
+  }
+
+  public NotificationQueueDto setData(byte[] data) {
+    this.data = data;
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    NotificationQueueDto actionPlanDto = (NotificationQueueDto) o;
+    return !(id != null ? !id.equals(actionPlanDto.id) : actionPlanDto.id != null);
+  }
+
+  @Override
+  public int hashCode() {
+    return id != null ? id.hashCode() : 0;
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+  }
+
+  public static NotificationQueueDto toNotificationQueueDto(Notification notification) {
+    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+    try {
+      ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
+      objectOutputStream.writeObject(notification);
+      objectOutputStream.close();
+      return new NotificationQueueDto().setData(byteArrayOutputStream.toByteArray());
+
+    } catch (IOException e) {
+      throw new SonarException(e);
+
+    } finally {
+      IOUtils.closeQuietly(byteArrayOutputStream);
+    }
+  }
+
+  public Notification toNotification() {
+    if (this.data == null) {
+      return null;
+    }
+    ByteArrayInputStream byteArrayInputStream = null;
+    try {
+      byteArrayInputStream = new ByteArrayInputStream(this.data);
+      ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
+      Object result = objectInputStream.readObject();
+      objectInputStream.close();
+      return (Notification) result;
+
+    } catch (IOException e) {
+      throw new SonarException(e);
+
+    } catch (ClassNotFoundException e) {
+      throw new SonarException(e);
+
+    } finally {
+      IOUtils.closeQuietly(byteArrayInputStream);
+    }
+  }
+
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueMapper.java b/sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueMapper.java
new file mode 100644 (file)
index 0000000..120637c
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.db;
+
+import java.util.List;
+
+/**
+ * @since 4.0
+ */
+public interface NotificationQueueMapper {
+
+  void insert(NotificationQueueDto actionPlanDto);
+
+  void delete(Long id);
+
+  List<NotificationQueueDto> findOldest(int count);
+
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/notification/db/package-info.java b/sonar-core/src/main/java/org/sonar/core/notification/db/package-info.java
new file mode 100644 (file)
index 0000000..f33c44e
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.core.notification.db;
+
+import javax.annotation.ParametersAreNonnullByDefault;
\ No newline at end of file
index 3027aada8fa9a3c8862e435c1c3eacd367667de6..9512388b61a46dbe442cff541c0c35cc481b5fd5 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.core.persistence;
 
+import org.sonar.core.notification.db.NotificationQueueDao;
+
 import com.google.common.collect.ImmutableList;
 import org.sonar.core.dashboard.ActiveDashboardDao;
 import org.sonar.core.dashboard.DashboardDao;
@@ -65,6 +67,7 @@ public final class DaoUtils {
       IssueFilterFavouriteDao.class,
       LoadedTemplateDao.class,
       MeasureFilterDao.class,
+      NotificationQueueDao.class,
       PermissionDao.class,
       PropertiesDao.class,
       PurgeDao.class,
index 19a9ecbbf57d317e04210fcc3526e262b429e381..ced5b2e5eeff39b707dd572346e3abfa0d73057c 100644 (file)
@@ -24,7 +24,11 @@ import com.google.common.io.Closeables;
 import org.apache.ibatis.builder.xml.XMLMapperBuilder;
 import org.apache.ibatis.logging.LogFactory;
 import org.apache.ibatis.mapping.Environment;
-import org.apache.ibatis.session.*;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.ExecutorType;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.session.SqlSessionFactoryBuilder;
 import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
 import org.apache.ibatis.type.JdbcType;
 import org.slf4j.LoggerFactory;
@@ -35,7 +39,14 @@ import org.sonar.api.database.model.MeasureData;
 import org.sonar.api.database.model.MeasureMapper;
 import org.sonar.api.database.model.MeasureModel;
 import org.sonar.core.config.Logback;
-import org.sonar.core.dashboard.*;
+import org.sonar.core.dashboard.ActiveDashboardDto;
+import org.sonar.core.dashboard.ActiveDashboardMapper;
+import org.sonar.core.dashboard.DashboardDto;
+import org.sonar.core.dashboard.DashboardMapper;
+import org.sonar.core.dashboard.WidgetDto;
+import org.sonar.core.dashboard.WidgetMapper;
+import org.sonar.core.dashboard.WidgetPropertyDto;
+import org.sonar.core.dashboard.WidgetPropertyMapper;
 import org.sonar.core.dependency.DependencyDto;
 import org.sonar.core.dependency.DependencyMapper;
 import org.sonar.core.dependency.ResourceSnapshotDto;
@@ -44,9 +55,23 @@ import org.sonar.core.duplication.DuplicationMapper;
 import org.sonar.core.duplication.DuplicationUnitDto;
 import org.sonar.core.graph.jdbc.GraphDto;
 import org.sonar.core.graph.jdbc.GraphDtoMapper;
-import org.sonar.core.issue.db.*;
+import org.sonar.core.issue.db.ActionPlanDto;
+import org.sonar.core.issue.db.ActionPlanMapper;
+import org.sonar.core.issue.db.ActionPlanStatsDto;
+import org.sonar.core.issue.db.ActionPlanStatsMapper;
+import org.sonar.core.issue.db.IssueChangeDto;
+import org.sonar.core.issue.db.IssueChangeMapper;
+import org.sonar.core.issue.db.IssueDto;
+import org.sonar.core.issue.db.IssueFilterDto;
+import org.sonar.core.issue.db.IssueFilterFavouriteDto;
+import org.sonar.core.issue.db.IssueFilterFavouriteMapper;
+import org.sonar.core.issue.db.IssueFilterMapper;
+import org.sonar.core.issue.db.IssueMapper;
+import org.sonar.core.issue.db.IssueStatsMapper;
 import org.sonar.core.measure.MeasureFilterDto;
 import org.sonar.core.measure.MeasureFilterMapper;
+import org.sonar.core.notification.db.NotificationQueueDto;
+import org.sonar.core.notification.db.NotificationQueueMapper;
 import org.sonar.core.permission.PermissionTemplateDto;
 import org.sonar.core.permission.PermissionTemplateGroupDto;
 import org.sonar.core.permission.PermissionTemplateMapper;
@@ -55,7 +80,12 @@ import org.sonar.core.properties.PropertiesMapper;
 import org.sonar.core.properties.PropertyDto;
 import org.sonar.core.purge.PurgeMapper;
 import org.sonar.core.purge.PurgeableSnapshotDto;
-import org.sonar.core.resource.*;
+import org.sonar.core.resource.ResourceDto;
+import org.sonar.core.resource.ResourceIndexDto;
+import org.sonar.core.resource.ResourceIndexerMapper;
+import org.sonar.core.resource.ResourceKeyUpdaterMapper;
+import org.sonar.core.resource.ResourceMapper;
+import org.sonar.core.resource.SnapshotDto;
 import org.sonar.core.rule.RuleDto;
 import org.sonar.core.rule.RuleMapper;
 import org.sonar.core.source.jdbc.SnapshotDataDto;
@@ -63,7 +93,14 @@ import org.sonar.core.source.jdbc.SnapshotDataMapper;
 import org.sonar.core.source.jdbc.SnapshotSourceMapper;
 import org.sonar.core.template.LoadedTemplateDto;
 import org.sonar.core.template.LoadedTemplateMapper;
-import org.sonar.core.user.*;
+import org.sonar.core.user.AuthorDto;
+import org.sonar.core.user.AuthorMapper;
+import org.sonar.core.user.GroupDto;
+import org.sonar.core.user.GroupRoleDto;
+import org.sonar.core.user.RoleMapper;
+import org.sonar.core.user.UserDto;
+import org.sonar.core.user.UserMapper;
+import org.sonar.core.user.UserRoleDto;
 
 import java.io.InputStream;
 
@@ -102,6 +139,7 @@ public class MyBatis implements BatchComponent, ServerComponent {
     loadAlias(conf, "GroupRole", GroupRoleDto.class);
     loadAlias(conf, "LoadedTemplate", LoadedTemplateDto.class);
     loadAlias(conf, "MeasureFilter", MeasureFilterDto.class);
+    loadAlias(conf, "NotificationQueue", NotificationQueueDto.class);
     loadAlias(conf, "Property", PropertyDto.class);
     loadAlias(conf, "PurgeableSnapshot", PurgeableSnapshotDto.class);
     loadAlias(conf, "Resource", ResourceDto.class);
@@ -139,7 +177,8 @@ public class MyBatis implements BatchComponent, ServerComponent {
       LoadedTemplateMapper.class, MeasureFilterMapper.class, PermissionTemplateMapper.class, PropertiesMapper.class, PurgeMapper.class,
       ResourceKeyUpdaterMapper.class, ResourceIndexerMapper.class, ResourceSnapshotMapper.class, RoleMapper.class, RuleMapper.class,
       SchemaMigrationMapper.class, SemaphoreMapper.class, UserMapper.class, WidgetMapper.class, WidgetPropertyMapper.class,
-      MeasureMapper.class, SnapshotDataMapper.class, SnapshotSourceMapper.class, ActionPlanMapper.class, ActionPlanStatsMapper.class
+      MeasureMapper.class, SnapshotDataMapper.class, SnapshotSourceMapper.class, ActionPlanMapper.class, ActionPlanStatsMapper.class,
+      NotificationQueueMapper.class
     };
     loadMappers(conf, mappers);
     configureLogback(mappers);
index ffe8c102dd71523e3448cc0c928b8361560e1acb..0cf0c2f31c868b9ae8b8386b9c5876057ca4d0c9 100644 (file)
@@ -30,7 +30,6 @@
     <class>org.sonar.api.profiles.Alert</class>
     <class>org.sonar.api.rules.ActiveRuleChange</class>
     <class>org.sonar.api.rules.ActiveRuleParamChange</class>
-    <class>org.sonar.core.notification.NotificationQueueElement</class>
 
     <properties>
       <property name="hibernate.current_session_context_class" value="thread"/>
@@ -46,4 +45,4 @@
       <property name="hibernate.cache.use_query_cache" value="false"/>
     </properties>
   </persistence-unit>
-</persistence>
\ No newline at end of file
+</persistence>
diff --git a/sonar-core/src/main/resources/org/sonar/core/notification/db/NotificationQueueMapper.xml b/sonar-core/src/main/resources/org/sonar/core/notification/db/NotificationQueueMapper.xml
new file mode 100644 (file)
index 0000000..c148505
--- /dev/null
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mappei.dtd">
+
+<mapper namespace="org.sonar.core.notification.db.NotificationQueueMapper">
+
+  <insert id="insert" parameterType="NotificationQueue" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+    INSERT INTO notifications (created_at, data)
+    VALUES (current_timestamp, #{data})
+  </insert>
+
+  <delete id="delete" parameterType="long">
+    delete from notifications where id=#{id}
+  </delete>
+
+  <select id="findOldest" parameterType="int" resultType="NotificationQueue">
+    select id, created_at, data
+    from notifications
+    order by created_at asc
+    limit #{count}
+  </select>
+
+  <!-- SQL Server -->
+  <select id="findOldest" parameterType="int" resultType="NotificationQueue" databaseId="mssql">
+    select top (#{count}) id, created_at, data
+    from notifications
+    order by created_at asc
+  </select>
+
+  <!-- Oracle -->
+  <select id="findOldest" parameterType="int" resultType="NotificationQueue" databaseId="oracle">
+    select * from (select
+      id, created_at, data
+      from notifications
+      order by created_at asc
+    )
+    where rownum &lt;= #{count}
+  </select>
+
+</mapper>
index 7953acd55aa2a85ea4967e7e5cc8ea9296a4fabb..bdd0a264d4d2525ba4b62d254d823d6209e42979 100644 (file)
@@ -23,21 +23,27 @@ 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.api.notifications.NotificationDispatcher;
+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.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 
 import static org.fest.assertions.Assertions.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.nullValue;
-import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 public class DefaultNotificationManagerTest extends AbstractDbUnitTestCase {
@@ -56,6 +62,9 @@ public class DefaultNotificationManagerTest extends AbstractDbUnitTestCase {
   @Mock
   private NotificationChannel twitterChannel;
 
+  @Mock
+  private NotificationQueueDao notificationQueueDao;
+
   @Before
   public void setUp() {
     MockitoAnnotations.initMocks(this);
@@ -63,14 +72,14 @@ public class DefaultNotificationManagerTest extends AbstractDbUnitTestCase {
     when(emailChannel.getKey()).thenReturn("Email");
     when(twitterChannel.getKey()).thenReturn("Twitter");
 
-    manager = new DefaultNotificationManager(new NotificationChannel[] {emailChannel, twitterChannel}, getSessionFactory(), propertiesDao);
+    manager = new DefaultNotificationManager(new NotificationChannel[] {emailChannel, twitterChannel}, notificationQueueDao, propertiesDao);
   }
 
   @Test
   public void shouldProvideChannelList() {
     assertThat(manager.getChannels()).containsOnly(emailChannel, twitterChannel);
 
-    manager = new DefaultNotificationManager(getSessionFactory(), propertiesDao);
+    manager = new DefaultNotificationManager(notificationQueueDao, propertiesDao);
     assertThat(manager.getChannels()).hasSize(0);
   }
 
@@ -79,10 +88,21 @@ public class DefaultNotificationManagerTest extends AbstractDbUnitTestCase {
     Notification notification = new Notification("test");
     manager.scheduleForSending(notification);
 
-    NotificationQueueElement queueElement = manager.getFromQueue();
-    assertThat(queueElement.getNotification(), is(notification));
+    verify(notificationQueueDao, only()).insert(any(NotificationQueueDto.class));
+  }
+
+  @Test
+  public void shouldGetFromQueueAndDelete() throws Exception {
+    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();
 
-    assertThat(manager.getFromQueue(), nullValue());
+    InOrder inOrder = inOrder(notificationQueueDao);
+    inOrder.verify(notificationQueueDao).findOldest(1);
+    inOrder.verify(notificationQueueDao).delete(dtos);
   }
 
   @Test
@@ -116,7 +136,7 @@ public class DefaultNotificationManagerTest extends AbstractDbUnitTestCase {
     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);
+    Multimap<String, NotificationChannel> multiMap = manager.findSubscribedRecipientsForDispatcher(dispatcher, (Integer) null);
     assertThat(multiMap.entries()).hasSize(3);
 
     Map<String, Collection<NotificationChannel>> map = multiMap.asMap();
diff --git a/sonar-core/src/test/java/org/sonar/core/notification/db/NotificationQueueDaoTest.java b/sonar-core/src/test/java/org/sonar/core/notification/db/NotificationQueueDaoTest.java
new file mode 100644 (file)
index 0000000..ec1f37e
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.db;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.notifications.Notification;
+import org.sonar.core.persistence.AbstractDaoTestCase;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class NotificationQueueDaoTest extends AbstractDaoTestCase {
+
+  NotificationQueueDao dao;
+
+  @Before
+  public void createDao() {
+    dao = new NotificationQueueDao(getMyBatis());
+  }
+
+  @Test
+  public void should_insert_new_notification_queue() {
+    NotificationQueueDto notificationQueueDto = NotificationQueueDto.toNotificationQueueDto(new Notification("email"));
+
+    dao.insert(notificationQueueDto);
+
+    checkTables("should_insert_new_notification_queue", new String[] {"id", "created_at"}, "notifications");
+    assertThat(dao.findOldest(1).get(0).toNotification().getType()).isEqualTo("email");
+  }
+
+  @Test
+  public void should_delete_notification() {
+    setupData("should_delete_notification");
+
+    NotificationQueueDto dto1 = new NotificationQueueDto().setId(1L);
+    NotificationQueueDto dto3 = new NotificationQueueDto().setId(3L);
+
+    dao.delete(Arrays.asList(dto1, dto3));
+
+    checkTables("should_delete_notification", "notifications");
+  }
+
+  @Test
+  public void should_findOldest() {
+    setupData("should_findOldest");
+
+    Collection<NotificationQueueDto> result = dao.findOldest(3);
+    assertThat(result).hasSize(3);
+    assertThat(result).onProperty("id").containsOnly(1L, 2L, 4L);
+
+    result = dao.findOldest(6);
+    assertThat(result).hasSize(4);
+  }
+}
diff --git a/sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_delete_notification-result.xml b/sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_delete_notification-result.xml
new file mode 100644 (file)
index 0000000..f289e5d
--- /dev/null
@@ -0,0 +1,13 @@
+<dataset>
+
+  <notifications id="2" created_at="2013-08-27" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDI=" />
+
+  <notifications id="4" created_at="2013-08-28" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDQ=" />
+
+</dataset>
diff --git a/sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_delete_notification.xml b/sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_delete_notification.xml
new file mode 100644 (file)
index 0000000..feeca98
--- /dev/null
@@ -0,0 +1,23 @@
+<dataset>
+
+  <notifications id="1" created_at="2013-08-26" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDE=" />
+
+  <notifications id="2" created_at="2013-08-27" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDI=" />
+
+  <notifications id="3" created_at="2013-08-29" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDM=" />
+
+  <notifications id="4" created_at="2013-08-28" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDQ=" />
+
+</dataset>
diff --git a/sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_findOldest.xml b/sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_findOldest.xml
new file mode 100644 (file)
index 0000000..feeca98
--- /dev/null
@@ -0,0 +1,23 @@
+<dataset>
+
+  <notifications id="1" created_at="2013-08-26" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDE=" />
+
+  <notifications id="2" created_at="2013-08-27" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDI=" />
+
+  <notifications id="3" created_at="2013-08-29" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDM=" />
+
+  <notifications id="4" created_at="2013-08-28" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAZlbWFpbDQ=" />
+
+</dataset>
diff --git a/sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_insert_new_notification_queue-result.xml b/sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_insert_new_notification_queue-result.xml
new file mode 100644 (file)
index 0000000..f6c81b1
--- /dev/null
@@ -0,0 +1,8 @@
+<dataset>
+
+  <notifications id="1" created_at="[null]" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAVlbWFpbA==" />
+
+</dataset>
index 29d1690e1e7e488558f0c24aa81a33f127d71c26..c2b6dbf4ab86e3a0707ecd04ce20219999e0c9f8 100644 (file)
@@ -33,7 +33,6 @@ import org.sonar.api.notifications.NotificationChannel;
 import org.sonar.api.notifications.NotificationDispatcher;
 import org.sonar.api.utils.TimeProfiler;
 import org.sonar.core.notification.DefaultNotificationManager;
-import org.sonar.core.notification.NotificationQueueElement;
 
 import java.util.Arrays;
 import java.util.Collection;
@@ -115,13 +114,13 @@ public class NotificationService implements ServerComponent {
   synchronized void processQueue() {
     TIME_PROFILER.start("Processing notifications queue");
 
-    NotificationQueueElement queueElement = manager.getFromQueue();
-    while (queueElement != null) {
-      deliver(queueElement.getNotification());
+    Notification notifToSend = manager.getFromQueue();
+    while (notifToSend != null) {
+      deliver(notifToSend);
       if (stopping) {
         break;
       }
-      queueElement = manager.getFromQueue();
+      notifToSend = manager.getFromQueue();
     }
 
     TIME_PROFILER.stop();
index 1e3c109a206b8ee7ea96896f014bacc04948e5c9..7c14b46a8f50dcd8f6c1c9dad68c1eaef5d9079e 100644 (file)
@@ -27,7 +27,6 @@ import org.sonar.api.notifications.Notification;
 import org.sonar.api.notifications.NotificationChannel;
 import org.sonar.api.notifications.NotificationDispatcher;
 import org.sonar.core.notification.DefaultNotificationManager;
-import org.sonar.core.notification.NotificationQueueElement;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Matchers.any;
@@ -46,7 +45,6 @@ public class NotificationServiceTest {
   private static String ASSIGNEE_SIMON = "simon";
 
   private final DefaultNotificationManager manager = mock(DefaultNotificationManager.class);
-  private final NotificationQueueElement queueElement = mock(NotificationQueueElement.class);
   private final Notification notification = mock(Notification.class);
   private final NotificationChannel emailChannel = mock(NotificationChannel.class);
   private final NotificationChannel gtalkChannel = mock(NotificationChannel.class);
@@ -60,13 +58,12 @@ public class NotificationServiceTest {
     when(gtalkChannel.getKey()).thenReturn("gtalk");
     when(commentOnReviewAssignedToMe.getKey()).thenReturn("comment on review assigned to me");
     when(commentOnReviewCreatedByMe.getKey()).thenReturn("comment on review created by me");
-    when(queueElement.getNotification()).thenReturn(notification);
-    when(manager.getFromQueue()).thenReturn(queueElement).thenReturn(null);
+    when(manager.getFromQueue()).thenReturn(notification).thenReturn(null);
 
     Settings settings = new Settings().setProperty("sonar.notifications.delay", 1L);
 
     service = new NotificationService(settings, manager,
-        new NotificationDispatcher[] {commentOnReviewAssignedToMe, commentOnReviewCreatedByMe});
+      new NotificationDispatcher[] {commentOnReviewAssignedToMe, commentOnReviewCreatedByMe});
   }
 
   /**
@@ -132,7 +129,7 @@ public class NotificationServiceTest {
   public void scenario3() {
     setUpMocks(CREATOR_EVGENY, ASSIGNEE_SIMON);
     doAnswer(addUser(ASSIGNEE_SIMON, new NotificationChannel[] {emailChannel, gtalkChannel}))
-        .when(commentOnReviewAssignedToMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
+      .when(commentOnReviewAssignedToMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
 
     service.start();
     verify(emailChannel, timeout(2000)).deliver(notification, ASSIGNEE_SIMON);
@@ -168,8 +165,7 @@ public class NotificationServiceTest {
   @Test
   public void shouldNotStopWhenException() {
     setUpMocks(CREATOR_SIMON, ASSIGNEE_SIMON);
-    when(queueElement.getNotification()).thenThrow(new RuntimeException("Unexpected exception")).thenReturn(notification);
-    when(manager.getFromQueue()).thenReturn(queueElement).thenReturn(queueElement).thenReturn(null);
+    when(manager.getFromQueue()).thenThrow(new RuntimeException("Unexpected exception")).thenReturn(notification).thenReturn(null);
     doAnswer(addUser(ASSIGNEE_SIMON, emailChannel)).when(commentOnReviewAssignedToMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));
     doAnswer(addUser(CREATOR_SIMON, emailChannel)).when(commentOnReviewCreatedByMe).dispatch(same(notification), any(NotificationDispatcher.Context.class));