@Test public void testFetchJSONP() throws Exception { final ZonedDateTime now = ZonedDateTime.now(Clock.systemUTC()); final Notification notification = Notification.builder() .fromNotification(Notification.create("1")) .withCreatedAt(now) .build(); final ImmutableSortedSet<Notification> expected = ImmutableSortedSet.of(notification); final UserNotifications notifications = new UserNotifications(expected); when(store.fetch("test")).thenReturn(Optional.of(notifications)); when(store.skip(notifications.getNotifications(), "1", true, 20)).thenReturn(expected); final Response response = resources.client().target("/v1/notifications/test").request("application/javascript").get(); final String actual = response.readEntity(String.class); verify(store).fetch("test"); verify(store).skip(notifications.getNotifications(), "1", true, 20); assertThat(response.getStatus()).isEqualTo(200); assertThat(response.getHeaderString(HttpHeaders.CONTENT_TYPE)) .isEqualTo("application/javascript;charset=UTF-8"); assertThat(response.getHeaderString("Accept-Ranges")).isEqualTo("id"); assertThat(response.getHeaderString("Content-Range")).isEqualTo("id 1..1"); assertThat(response.getHeaderString("Next-Range")).isNull(); assertThat(actual).isEqualTo("callback([" + MAPPER.writeValueAsString(notification) + "])"); }
@GET @JSONP @Timed @Path("/{username}") @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) @CacheControl(mustRevalidate = true, noCache = true, noStore = true) public Response fetch( @HeaderParam("Range") final String rangeHeader, @PathParam("username") final String username) { final Optional<UserNotifications> list; try { list = store.fetch(username); } catch (NotificationStoreException e) { throw new NotificationException( Response.Status.INTERNAL_SERVER_ERROR, "Unable to fetch notifications", e); } if (!list.isPresent()) { throw new NotificationException(Response.Status.NOT_FOUND, "Notifications not found"); } final ImmutableSortedSet<Notification> notifications = list.get().getNotifications(); final int total = notifications.size(); if (total < 1) { return Response.ok(notifications).header(ACCEPT_RANGES_HEADER, RANGE_NAME).build(); } final Notification newest = notifications.first(); final Notification oldest = notifications.last(); Notification from = newest; boolean fromInclusive = true; Notification to; boolean toInclusive = true; int limit = DEFAULT_LIMIT; final ResponseBuilder builder; if (rangeHeader == null) { builder = Response.ok(); try { to = Iterables.getLast(store.skip(notifications, from.getId(""), true, limit)); } catch (NoSuchElementException e) { LOGGER.debug("List of notifications is empty, using oldest", e); to = oldest; } } else { builder = Response.status(Response.Status.PARTIAL_CONTENT); final RangeHeader range = RangeHeader.parse(rangeHeader); limit = range.getMax().orElse(DEFAULT_LIMIT); if (limit > MAX_LIMIT) { limit = MAX_LIMIT; } try { if (range.getFromId().isPresent()) { from = notifications.floor(Notification.create(range.getFromId().get())); if (from == null) { from = newest; } fromInclusive = range.getFromInclusive().orElse(true); to = Iterables.getLast(store.skip(notifications, from.getId(""), fromInclusive, limit)); } else { to = Iterables.getLast(store.skip(notifications, from.getId(""), true, limit)); } } catch (NoSuchElementException e) { LOGGER.debug("List of notifications is empty, using oldest", e); to = oldest; } } builder.header(ACCEPT_RANGES_HEADER, RANGE_NAME); builder.lastModified(Date.from(newest.getCreatedAt().toInstant())); final ImmutableSortedSet<Notification> subSet = notifications.subSet(from, fromInclusive, to, toInclusive); if (!subSet.isEmpty()) { final String firstId = subSet.first().getId(""); final String lastId = subSet.last().getId(""); builder.header(CONTENT_RANGE_HEADER, String.format("%s %s..%s", RANGE_NAME, firstId, lastId)); if (subSet.last().compareTo(oldest) < 0) { builder.header( NEXT_RANGE_HEADER, String.format("%s ]%s..; max=%d", RANGE_NAME, lastId, limit)); } } return builder.entity(subSet).build(); }
NotificationResource { @GET @JSONP @Timed @Path("/{username}") @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) @CacheControl(mustRevalidate = true, noCache = true, noStore = true) public Response fetch( @HeaderParam("Range") final String rangeHeader, @PathParam("username") final String username) { final Optional<UserNotifications> list; try { list = store.fetch(username); } catch (NotificationStoreException e) { throw new NotificationException( Response.Status.INTERNAL_SERVER_ERROR, "Unable to fetch notifications", e); } if (!list.isPresent()) { throw new NotificationException(Response.Status.NOT_FOUND, "Notifications not found"); } final ImmutableSortedSet<Notification> notifications = list.get().getNotifications(); final int total = notifications.size(); if (total < 1) { return Response.ok(notifications).header(ACCEPT_RANGES_HEADER, RANGE_NAME).build(); } final Notification newest = notifications.first(); final Notification oldest = notifications.last(); Notification from = newest; boolean fromInclusive = true; Notification to; boolean toInclusive = true; int limit = DEFAULT_LIMIT; final ResponseBuilder builder; if (rangeHeader == null) { builder = Response.ok(); try { to = Iterables.getLast(store.skip(notifications, from.getId(""), true, limit)); } catch (NoSuchElementException e) { LOGGER.debug("List of notifications is empty, using oldest", e); to = oldest; } } else { builder = Response.status(Response.Status.PARTIAL_CONTENT); final RangeHeader range = RangeHeader.parse(rangeHeader); limit = range.getMax().orElse(DEFAULT_LIMIT); if (limit > MAX_LIMIT) { limit = MAX_LIMIT; } try { if (range.getFromId().isPresent()) { from = notifications.floor(Notification.create(range.getFromId().get())); if (from == null) { from = newest; } fromInclusive = range.getFromInclusive().orElse(true); to = Iterables.getLast(store.skip(notifications, from.getId(""), fromInclusive, limit)); } else { to = Iterables.getLast(store.skip(notifications, from.getId(""), true, limit)); } } catch (NoSuchElementException e) { LOGGER.debug("List of notifications is empty, using oldest", e); to = oldest; } } builder.header(ACCEPT_RANGES_HEADER, RANGE_NAME); builder.lastModified(Date.from(newest.getCreatedAt().toInstant())); final ImmutableSortedSet<Notification> subSet = notifications.subSet(from, fromInclusive, to, toInclusive); if (!subSet.isEmpty()) { final String firstId = subSet.first().getId(""); final String lastId = subSet.last().getId(""); builder.header(CONTENT_RANGE_HEADER, String.format("%s %s..%s", RANGE_NAME, firstId, lastId)); if (subSet.last().compareTo(oldest) < 0) { builder.header( NEXT_RANGE_HEADER, String.format("%s ]%s..; max=%d", RANGE_NAME, lastId, limit)); } } return builder.entity(subSet).build(); } }
NotificationResource { @GET @JSONP @Timed @Path("/{username}") @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) @CacheControl(mustRevalidate = true, noCache = true, noStore = true) public Response fetch( @HeaderParam("Range") final String rangeHeader, @PathParam("username") final String username) { final Optional<UserNotifications> list; try { list = store.fetch(username); } catch (NotificationStoreException e) { throw new NotificationException( Response.Status.INTERNAL_SERVER_ERROR, "Unable to fetch notifications", e); } if (!list.isPresent()) { throw new NotificationException(Response.Status.NOT_FOUND, "Notifications not found"); } final ImmutableSortedSet<Notification> notifications = list.get().getNotifications(); final int total = notifications.size(); if (total < 1) { return Response.ok(notifications).header(ACCEPT_RANGES_HEADER, RANGE_NAME).build(); } final Notification newest = notifications.first(); final Notification oldest = notifications.last(); Notification from = newest; boolean fromInclusive = true; Notification to; boolean toInclusive = true; int limit = DEFAULT_LIMIT; final ResponseBuilder builder; if (rangeHeader == null) { builder = Response.ok(); try { to = Iterables.getLast(store.skip(notifications, from.getId(""), true, limit)); } catch (NoSuchElementException e) { LOGGER.debug("List of notifications is empty, using oldest", e); to = oldest; } } else { builder = Response.status(Response.Status.PARTIAL_CONTENT); final RangeHeader range = RangeHeader.parse(rangeHeader); limit = range.getMax().orElse(DEFAULT_LIMIT); if (limit > MAX_LIMIT) { limit = MAX_LIMIT; } try { if (range.getFromId().isPresent()) { from = notifications.floor(Notification.create(range.getFromId().get())); if (from == null) { from = newest; } fromInclusive = range.getFromInclusive().orElse(true); to = Iterables.getLast(store.skip(notifications, from.getId(""), fromInclusive, limit)); } else { to = Iterables.getLast(store.skip(notifications, from.getId(""), true, limit)); } } catch (NoSuchElementException e) { LOGGER.debug("List of notifications is empty, using oldest", e); to = oldest; } } builder.header(ACCEPT_RANGES_HEADER, RANGE_NAME); builder.lastModified(Date.from(newest.getCreatedAt().toInstant())); final ImmutableSortedSet<Notification> subSet = notifications.subSet(from, fromInclusive, to, toInclusive); if (!subSet.isEmpty()) { final String firstId = subSet.first().getId(""); final String lastId = subSet.last().getId(""); builder.header(CONTENT_RANGE_HEADER, String.format("%s %s..%s", RANGE_NAME, firstId, lastId)); if (subSet.last().compareTo(oldest) < 0) { builder.header( NEXT_RANGE_HEADER, String.format("%s ]%s..; max=%d", RANGE_NAME, lastId, limit)); } } return builder.entity(subSet).build(); } NotificationResource(final NotificationStore store); }
NotificationResource { @GET @JSONP @Timed @Path("/{username}") @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) @CacheControl(mustRevalidate = true, noCache = true, noStore = true) public Response fetch( @HeaderParam("Range") final String rangeHeader, @PathParam("username") final String username) { final Optional<UserNotifications> list; try { list = store.fetch(username); } catch (NotificationStoreException e) { throw new NotificationException( Response.Status.INTERNAL_SERVER_ERROR, "Unable to fetch notifications", e); } if (!list.isPresent()) { throw new NotificationException(Response.Status.NOT_FOUND, "Notifications not found"); } final ImmutableSortedSet<Notification> notifications = list.get().getNotifications(); final int total = notifications.size(); if (total < 1) { return Response.ok(notifications).header(ACCEPT_RANGES_HEADER, RANGE_NAME).build(); } final Notification newest = notifications.first(); final Notification oldest = notifications.last(); Notification from = newest; boolean fromInclusive = true; Notification to; boolean toInclusive = true; int limit = DEFAULT_LIMIT; final ResponseBuilder builder; if (rangeHeader == null) { builder = Response.ok(); try { to = Iterables.getLast(store.skip(notifications, from.getId(""), true, limit)); } catch (NoSuchElementException e) { LOGGER.debug("List of notifications is empty, using oldest", e); to = oldest; } } else { builder = Response.status(Response.Status.PARTIAL_CONTENT); final RangeHeader range = RangeHeader.parse(rangeHeader); limit = range.getMax().orElse(DEFAULT_LIMIT); if (limit > MAX_LIMIT) { limit = MAX_LIMIT; } try { if (range.getFromId().isPresent()) { from = notifications.floor(Notification.create(range.getFromId().get())); if (from == null) { from = newest; } fromInclusive = range.getFromInclusive().orElse(true); to = Iterables.getLast(store.skip(notifications, from.getId(""), fromInclusive, limit)); } else { to = Iterables.getLast(store.skip(notifications, from.getId(""), true, limit)); } } catch (NoSuchElementException e) { LOGGER.debug("List of notifications is empty, using oldest", e); to = oldest; } } builder.header(ACCEPT_RANGES_HEADER, RANGE_NAME); builder.lastModified(Date.from(newest.getCreatedAt().toInstant())); final ImmutableSortedSet<Notification> subSet = notifications.subSet(from, fromInclusive, to, toInclusive); if (!subSet.isEmpty()) { final String firstId = subSet.first().getId(""); final String lastId = subSet.last().getId(""); builder.header(CONTENT_RANGE_HEADER, String.format("%s %s..%s", RANGE_NAME, firstId, lastId)); if (subSet.last().compareTo(oldest) < 0) { builder.header( NEXT_RANGE_HEADER, String.format("%s ]%s..; max=%d", RANGE_NAME, lastId, limit)); } } return builder.entity(subSet).build(); } NotificationResource(final NotificationStore store); @GET @JSONP @Timed @Path("/{username}") @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) @CacheControl(mustRevalidate = true, noCache = true, noStore = true) Response fetch(
      @HeaderParam("Range") final String rangeHeader,
      @PathParam("username") final String username); @POST @Timed @Path("/{username}") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) Response add(
      @PathParam("username") final String username,
      @NotNull @Valid final Notification notification); @DELETE @Timed @Path("/{username}") Response delete(
      @PathParam("username") final String username,
      @QueryParam("ids") final StringSetParam idsParam); }
