NotificationStore { public UserNotifications splitNotifications( final String username, @Nullable final SortedSet<Notification> notifications) throws NotificationStoreException { Objects.requireNonNull(username, "username == null"); Preconditions.checkArgument(!username.isEmpty(), "username cannot be empty"); if (notifications == null || notifications.isEmpty()) { return new UserNotifications(); } final String newestId = notifications.first().getId(""); LOGGER.debug("User ({}) newest notification ID: {}", username, newestId); final Map<String, Rule> rules = ruleStore.fetchCached(); LOGGER.debug("Fetched {} rules from cache", rules.size()); final Rollup unseenRollup = new Rollup(rules); final Optional<String> cursor = cursors.fetch(username, CURSOR_NAME); if (!cursor.isPresent()) { LOGGER.debug("User ({}) has no cursor, setting to {}", username, newestId); cursors.store(username, CURSOR_NAME, newestId); return new UserNotifications(unseenRollup.rollup(setUnseenState(notifications, true))); } final String lastSeenId = cursor.orElse(""); LOGGER.debug("User ({}) last seen notification ID: {}", username, lastSeenId); if (lastSeenId.compareTo(newestId) < 0) { LOGGER.debug("User ({}) updating cursor to {}", username, newestId); cursors.store(username, CURSOR_NAME, newestId); } final Optional<Notification> lastNotification = tryFind(notifications, lastSeenId); if (!lastNotification.isPresent()) { return new UserNotifications(unseenRollup.rollup(setUnseenState(notifications, true))); } final Stream<Notification> unseen = setUnseenState(notifications.headSet(lastNotification.get()), true); final Stream<Notification> seen = setUnseenState(notifications.tailSet(lastNotification.get()), false); final Rollup seenRollup = new Rollup(rules); return new UserNotifications(unseenRollup.rollup(unseen), seenRollup.rollup(seen)); } NotificationStore(
      final RiakClient client,
      final IdGenerator idGenerator,
      final CursorStore cursors,
      final RuleStore ruleStore,
      final Duration timeout,
      final Duration requestTimeout); void initialize(); Optional<UserNotifications> fetch(final String username); UserNotifications splitNotifications(
      final String username, @Nullable final SortedSet<Notification> notifications); Notification store(final String username, final Notification notification); void removeAll(final String username); void remove(final String username, final Collection<String> ids); static Stream<Notification> setUnseenState(
      final Iterable<Notification> notifications, final boolean unseen); static Optional<Notification> tryFind(
      final Iterable<Notification> notifications, final String id); static int indexOf(final Iterable<Notification> notifications, final String id); Iterable<Notification> skip(
      final Iterable<Notification> notifications,
      final String startId,
      final boolean inclusive,
      final int limitSize);  }
@Test public void testSplitNotifications() throws Exception { when(cursors.fetch(TEST_USER, CURSOR_NAME)).thenReturn(Optional.of("4")); final Notification n1 = Notification.create("1"); final Notification n2 = Notification.create("2"); final Notification n3 = Notification.create("3"); final Notification n4 = Notification.create("4"); final Notification n5 = Notification.create("5"); final Notification n6 = Notification.create("6"); final Notification n1Seen = Notification.builder().fromNotification(n1).withUnseen(false).build(); final Notification n2Seen = Notification.builder().fromNotification(n2).withUnseen(false).build(); final Notification n3Seen = Notification.builder().fromNotification(n3).withUnseen(false).build(); final Notification n4Seen = Notification.builder().fromNotification(n4).withUnseen(false).build(); final Notification n5Unseen = Notification.builder().fromNotification(n5).withUnseen(true).build(); final Notification n6Unseen = Notification.builder().fromNotification(n6).withUnseen(true).build(); final TreeSet<Notification> notifications = Sets.newTreeSet(Arrays.asList(n6, n5, n4, n3, n2, n1)); final List<Notification> expected = Arrays.asList(n6Unseen, n5Unseen, n4Seen, n3Seen, n2Seen, n1Seen); final UserNotifications actual = store.splitNotifications(TEST_USER, notifications); verify(cursors).fetch(TEST_USER, CURSOR_NAME); assertThat(actual.getNotifications()).containsExactlyElementsOf(expected); assertThat(actual.getUnseen()).containsExactly(n6Unseen, n5Unseen); assertThat(actual.getSeen()).containsExactly(n4Seen, n3Seen, n2Seen, n1Seen); }
@Test(expected = IllegalArgumentException.class) public void testSplitNotificationsUserEmpty() throws Exception { store.splitNotifications("", new TreeSet<Notification>()); }
@Test public void testSplitNotificationsNotificationsNull() throws Exception { final Set<Notification> expected = Collections.emptySortedSet(); final UserNotifications actual = store.splitNotifications(TEST_USER, null); assertThat(actual.getNotifications()).isEqualTo(expected); }
@Test public void testSplitNotificationsNotificationsEmpty() throws Exception { final Set<Notification> expected = Collections.emptySortedSet(); final UserNotifications actual = store.splitNotifications(TEST_USER, Sets.<Notification>newTreeSet()); assertThat(actual.getNotifications()).isEqualTo(expected); }