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;
/**
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();
}
+++ /dev/null
-/*
- * 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();
- }
-}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+
+}
--- /dev/null
+/*
+ * 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
*/
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;
IssueFilterFavouriteDao.class,
LoadedTemplateDao.class,
MeasureFilterDao.class,
+ NotificationQueueDao.class,
PermissionDao.class,
PropertiesDao.class,
PurgeDao.class,
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;
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;
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;
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;
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;
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);
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);
<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"/>
<property name="hibernate.cache.use_query_cache" value="false"/>
</properties>
</persistence-unit>
-</persistence>
\ No newline at end of file
+</persistence>
--- /dev/null
+<?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 <= #{count}
+ </select>
+
+</mapper>
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 {
@Mock
private NotificationChannel twitterChannel;
+ @Mock
+ private NotificationQueueDao notificationQueueDao;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
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);
}
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
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();
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<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>
--- /dev/null
+<dataset>
+
+ <notifications id="1" created_at="[null]" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC
+AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7
+eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv
+bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAVlbWFpbA==" />
+
+</dataset>
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;
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();
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;
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);
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});
}
/**
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);
@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));