]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4524 Create notifications using batch insert
authorJulien HENRY <julien.henry@sonarsource.com>
Tue, 27 Aug 2013 08:30:55 +0000 (10:30 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Tue, 27 Aug 2013 08:30:55 +0000 (10:30 +0200)
to improve performances

12 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJob.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/notification/SendIssueNotificationsPostJobTest.java
sonar-core/src/main/java/org/sonar/core/issue/IssueNotifications.java
sonar-core/src/main/java/org/sonar/core/notification/DefaultNotificationManager.java
sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueDao.java
sonar-core/src/main/java/org/sonar/core/notification/db/NotificationQueueDto.java
sonar-core/src/main/java/org/sonar/core/persistence/BatchSession.java
sonar-core/src/main/resources/org/sonar/core/notification/db/NotificationQueueMapper.xml
sonar-core/src/test/java/org/sonar/core/issue/IssueNotificationsTest.java
sonar-core/src/test/java/org/sonar/core/notification/DefaultNotificationManagerTest.java
sonar-core/src/test/java/org/sonar/core/notification/db/NotificationQueueDaoTest.java
sonar-plugin-api/src/main/java/org/sonar/api/notifications/NotificationManager.java

index 0a3658a34a037a51f462c164b6d377dc7bac6346..0cbe7456c6ecb063070f365c8d53d05d39f5ceaa 100644 (file)
@@ -30,6 +30,9 @@ import org.sonar.batch.issue.IssueCache;
 import org.sonar.core.DryRunIncompatible;
 import org.sonar.core.issue.IssueNotifications;
 
+import java.util.LinkedHashMap;
+import java.util.Map;
+
 /**
  * @since 3.6
  */
@@ -54,6 +57,7 @@ public class SendIssueNotificationsPostJob implements PostJob {
   private void sendNotifications(Project project) {
     int newIssues = 0;
     IssueChangeContext context = IssueChangeContext.createScan(project.getAnalysisDate());
+    Map<DefaultIssue, Rule> shouldSentNotification = new LinkedHashMap<DefaultIssue, Rule>();
     for (DefaultIssue issue : issueCache.all()) {
       if (issue.isNew() && issue.resolution() == null) {
         newIssues++;
@@ -62,10 +66,13 @@ public class SendIssueNotificationsPostJob implements PostJob {
         Rule rule = ruleFinder.findByKey(issue.ruleKey());
         // TODO warning - rules with status REMOVED are currently ignored, but should not
         if (rule != null) {
-          notifications.sendChanges(issue, context, rule, project, null);
+          shouldSentNotification.put(issue, rule);
         }
       }
     }
+    if (!shouldSentNotification.isEmpty()) {
+      notifications.sendChanges(shouldSentNotification, context, project, null);
+    }
     if (newIssues > 0) {
       notifications.sendNewIssues(project, newIssues);
     }
index 736049d7ae8961c7300cda6c56b06bd13c0931a1..82a1fe70ff4ad7773f0fa778ea13bc37c42b9162 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.plugins.core.issue.notification;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.api.batch.SensorContext;
@@ -36,8 +37,17 @@ import org.sonar.batch.issue.IssueCache;
 import org.sonar.core.issue.IssueNotifications;
 
 import java.util.Arrays;
-
-import static org.mockito.Mockito.*;
+import java.util.Map;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class SendIssueNotificationsPostJobTest {
@@ -62,7 +72,7 @@ public class SendIssueNotificationsPostJobTest {
     when(issueCache.all()).thenReturn(Arrays.asList(
       new DefaultIssue().setNew(true),
       new DefaultIssue().setNew(false)
-    ));
+      ));
 
     SendIssueNotificationsPostJob job = new SendIssueNotificationsPostJob(issueCache, notifications, ruleFinder);
     job.executeOn(project, sensorContext);
@@ -75,7 +85,7 @@ public class SendIssueNotificationsPostJobTest {
     when(project.getAnalysisDate()).thenReturn(DateUtils.parseDate("2013-05-18"));
     when(issueCache.all()).thenReturn(Arrays.asList(
       new DefaultIssue().setNew(false)
-    ));
+      ));
 
     SendIssueNotificationsPostJob job = new SendIssueNotificationsPostJob(issueCache, notifications, ruleFinder);
     job.executeOn(project, sensorContext);
@@ -100,7 +110,7 @@ public class SendIssueNotificationsPostJobTest {
     SendIssueNotificationsPostJob job = new SendIssueNotificationsPostJob(issueCache, notifications, ruleFinder);
     job.executeOn(project, sensorContext);
 
-    verify(notifications).sendChanges(eq(issue), any(IssueChangeContext.class), eq(rule), any(Component.class), (Component)isNull());
+    verify(notifications).sendChanges(argThat(matchMapOf(issue, rule)), any(IssueChangeContext.class), any(Component.class), (Component) isNull());
   }
 
   @Test
@@ -119,7 +129,7 @@ public class SendIssueNotificationsPostJobTest {
     SendIssueNotificationsPostJob job = new SendIssueNotificationsPostJob(issueCache, notifications, ruleFinder);
     job.executeOn(project, sensorContext);
 
-    verify(notifications, never()).sendChanges(eq(issue), eq(changeContext), any(Rule.class), any(Component.class), any(Component.class));
+    verify(notifications, never()).sendChanges(argThat(matchMapOf(issue, null)), eq(changeContext), any(Component.class), any(Component.class));
   }
 
   @Test
@@ -139,6 +149,25 @@ public class SendIssueNotificationsPostJobTest {
     SendIssueNotificationsPostJob job = new SendIssueNotificationsPostJob(issueCache, notifications, ruleFinder);
     job.executeOn(project, sensorContext);
 
-    verify(notifications, never()).sendChanges(eq(issue), eq(changeContext), any(Rule.class), any(Component.class), any(Component.class));
+    verify(notifications, never()).sendChanges(argThat(matchMapOf(issue, null)), eq(changeContext), any(Component.class), any(Component.class));
+  }
+
+  private static IsMapOfIssueAndRule matchMapOf(DefaultIssue issue, Rule rule) {
+    return new IsMapOfIssueAndRule(issue, rule);
+  }
+
+  static class IsMapOfIssueAndRule extends ArgumentMatcher<Map<DefaultIssue, Rule>> {
+    private DefaultIssue issue;
+    private Rule rule;
+
+    public IsMapOfIssueAndRule(DefaultIssue issue, Rule rule) {
+      this.issue = issue;
+      this.rule = rule;
+    }
+
+    public boolean matches(Object arg) {
+      Map map = (Map) arg;
+      return map.size() == 1 && map.get(issue) != null && map.get(issue).equals(rule);
+    }
   }
 }
index 1a96d48446d0e55ff5e5d4f524086a8800eeb7a7..5619105e218e2bdd6e7ecc225f6c174ee3accf5a 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.core.issue;
 
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import org.sonar.api.BatchComponent;
 import org.sonar.api.ServerComponent;
 import org.sonar.api.component.Component;
@@ -35,8 +37,11 @@ import org.sonar.core.i18n.RuleI18nManager;
 
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Map.Entry;
 
 /**
  * Send notifications related to issues.
@@ -63,17 +68,23 @@ public class IssueNotifications implements BatchComponent, ServerComponent {
   }
 
   @CheckForNull
-  public Notification sendChanges(DefaultIssue issue, IssueChangeContext context, IssueQueryResult queryResult) {
-    return sendChanges(issue, context, queryResult.rule(issue), queryResult.project(issue), queryResult.component(issue));
+  public List<Notification> sendChanges(DefaultIssue issue, IssueChangeContext context, IssueQueryResult queryResult) {
+    Map<DefaultIssue, Rule> issues = Maps.newHashMap();
+    issues.put(issue, queryResult.rule(issue));
+    return sendChanges(issues, context, queryResult.project(issue), queryResult.component(issue));
   }
 
   @CheckForNull
-  public Notification sendChanges(DefaultIssue issue, IssueChangeContext context, Rule rule, Component project, @Nullable Component component) {
-    Notification notification = createChangeNotification(issue, context, rule, project, component, null);
-    if (notification != null) {
-      notificationsManager.scheduleForSending(notification);
+  public List<Notification> sendChanges(Map<DefaultIssue, Rule> issues, IssueChangeContext context, Component project, @Nullable Component component) {
+    List<Notification> notifications = Lists.newArrayList();
+    for (Entry<DefaultIssue, Rule> entry : issues.entrySet()) {
+      Notification notification = createChangeNotification(entry.getKey(), context, entry.getValue(), project, component, null);
+      if (notification != null) {
+        notifications.add(notification);
+      }
     }
-    return notification;
+    notificationsManager.scheduleForSending(notifications);
+    return notifications;
   }
 
   @CheckForNull
@@ -87,7 +98,7 @@ public class IssueNotifications implements BatchComponent, ServerComponent {
 
   @CheckForNull
   private Notification createChangeNotification(DefaultIssue issue, IssueChangeContext context, Rule rule, Component project,
-                                                @Nullable Component component, @Nullable String comment) {
+    @Nullable Component component, @Nullable String comment) {
     Notification notification = null;
     if (comment != null || issue.mustSendNotifications()) {
       FieldDiffs currentChange = issue.currentChange();
index 6d8b17921a2f5bee533012f470cdd1eebfbb9de6..e57c182f566d19e31040f21f7edb659ccfead5a0 100644 (file)
@@ -20,7 +20,9 @@
 package org.sonar.core.notification;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
 import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.SetMultimap;
 import org.sonar.api.notifications.Notification;
@@ -66,7 +68,16 @@ public class DefaultNotificationManager implements NotificationManager {
    */
   public void scheduleForSending(Notification notification) {
     NotificationQueueDto dto = NotificationQueueDto.toNotificationQueueDto(notification);
-    notificationQueueDao.insert(dto);
+    notificationQueueDao.insert(Arrays.asList(dto));
+  }
+
+  public void scheduleForSending(List<Notification> notification) {
+    notificationQueueDao.insert(Lists.transform(notification, new Function<Notification, NotificationQueueDto>() {
+      @Override
+      public NotificationQueueDto apply(Notification notification) {
+        return NotificationQueueDto.toNotificationQueueDto(notification);
+      }
+    }));
   }
 
   /**
index 8bb11e157d89543996867b46ee49574e1e3f476c..1152e48937b36af4627260136aba9b2810fe665c 100644 (file)
@@ -39,10 +39,12 @@ public class NotificationQueueDao implements BatchComponent, ServerComponent {
     this.mybatis = mybatis;
   }
 
-  public void insert(NotificationQueueDto dto) {
-    SqlSession session = mybatis.openSession();
+  public void insert(List<NotificationQueueDto> dtos) {
+    SqlSession session = mybatis.openBatchSession();
     try {
-      session.getMapper(NotificationQueueMapper.class).insert(dto);
+      for (NotificationQueueDto dto : dtos) {
+        session.getMapper(NotificationQueueMapper.class).insert(dto);
+      }
       session.commit();
     } finally {
       MyBatis.closeQuietly(session);
index 4e9668a507b6c460d4be2703318ad4f17101eafa..a42edc1a86e293a093dacd7b68c5e9d73629f14a 100644 (file)
@@ -31,7 +31,6 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
-import java.util.Date;
 
 /**
  * @since 4.0
@@ -39,7 +38,6 @@ import java.util.Date;
 public class NotificationQueueDto {
 
   private Long id;
-  private Date createdAt;
   private byte[] data;
 
   public Long getId() {
@@ -51,15 +49,6 @@ public class NotificationQueueDto {
     return this;
   }
 
-  public Date getCreatedAt() {
-    return createdAt;
-  }
-
-  public NotificationQueueDto setCreatedAt(Date createdAt) {
-    this.createdAt = createdAt;
-    return this;
-  }
-
   public byte[] getData() {
     return data;
   }
@@ -69,24 +58,6 @@ public class NotificationQueueDto {
     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);
@@ -101,7 +72,7 @@ public class NotificationQueueDto {
       return new NotificationQueueDto().setData(byteArrayOutputStream.toByteArray());
 
     } catch (IOException e) {
-      throw new SonarException(e);
+      throw new SonarException("Unable to write notification", e);
 
     } finally {
       IOUtils.closeQuietly(byteArrayOutputStream);
@@ -121,10 +92,10 @@ public class NotificationQueueDto {
       return (Notification) result;
 
     } catch (IOException e) {
-      throw new SonarException(e);
+      throw new SonarException("Unable to read notification", e);
 
     } catch (ClassNotFoundException e) {
-      throw new SonarException(e);
+      throw new SonarException("Unable to read notification", e);
 
     } finally {
       IOUtils.closeQuietly(byteArrayInputStream);
index 739fbfdc974e58073eb840e1b0ae2064f3975e52..d19383176c24f099912ca2dee050d7d95466cb05 100644 (file)
@@ -123,7 +123,7 @@ public final class BatchSession implements SqlSession {
       if (null != mappedStatement) {
         KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
         if (keyGenerator instanceof Jdbc3KeyGenerator) {
-          throw new IllegalStateException("Batch updates cannot use generated keys");
+          throw new IllegalStateException("Batch inserts cannot use generated keys");
         }
       }
     }
index c14850598b26bffd011ac4cc3b271872d4c6f82e..eb933957b73c7f3ebe29fceb2a9f6d33b962e227 100644 (file)
@@ -4,7 +4,7 @@
 
 <mapper namespace="org.sonar.core.notification.db.NotificationQueueMapper">
 
-  <insert id="insert" parameterType="NotificationQueue" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+  <insert id="insert" parameterType="NotificationQueue" useGeneratedKeys="false">
     INSERT INTO notifications (created_at, data)
     VALUES (current_timestamp, #{data})
   </insert>
@@ -14,7 +14,7 @@
   </delete>
 
   <select id="findOldest" parameterType="int" resultType="NotificationQueue">
-    select id, created_at, data
+    select id, data
     from notifications
     order by created_at asc
     limit #{count}
@@ -22,7 +22,7 @@
 
   <!-- SQL Server -->
   <select id="findOldest" parameterType="int" resultType="NotificationQueue" databaseId="mssql">
-    select top (#{count}) id, created_at, data
+    select top (#{count}) id, data
     from notifications
     order by created_at asc
   </select>
@@ -30,7 +30,7 @@
   <!-- Oracle -->
   <select id="findOldest" parameterType="int" resultType="NotificationQueue" databaseId="oracle">
     select * from (select
-      id, created_at, data
+      id, data
       from notifications
       order by created_at asc
     )
index 9528e691d289e189a880cad3ac7be84662ec3bac..806dcea781ad51230f288dc9a4984a993c13260a 100644 (file)
@@ -41,6 +41,7 @@ import java.util.Arrays;
 import java.util.Date;
 
 import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.eq;
 
 @RunWith(MockitoJUnitRunner.class)
 public class IssueNotificationsTest {
@@ -82,10 +83,10 @@ public class IssueNotificationsTest {
       .setSendNotifications(true)
       .setComponentKey("struts:Action")
       .setProjectKey("struts");
-    DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays.<Issue>asList(issue));
-    queryResult.addProjects(Arrays.<Component>asList(new Project("struts")));
+    DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays.<Issue> asList(issue));
+    queryResult.addProjects(Arrays.<Component> asList(new Project("struts")));
 
-    Notification notification = issueNotifications.sendChanges(issue, context, queryResult);
+    Notification notification = issueNotifications.sendChanges(issue, context, queryResult).get(0);
 
     assertThat(notification.getFieldValue("message")).isEqualTo("the message");
     assertThat(notification.getFieldValue("key")).isEqualTo("ABCDE");
@@ -97,7 +98,7 @@ public class IssueNotificationsTest {
     assertThat(notification.getFieldValue("new.status")).isEqualTo("RESOLVED");
     assertThat(notification.getFieldValue("old.assignee")).isEqualTo("simon");
     assertThat(notification.getFieldValue("new.assignee")).isNull();
-    Mockito.verify(manager).scheduleForSending(notification);
+    Mockito.verify(manager).scheduleForSending(eq(Arrays.asList(notification)));
   }
 
   @Test
@@ -109,8 +110,8 @@ public class IssueNotificationsTest {
       .setAssignee("freddy")
       .setComponentKey("struts:Action")
       .setProjectKey("struts");
-    DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays.<Issue>asList(issue));
-    queryResult.addProjects(Arrays.<Component>asList(new Project("struts")));
+    DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays.<Issue> asList(issue));
+    queryResult.addProjects(Arrays.<Component> asList(new Project("struts")));
 
     Notification notification = issueNotifications.sendChanges(issue, context, queryResult, "I don't know how to fix it?");
 
@@ -131,11 +132,11 @@ public class IssueNotificationsTest {
       .setSendNotifications(true)
       .setComponentKey("struts:Action")
       .setProjectKey("struts");
-    DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays.<Issue>asList(issue));
-    queryResult.addProjects(Arrays.<Component>asList(new Project("struts")));
-    queryResult.addComponents(Arrays.<Component>asList(new ResourceComponent(new File("struts:Action").setEffectiveKey("struts:Action"))));
+    DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays.<Issue> asList(issue));
+    queryResult.addProjects(Arrays.<Component> asList(new Project("struts")));
+    queryResult.addComponents(Arrays.<Component> asList(new ResourceComponent(new File("struts:Action").setEffectiveKey("struts:Action"))));
 
-    Notification notification = issueNotifications.sendChanges(issue, context, queryResult);
+    Notification notification = issueNotifications.sendChanges(issue, context, queryResult).get(0);
 
     assertThat(notification.getFieldValue("message")).isEqualTo("the message");
     assertThat(notification.getFieldValue("key")).isEqualTo("ABCDE");
@@ -143,7 +144,7 @@ public class IssueNotificationsTest {
     assertThat(notification.getFieldValue("componentName")).isEqualTo("struts:Action");
     assertThat(notification.getFieldValue("old.resolution")).isNull();
     assertThat(notification.getFieldValue("new.resolution")).isEqualTo("FIXED");
-    Mockito.verify(manager).scheduleForSending(notification);
+    Mockito.verify(manager).scheduleForSending(eq(Arrays.asList(notification)));
   }
 
   @Test
@@ -154,8 +155,8 @@ public class IssueNotificationsTest {
       .setKey("ABCDE")
       .setComponentKey("struts:Action")
       .setProjectKey("struts");
-    DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays.<Issue>asList(issue));
-    queryResult.addProjects(Arrays.<Component>asList(new Project("struts")));
+    DefaultIssueQueryResult queryResult = new DefaultIssueQueryResult(Arrays.<Issue> asList(issue));
+    queryResult.addProjects(Arrays.<Component> asList(new Project("struts")));
 
     Notification notification = issueNotifications.sendChanges(issue, context, queryResult, null);
 
index bdd0a264d4d2525ba4b62d254d823d6209e42979..118d2a356ba6b8b4e04b8f9fdd910fb5f9828f6b 100644 (file)
@@ -88,7 +88,7 @@ public class DefaultNotificationManagerTest extends AbstractDbUnitTestCase {
     Notification notification = new Notification("test");
     manager.scheduleForSending(notification);
 
-    verify(notificationQueueDao, only()).insert(any(NotificationQueueDto.class));
+    verify(notificationQueueDao, only()).insert(any(List.class));
   }
 
   @Test
index ec1f37e82aa3718cac5e46d0c870ac26e79e380b..6be6b8f913b92552ceaf45a68bf7636487efc877 100644 (file)
@@ -43,7 +43,7 @@ public class NotificationQueueDaoTest extends AbstractDaoTestCase {
   public void should_insert_new_notification_queue() {
     NotificationQueueDto notificationQueueDto = NotificationQueueDto.toNotificationQueueDto(new Notification("email"));
 
-    dao.insert(notificationQueueDto);
+    dao.insert(Arrays.asList(notificationQueueDto));
 
     checkTables("should_insert_new_notification_queue", new String[] {"id", "created_at"}, "notifications");
     assertThat(dao.findOldest(1).get(0).toNotification().getType()).isEqualTo("email");
index 1200efbaf8a3c9b71ecc420cba15ea36ddba0688..3f9e24a990684811b0ca840a4db901edfe23afb6 100644 (file)
@@ -25,35 +25,45 @@ import org.sonar.api.ServerComponent;
 
 import javax.annotation.Nullable;
 
+import java.util.List;
+
 /**
  * <p>
  * The notification manager receives notifications and is in charge of storing them so that they are processed by the notification service.
  * </p>
  * <p>
- * Pico provides an instance of this class, and plugins just need to create notifications and pass them to this manager with 
+ * Pico provides an instance of this class, and plugins just need to create notifications and pass them to this manager with
  * the {@link NotificationManager#scheduleForSending(Notification)} method.
  * </p>
- * 
+ *
  * @since 2.10
  */
 public interface NotificationManager extends ServerComponent, BatchComponent {
 
   /**
    * Receives a notification and stores it so that it is processed by the notification service.
-   * 
+   *
    * @param notification the notification.
    */
   void scheduleForSending(Notification notification);
 
+  /**
+   * Receives notifications and stores them so that they are processed by the notification service.
+   *
+   * @param notifications the notifications.
+   * @since 4.0
+   */
+  void scheduleForSending(List<Notification> notifications);
+
   /**
    * <p>
-   * Returns the list of users who subscribed to the given dispatcher, along with the notification channels (email, twitter, ...) that they choose 
+   * Returns the list of users who subscribed to the given dispatcher, along with the notification channels (email, twitter, ...) that they choose
    * for this dispatcher.
    * </p>
    * <p>
    * The resource ID can be null in case of notifications that have nothing to do with a specific project (like system notifications).
    * </p>
-   * 
+   *
    * @param dispatcher the dispatcher for which this list of users is requested
    * @param resourceId the optional resource which is concerned by this request
    * @return the list of user login along with the subscribed channels