diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2013-08-26 17:25:48 +0200 |
---|---|---|
committer | Julien HENRY <julien.henry@sonarsource.com> | 2013-08-26 17:25:48 +0200 |
commit | 8d4325511265cae0947a763cf559b2514052e4b7 (patch) | |
tree | 6a1d51fd446de81470dafc5a2b78530cdbd1bdd9 | |
parent | 7d8aa2afd1c693fa4cdf94c1208ec130e8aa4b94 (diff) | |
download | sonarqube-8d4325511265cae0947a763cf559b2514052e4b7.tar.gz sonarqube-8d4325511265cae0947a763cf559b2514052e4b7.zip |
SONAR-4524 Migrated Notifications to MyBatis
17 files changed, 473 insertions, 83 deletions
diff --git a/sonar-core/src/main/java/org/sonar/core/notification/DefaultNotificationManager.java b/sonar-core/src/main/java/org/sonar/core/notification/DefaultNotificationManager.java index 0a344a83a07..6d8b17921a2 100644 --- a/sonar-core/src/main/java/org/sonar/core/notification/DefaultNotificationManager.java +++ b/sonar-core/src/main/java/org/sonar/core/notification/DefaultNotificationManager.java @@ -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/db/NotificationQueueDao.java b/sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueDao.java new file mode 100644 index 00000000000..8bb11e157d8 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueDao.java @@ -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/NotificationQueueElement.java b/sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueDto.java index fcc90f8ca79..4e9668a507b 100644 --- a/sonar-core/src/main/java/org/sonar/core/notification/NotificationQueueElement.java +++ b/sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueDto.java @@ -17,56 +17,88 @@ * 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; + +package org.sonar.core.notification.db; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.builder.ReflectionToStringBuilder; +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 javax.persistence.*; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.Date; -@Entity -@Table(name = "notifications") -public class NotificationQueueElement { - - @Id - @Column(name = "id") - @GeneratedValue - private Integer id; +/** + * @since 4.0 + */ +public class NotificationQueueDto { - @Column(name = "created_at") + private Long id; private Date createdAt; - - @Column(name = "data", updatable = true, nullable = true, length = 167772150) private byte[] data; - public Integer getId() { + public Long getId() { return id; } - public void setId(Integer id) { + public NotificationQueueDto setId(Long id) { this.id = id; + return this; } public Date getCreatedAt() { return createdAt; } - public void setCreatedAt(Date createdAt) { + public NotificationQueueDto setCreatedAt(Date createdAt) { this.createdAt = createdAt; + return this; } - public void setNotification(Notification notification) { + 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(); - this.data = byteArrayOutputStream.toByteArray(); + return new NotificationQueueDto().setData(byteArrayOutputStream.toByteArray()); } catch (IOException e) { throw new SonarException(e); @@ -76,7 +108,7 @@ public class NotificationQueueElement { } } - public Notification getNotification() { + public Notification toNotification() { if (this.data == null) { return null; } @@ -93,14 +125,10 @@ public class NotificationQueueElement { } 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/NotificationQueueMapper.java b/sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueMapper.java new file mode 100644 index 00000000000..120637c6e98 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueMapper.java @@ -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 index 00000000000..f33c44e5652 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/notification/db/package-info.java @@ -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 diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java b/sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java index 3027aada8fa..9512388b61a 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java @@ -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, diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java b/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java index 19a9ecbbf57..ced5b2e5eef 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java @@ -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); diff --git a/sonar-core/src/main/resources/META-INF/persistence.xml b/sonar-core/src/main/resources/META-INF/persistence.xml index ffe8c102dd7..0cf0c2f31c8 100644 --- a/sonar-core/src/main/resources/META-INF/persistence.xml +++ b/sonar-core/src/main/resources/META-INF/persistence.xml @@ -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 index 00000000000..c14850598b2 --- /dev/null +++ b/sonar-core/src/main/resources/org/sonar/core/notification/db/NotificationQueueMapper.xml @@ -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 <= #{count} + </select> + +</mapper> diff --git a/sonar-core/src/test/java/org/sonar/core/notification/DefaultNotificationManagerTest.java b/sonar-core/src/test/java/org/sonar/core/notification/DefaultNotificationManagerTest.java index 7953acd55aa..bdd0a264d4d 100644 --- a/sonar-core/src/test/java/org/sonar/core/notification/DefaultNotificationManagerTest.java +++ b/sonar-core/src/test/java/org/sonar/core/notification/DefaultNotificationManagerTest.java @@ -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 index 00000000000..ec1f37e82aa --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/notification/db/NotificationQueueDaoTest.java @@ -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 index 00000000000..f289e5d1fb2 --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_delete_notification-result.xml @@ -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 index 00000000000..feeca98cc74 --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_delete_notification.xml @@ -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 index 00000000000..feeca98cc74 --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_findOldest.xml @@ -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 index 00000000000..f6c81b15f64 --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/notification/db/NotificationQueueDaoTest/should_insert_new_notification_queue-result.xml @@ -0,0 +1,8 @@ +<dataset> + + <notifications id="1" created_at="[null]" data="rO0ABXNyAChvcmcuc29uYXIuYXBpLm5vdGlmaWNhdGlvbnMuTm90aWZpY2F0aW9uTppHnJFK4aAC +AAJMAAZmaWVsZHN0AA9MamF2YS91dGlsL01hcDtMAAR0eXBldAASTGphdmEvbGFuZy9TdHJpbmc7 +eHBzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hv +bGR4cD9AAAAAAAAMdwgAAAAQAAAAAHh0AAVlbWFpbA==" /> + +</dataset> 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 index 29d1690e1e7..c2b6dbf4ab8 100644 --- a/sonar-server/src/main/java/org/sonar/server/notifications/NotificationService.java +++ b/sonar-server/src/main/java/org/sonar/server/notifications/NotificationService.java @@ -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(); 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 index 1e3c109a206..7c14b46a8f5 100644 --- a/sonar-server/src/test/java/org/sonar/server/notifications/NotificationServiceTest.java +++ b/sonar-server/src/test/java/org/sonar/server/notifications/NotificationServiceTest.java @@ -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)); |