<commit_before>/**
 * Copyright 2015 Smoke Turner, LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.smoketurner.notification.application.resources;

import io.dropwizard.jersey.caching.CacheControl;
import io.dropwizard.jersey.errors.ErrorMessage;

import java.util.Date;
import java.util.NoSuchElementException;

import javax.annotation.Nonnull;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriBuilder;

import org.glassfish.jersey.server.JSONP;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.codahale.metrics.annotation.Timed;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.smoketurner.notification.api.Notification;
import com.smoketurner.notification.application.core.LongSetParam;
import com.smoketurner.notification.application.core.RangeHeader;
import com.smoketurner.notification.application.core.UserNotifications;
import com.smoketurner.notification.application.exceptions.NotificationException;
import com.smoketurner.notification.application.exceptions.NotificationStoreException;
import com.smoketurner.notification.application.store.NotificationStore;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
import com.wordnik.swagger.annotations.ApiResponse;
import com.wordnik.swagger.annotations.ApiResponses;

@Path("/v1/notifications")
@Api(value = "notifications", description = "Operations about notifications")
public class NotificationResource {

  private static final Logger LOGGER = LoggerFactory.getLogger(NotificationResource.class);
  private static final String ACCEPT_RANGES_HEADER = "Accept-Ranges";
  private static final String CONTENT_RANGE_HEADER = "Content-Range";
  private static final String NEXT_RANGE_HEADER = "Next-Range";
  private static final int DEFAULT_LIMIT = 20;
  private static final int MAX_LIMIT = 1000;
  private static final String RANGE_NAME = "id";
  private final NotificationStore store;

  /**
   * Constructor
   *
   * @param store Notification data store
   */
  public NotificationResource(@Nonnull final NotificationStore store) {
    this.store = Preconditions.checkNotNull(store);
  }

  @GET
  @JSONP
  @Timed
  @Path("/{username}")
  @Produces({MediaType.APPLICATION_JSON, "application/javascript"})
  @CacheControl(mustRevalidate = true, noCache = true, noStore = true)
  @ApiOperation(value = "Fetch Notifications",
      notes = "Return notifications for the given username", responseContainer = "List",
      response = Notification.class)
  @ApiResponses(value = {
      @ApiResponse(code = 500, message = "Unable to fetch notifications",
          response = ErrorMessage.class),
      @ApiResponse(code = 404, message = "Notifications not found", response = ErrorMessage.class)})
  public Response fetch(
      @ApiParam(value = "range header", required = false) @HeaderParam("Range") final String rangeHeader,
      @ApiParam(value = "username", required = true) @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 there are no notifications, just return an empty list
    if (total < 1) {
      return Response.ok(notifications).header(ACCEPT_RANGES_HEADER, RANGE_NAME).build();
    }

    // The newest notification is always the first notification in the list and is used to set the
    // Last-Modified response header below.
    final Notification newest = notifications.first();
    final Notification oldest = notifications.last();

    Notification from = newest;
    boolean fromInclusive = true;
    Notification to = oldest;
    boolean toInclusive = true;

    int limit = DEFAULT_LIMIT;
    final ResponseBuilder builder;

    // If no Range header is present on the request, return a 200 response
    if (rangeHeader == null) {
      builder = Response.ok();
      try {
        to = Iterables.getLast(store.skip(notifications, from.getId(0L), true, limit));
      } catch (NoSuchElementException e) {
        LOGGER.debug("List of notifications is empty, using oldest");
        to = oldest;
      }
    } else {
      // If a Range header is present, return a 206 response
      builder = Response.status(Response.Status.PARTIAL_CONTENT);
      final RangeHeader range = RangeHeader.parse(rangeHeader);
      limit = range.getMax().or(DEFAULT_LIMIT);
      if (limit > MAX_LIMIT) {
        limit = MAX_LIMIT;
      }
      try {
        if (range.getFromId().isPresent()) {
          from =
              notifications.floor(Notification.builder().withId(range.getFromId().get()).build());
          if (from == null) {
            from = newest;
          }
          fromInclusive = range.getFromInclusive().or(true);

          to = Iterables.getLast(store.skip(notifications, from.getId(0L), fromInclusive, limit));
        } else {
          to = Iterables.getLast(store.skip(notifications, from.getId(0L), true, limit));
        }
      } catch (NoSuchElementException e) {
        LOGGER.debug("List of notifications is empty, using oldest");
        to = oldest;
      }
    }

    // Add the Accept-Ranges response header<commit_msg>Bind exception to log message<commit_after>/**
 * Copyright 2015 Smoke Turner, LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.smoketurner.notification.application.resources;

import io.dropwizard.jersey.caching.CacheControl;
import io.dropwizard.jersey.errors.ErrorMessage;

import java.util.Date;
import java.util.NoSuchElementException;

import javax.annotation.Nonnull;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriBuilder;

import org.glassfish.jersey.server.JSONP;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.codahale.metrics.annotation.Timed;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.smoketurner.notification.api.Notification;
import com.smoketurner.notification.application.core.LongSetParam;
import com.smoketurner.notification.application.core.RangeHeader;
import com.smoketurner.notification.application.core.UserNotifications;
import com.smoketurner.notification.application.exceptions.NotificationException;
import com.smoketurner.notification.application.exceptions.NotificationStoreException;
import com.smoketurner.notification.application.store.NotificationStore;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
import com.wordnik.swagger.annotations.ApiResponse;
import com.wordnik.swagger.annotations.ApiResponses;

@Path("/v1/notifications")
@Api(value = "notifications", description = "Operations about notifications")
public class NotificationResource {

  private static final Logger LOGGER = LoggerFactory.getLogger(NotificationResource.class);
  private static final String ACCEPT_RANGES_HEADER = "Accept-Ranges";
  private static final String CONTENT_RANGE_HEADER = "Content-Range";
  private static final String NEXT_RANGE_HEADER = "Next-Range";
  private static final int DEFAULT_LIMIT = 20;
  private static final int MAX_LIMIT = 1000;
  private static final String RANGE_NAME = "id";
  private final NotificationStore store;

  /**
   * Constructor
   *
   * @param store Notification data store
   */
  public NotificationResource(@Nonnull final NotificationStore store) {
    this.store = Preconditions.checkNotNull(store);
  }

  @GET
  @JSONP
  @Timed
  @Path("/{username}")
  @Produces({MediaType.APPLICATION_JSON, "application/javascript"})
  @CacheControl(mustRevalidate = true, noCache = true, noStore = true)
  @ApiOperation(value = "Fetch Notifications",
      notes = "Return notifications for the given username", responseContainer = "List",
      response = Notification.class)
  @ApiResponses(value = {
      @ApiResponse(code = 500, message = "Unable to fetch notifications",
          response = ErrorMessage.class),
      @ApiResponse(code = 404, message = "Notifications not found", response = ErrorMessage.class)})
  public Response fetch(
      @ApiParam(value = "range header", required = false) @HeaderParam("Range") final String rangeHeader,
      @ApiParam(value = "username", required = true) @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 there are no notifications, just return an empty list
    if (total < 1) {
      return Response.ok(notifications).header(ACCEPT_RANGES_HEADER, RANGE_NAME).build();
    }

    // The newest notification is always the first notification in the list and is used to set the
    // Last-Modified response header below.
    final Notification newest = notifications.first();
    final Notification oldest = notifications.last();

    Notification from = newest;
    boolean fromInclusive = true;
    Notification to = oldest;
    boolean toInclusive = true;

    int limit = DEFAULT_LIMIT;
    final ResponseBuilder builder;

    // If no Range header is present on the request, return a 200 response
    if (rangeHeader == null) {
      builder = Response.ok();
      try {
        to = Iterables.getLast(store.skip(notifications, from.getId(0L), true, limit));
      } catch (NoSuchElementException e) {
        LOGGER.debug("List of notifications is empty, using oldest", e);
        to = oldest;
      }
    } else {
      // If a Range header is present, return a 206 response
      builder = Response.status(Response.Status.PARTIAL_CONTENT);
      final RangeHeader range = RangeHeader.parse(rangeHeader);
      limit = range.getMax().or(DEFAULT_LIMIT);
      if (limit > MAX_LIMIT) {
        limit = MAX_LIMIT;
      }
      try {
        if (range.getFromId().isPresent()) {
          from =
              notifications.floor(Notification.builder().withId(range.getFromId().get()).build());
          if (from == null) {
            from = newest;
          }
          fromInclusive = range.getFromInclusive().or(true);

          to = Iterables.getLast(store.skip(notifications, from.getId(0L), fromInclusive, limit));
        } else {
          to = Iterables.getLast(store.skip(notifications, from.getId(0L), true, limit));
        }
      } catch (NoSuchElementException e) {
        LOGGER.debug("List of notifications is empty, using oldest", e);
        to = oldest;
      }
    }

    // Add the Accept-Ranges response header
{'fraction_non_alphanumeric': 0.10147545919903643, 'fraction_numerical': 0.004968383017163505, 'mean_word_length': 3.929499072356215, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3027132', 'n_tokens_mistral': 3757, 'n_tokens_neox': 3678, 'n_words': 991}