NotificationResource { @GET @JSONP @Timed @Path("/{username}") @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) @CacheControl(mustRevalidate = true, noCache = true, noStore = true) public Response fetch( @HeaderParam("Range") final String rangeHeader, @PathParam("username") final String username) { final Optional<UserNotifications> list; try { list = store.fetch(username); } catch (NotificationStoreException e) { throw new NotificationException( Response.Status.INTERNAL_SERVER_ERROR, "Unable to fetch notifications", e); } if (!list.isPresent()) { throw new NotificationException(Response.Status.NOT_FOUND, "Notifications not found"); } final ImmutableSortedSet<Notification> notifications = list.get().getNotifications(); final int total = notifications.size(); if (total < 1) { return Response.ok(notifications).header(ACCEPT_RANGES_HEADER, RANGE_NAME).build(); } final Notification newest = notifications.first(); final Notification oldest = notifications.last(); Notification from = newest; boolean fromInclusive = true; Notification to; boolean toInclusive = true; int limit = DEFAULT_LIMIT; final ResponseBuilder builder; if (rangeHeader == null) { builder = Response.ok(); try { to = Iterables.getLast(store.skip(notifications, from.getId(""), true, limit)); } catch (NoSuchElementException e) { LOGGER.debug("List of notifications is empty, using oldest", e); to = oldest; } } else { builder = Response.status(Response.Status.PARTIAL_CONTENT); final RangeHeader range = RangeHeader.parse(rangeHeader); limit = range.getMax().orElse(DEFAULT_LIMIT); if (limit > MAX_LIMIT) { limit = MAX_LIMIT; } try { if (range.getFromId().isPresent()) { from = notifications.floor(Notification.create(range.getFromId().get())); if (from == null) { from = newest; } fromInclusive = range.getFromInclusive().orElse(true); to = Iterables.getLast(store.skip(notifications, from.getId(""), fromInclusive, limit)); } else { to = Iterables.getLast(store.skip(notifications, from.getId(""), true, limit)); } } catch (NoSuchElementException e) { LOGGER.debug("List of notifications is empty, using oldest", e); to = oldest; } } builder.header(ACCEPT_RANGES_HEADER, RANGE_NAME); builder.lastModified(Date.from(newest.getCreatedAt().toInstant())); final ImmutableSortedSet<Notification> subSet = notifications.subSet(from, fromInclusive, to, toInclusive); if (!subSet.isEmpty()) { final String firstId = subSet.first().getId(""); final String lastId = subSet.last().getId(""); builder.header(CONTENT_RANGE_HEADER, String.format("%s %s..%s", RANGE_NAME, firstId, lastId)); if (subSet.last().compareTo(oldest) < 0) { builder.header( NEXT_RANGE_HEADER, String.format("%s ]%s..; max=%d", RANGE_NAME, lastId, limit)); } } return builder.entity(subSet).build(); } NotificationResource(final NotificationStore store); @GET @JSONP @Timed @Path("/{username}") @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) @CacheControl(mustRevalidate = true, noCache = true, noStore = true) Response fetch(
      @HeaderParam("Range") final String rangeHeader,
      @PathParam("username") final String username); @POST @Timed @Path("/{username}") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) Response add(
      @PathParam("username") final String username,
      @NotNull @Valid final Notification notification); @DELETE @Timed @Path("/{username}") Response delete(
      @PathParam("username") final String username,
      @QueryParam("ids") final StringSetParam idsParam);  }