Package com.commafeed.frontend.resource

Source Code of com.commafeed.frontend.resource.CategoryREST

package com.commafeed.frontend.resource;

import io.dropwizard.hibernate.UnitOfWork;

import java.io.StringWriter;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
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.Status;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

import com.commafeed.CommaFeedConfiguration;
import com.commafeed.backend.cache.CacheService;
import com.commafeed.backend.dao.FeedCategoryDAO;
import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.feed.FeedEntryKeyword;
import com.commafeed.backend.feed.FeedUtils;
import com.commafeed.backend.model.FeedCategory;
import com.commafeed.backend.model.FeedEntryStatus;
import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserSettings.ReadingMode;
import com.commafeed.backend.model.UserSettings.ReadingOrder;
import com.commafeed.backend.service.FeedEntryService;
import com.commafeed.backend.service.FeedSubscriptionService;
import com.commafeed.frontend.auth.SecurityCheck;
import com.commafeed.frontend.model.Category;
import com.commafeed.frontend.model.Entries;
import com.commafeed.frontend.model.Entry;
import com.commafeed.frontend.model.Subscription;
import com.commafeed.frontend.model.UnreadCount;
import com.commafeed.frontend.model.request.AddCategoryRequest;
import com.commafeed.frontend.model.request.CategoryModificationRequest;
import com.commafeed.frontend.model.request.CollapseRequest;
import com.commafeed.frontend.model.request.IDRequest;
import com.commafeed.frontend.model.request.MarkRequest;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.feed.synd.SyndFeedImpl;
import com.rometools.rome.io.SyndFeedOutput;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;

@Path("/category")
@Api(value = "/category", description = "Operations about user categories")
@Slf4j
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RequiredArgsConstructor(onConstructor = @__({ @Inject }))
@Singleton
public class CategoryREST {

  public static final String ALL = "all";
  public static final String STARRED = "starred";

  private final FeedCategoryDAO feedCategoryDAO;
  private final FeedEntryStatusDAO feedEntryStatusDAO;
  private final FeedSubscriptionDAO feedSubscriptionDAO;
  private final FeedEntryService feedEntryService;
  private final FeedSubscriptionService feedSubscriptionService;
  private final CacheService cache;
  private final CommaFeedConfiguration config;

  @Path("/entries")
  @GET
  @UnitOfWork
  @ApiOperation(value = "Get category entries", notes = "Get a list of category entries", response = Entries.class)
  public Response getCategoryEntries(
      @SecurityCheck User user,
      @ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id,
      @ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("unread") @QueryParam("readType") ReadingMode readType,
      @ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
      @ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
      @ApiParam(value = "limit for paging, default 20, maximum 1000") @DefaultValue("20") @QueryParam("limit") int limit,
      @ApiParam(value = "date ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order,
      @ApiParam(
          value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
      @ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds,
      @ApiParam(value = "comma-separated list of excluded subscription ids") @QueryParam("excludedSubscriptionIds") String excludedSubscriptionIds,
      @ApiParam(value = "keep only entries tagged with this tag") @QueryParam("tag") String tag) {

    Preconditions.checkNotNull(readType);

    keywords = StringUtils.trimToNull(keywords);
    Preconditions.checkArgument(keywords == null || StringUtils.length(keywords) >= 3);
    List<FeedEntryKeyword> entryKeywords = FeedEntryKeyword.fromQueryString(keywords);

    limit = Math.min(limit, 1000);
    limit = Math.max(0, limit);

    Entries entries = new Entries();
    entries.setOffset(offset);
    entries.setLimit(limit);
    boolean unreadOnly = readType == ReadingMode.unread;
    if (StringUtils.isBlank(id)) {
      id = ALL;
    }

    Date newerThanDate = newerThan == null ? null : new Date(newerThan);

    List<Long> excludedIds = null;
    if (StringUtils.isNotEmpty(excludedSubscriptionIds)) {
      excludedIds = Lists.newArrayList();
      for (String excludedId : excludedSubscriptionIds.split(",")) {
        excludedIds.add(Long.valueOf(excludedId));
      }
    }

    if (ALL.equals(id)) {
      entries.setName(Optional.fromNullable(tag).or("All"));
      List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
      removeExcludedSubscriptions(subs, excludedIds);
      List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, subs, unreadOnly, entryKeywords, newerThanDate,
          offset, limit + 1, order, true, onlyIds, tag);

      for (FeedEntryStatus status : list) {
        entries.getEntries().add(
            Entry.build(status, config.getApplicationSettings().getPublicUrl(), config.getApplicationSettings()
                .isImageProxyEnabled()));
      }

    } else if (STARRED.equals(id)) {
      entries.setName("Starred");
      List<FeedEntryStatus> starred = feedEntryStatusDAO.findStarred(user, newerThanDate, offset, limit + 1, order, !onlyIds);
      for (FeedEntryStatus status : starred) {
        entries.getEntries().add(
            Entry.build(status, config.getApplicationSettings().getPublicUrl(), config.getApplicationSettings()
                .isImageProxyEnabled()));
      }
    } else {
      FeedCategory parent = feedCategoryDAO.findById(user, Long.valueOf(id));
      if (parent != null) {
        List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(user, parent);
        List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(user, categories);
        removeExcludedSubscriptions(subs, excludedIds);
        List<FeedEntryStatus> list = feedEntryStatusDAO.findBySubscriptions(user, subs, unreadOnly, entryKeywords, newerThanDate,
            offset, limit + 1, order, true, onlyIds, tag);

        for (FeedEntryStatus status : list) {
          entries.getEntries().add(
              Entry.build(status, config.getApplicationSettings().getPublicUrl(), config.getApplicationSettings()
                  .isImageProxyEnabled()));
        }
        entries.setName(parent.getName());
      } else {
        return Response.status(Status.NOT_FOUND).entity("<message>category not found</message>").build();
      }
    }

    boolean hasMore = entries.getEntries().size() > limit;
    if (hasMore) {
      entries.setHasMore(true);
      entries.getEntries().remove(entries.getEntries().size() - 1);
    }

    entries.setTimestamp(System.currentTimeMillis());
    entries.setIgnoredReadStatus(STARRED.equals(id) || keywords != null || tag != null);
    FeedUtils.removeUnwantedFromSearch(entries.getEntries(), entryKeywords);
    return Response.ok(entries).build();
  }

  @Path("/entriesAsFeed")
  @GET
  @UnitOfWork
  @ApiOperation(value = "Get category entries as feed", notes = "Get a feed of category entries")
  @Produces(MediaType.APPLICATION_XML)
  public Response getCategoryEntriesAsFeed(
      @SecurityCheck(apiKeyAllowed = true) User user,
      @ApiParam(value = "id of the category, 'all' or 'starred'", required = true) @QueryParam("id") String id,
      @ApiParam(value = "all entries or only unread ones", allowableValues = "all,unread", required = true) @DefaultValue("all") @QueryParam("readType") ReadingMode readType,
      @ApiParam(value = "only entries newer than this") @QueryParam("newerThan") Long newerThan,
      @ApiParam(value = "offset for paging") @DefaultValue("0") @QueryParam("offset") int offset,
      @ApiParam(value = "limit for paging, default 20, maximum 1000") @DefaultValue("20") @QueryParam("limit") int limit,
      @ApiParam(value = "date ordering", allowableValues = "asc,desc") @QueryParam("order") @DefaultValue("desc") ReadingOrder order,
      @ApiParam(
          value = "search for keywords in either the title or the content of the entries, separated by spaces, 3 characters minimum") @QueryParam("keywords") String keywords,
      @ApiParam(value = "return only entry ids") @DefaultValue("false") @QueryParam("onlyIds") boolean onlyIds,
      @ApiParam(value = "comma-separated list of excluded subscription ids") @QueryParam("excludedSubscriptionIds") String excludedSubscriptionIds,
      @ApiParam(value = "keep only entries tagged with this tag") @QueryParam("tag") String tag) {

    Response response = getCategoryEntries(user, id, readType, newerThan, offset, limit, order, keywords, onlyIds,
        excludedSubscriptionIds, tag);
    if (response.getStatus() != Status.OK.getStatusCode()) {
      return response;
    }
    Entries entries = (Entries) response.getEntity();

    SyndFeed feed = new SyndFeedImpl();
    feed.setFeedType("rss_2.0");
    feed.setTitle("CommaFeed - " + entries.getName());
    feed.setDescription("CommaFeed - " + entries.getName());
    String publicUrl = config.getApplicationSettings().getPublicUrl();
    feed.setLink(publicUrl);

    List<SyndEntry> children = Lists.newArrayList();
    for (Entry entry : entries.getEntries()) {
      children.add(entry.asRss());
    }
    feed.setEntries(children);

    SyndFeedOutput output = new SyndFeedOutput();
    StringWriter writer = new StringWriter();
    try {
      output.output(feed, writer);
    } catch (Exception e) {
      writer.write("Could not get feed information");
      log.error(e.getMessage(), e);
    }
    return Response.ok(writer.toString()).build();
  }

  @Path("/mark")
  @POST
  @UnitOfWork
  @ApiOperation(value = "Mark category entries", notes = "Mark feed entries of this category as read")
  public Response markCategoryEntries(@SecurityCheck User user,
      @ApiParam(value = "category id, or 'all'", required = true) MarkRequest req) {
    Preconditions.checkNotNull(req);
    Preconditions.checkNotNull(req.getId());

    Date olderThan = req.getOlderThan() == null ? null : new Date(req.getOlderThan());
    String keywords = req.getKeywords();
    List<FeedEntryKeyword> entryKeywords = FeedEntryKeyword.fromQueryString(keywords);

    if (ALL.equals(req.getId())) {
      List<FeedSubscription> subs = feedSubscriptionDAO.findAll(user);
      removeExcludedSubscriptions(subs, req.getExcludedSubscriptions());
      feedEntryService.markSubscriptionEntries(user, subs, olderThan, entryKeywords);
    } else if (STARRED.equals(req.getId())) {
      feedEntryService.markStarredEntries(user, olderThan);
    } else {
      FeedCategory parent = feedCategoryDAO.findById(user, Long.valueOf(req.getId()));
      List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(user, parent);
      List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(user, categories);
      removeExcludedSubscriptions(subs, req.getExcludedSubscriptions());
      feedEntryService.markSubscriptionEntries(user, subs, olderThan, entryKeywords);
    }
    return Response.ok().build();
  }

  private void removeExcludedSubscriptions(List<FeedSubscription> subs, List<Long> excludedIds) {
    if (CollectionUtils.isNotEmpty(excludedIds)) {
      Iterator<FeedSubscription> it = subs.iterator();
      while (it.hasNext()) {
        FeedSubscription sub = it.next();
        if (excludedIds.contains(sub.getId())) {
          it.remove();
        }
      }
    }
  }

  @Path("/add")
  @POST
  @UnitOfWork
  @ApiOperation(value = "Add a category", notes = "Add a new feed category", response = Long.class)
  public Response addCategory(@SecurityCheck User user, @ApiParam(required = true) AddCategoryRequest req) {
    Preconditions.checkNotNull(req);
    Preconditions.checkNotNull(req.getName());

    FeedCategory cat = new FeedCategory();
    cat.setName(req.getName());
    cat.setUser(user);
    cat.setPosition(0);
    String parentId = req.getParentId();
    if (parentId != null && !ALL.equals(parentId)) {
      FeedCategory parent = new FeedCategory();
      parent.setId(Long.valueOf(parentId));
      cat.setParent(parent);
    }
    feedCategoryDAO.saveOrUpdate(cat);
    cache.invalidateUserRootCategory(user);
    return Response.ok(cat.getId()).build();
  }

  @POST
  @Path("/delete")
  @UnitOfWork
  @ApiOperation(value = "Delete a category", notes = "Delete an existing feed category")
  public Response deleteCategory(@SecurityCheck User user, @ApiParam(required = true) IDRequest req) {

    Preconditions.checkNotNull(req);
    Preconditions.checkNotNull(req.getId());

    FeedCategory cat = feedCategoryDAO.findById(user, req.getId());
    if (cat != null) {
      List<FeedSubscription> subs = feedSubscriptionDAO.findByCategory(user, cat);
      for (FeedSubscription sub : subs) {
        sub.setCategory(null);
      }
      feedSubscriptionDAO.saveOrUpdate(subs);
      List<FeedCategory> categories = feedCategoryDAO.findAllChildrenCategories(user, cat);
      for (FeedCategory child : categories) {
        if (!child.getId().equals(cat.getId()) && child.getParent().getId().equals(cat.getId())) {
          child.setParent(null);
        }
      }
      feedCategoryDAO.saveOrUpdate(categories);

      feedCategoryDAO.delete(cat);
      cache.invalidateUserRootCategory(user);
      return Response.ok().build();
    } else {
      return Response.status(Status.NOT_FOUND).build();
    }
  }

  @POST
  @Path("/modify")
  @UnitOfWork
  @ApiOperation(value = "Rename a category", notes = "Rename an existing feed category")
  public Response modifyCategory(@SecurityCheck User user, @ApiParam(required = true) CategoryModificationRequest req) {
    Preconditions.checkNotNull(req);
    Preconditions.checkNotNull(req.getId());

    FeedCategory category = feedCategoryDAO.findById(user, req.getId());

    if (StringUtils.isNotBlank(req.getName())) {
      category.setName(req.getName());
    }

    FeedCategory parent = null;
    if (req.getParentId() != null && !CategoryREST.ALL.equals(req.getParentId())
        && !StringUtils.equals(req.getParentId(), String.valueOf(req.getId()))) {
      parent = feedCategoryDAO.findById(user, Long.valueOf(req.getParentId()));
    }
    category.setParent(parent);

    if (req.getPosition() != null) {
      List<FeedCategory> categories = feedCategoryDAO.findByParent(user, parent);
      Collections.sort(categories, new Comparator<FeedCategory>() {
        @Override
        public int compare(FeedCategory o1, FeedCategory o2) {
          return ObjectUtils.compare(o1.getPosition(), o2.getPosition());
        }
      });

      int existingIndex = -1;
      for (int i = 0; i < categories.size(); i++) {
        if (Objects.equals(categories.get(i).getId(), category.getId())) {
          existingIndex = i;
        }
      }
      if (existingIndex != -1) {
        categories.remove(existingIndex);
      }

      categories.add(Math.min(req.getPosition(), categories.size()), category);
      for (int i = 0; i < categories.size(); i++) {
        categories.get(i).setPosition(i);
      }
      feedCategoryDAO.saveOrUpdate(categories);
    } else {
      feedCategoryDAO.saveOrUpdate(category);
    }

    feedCategoryDAO.saveOrUpdate(category);
    cache.invalidateUserRootCategory(user);
    return Response.ok().build();
  }

  @POST
  @Path("/collapse")
  @UnitOfWork
  @ApiOperation(value = "Collapse a category", notes = "Save collapsed or expanded status for a category")
  public Response collapse(@SecurityCheck User user, @ApiParam(required = true) CollapseRequest req) {
    Preconditions.checkNotNull(req);
    Preconditions.checkNotNull(req.getId());

    FeedCategory category = feedCategoryDAO.findById(user, req.getId());
    if (category == null) {
      return Response.status(Status.NOT_FOUND).build();
    }
    category.setCollapsed(req.isCollapse());
    feedCategoryDAO.saveOrUpdate(category);
    cache.invalidateUserRootCategory(user);
    return Response.ok().build();
  }

  @GET
  @Path("/unreadCount")
  @UnitOfWork
  @ApiOperation(value = "Get unread count for feed subscriptions", response = UnreadCount.class, responseContainer = "List")
  public Response getUnreadCount(@SecurityCheck User user) {
    Map<Long, UnreadCount> unreadCount = feedSubscriptionService.getUnreadCount(user);
    return Response.ok(Lists.newArrayList(unreadCount.values())).build();
  }

  @GET
  @Path("/get")
  @UnitOfWork
  @ApiOperation(value = "Get feed categories", notes = "Get all categories and subscriptions of the user", response = Category.class)
  public Response getSubscriptions(@SecurityCheck User user) {
    Category root = cache.getUserRootCategory(user);
    if (root == null) {
      log.debug("tree cache miss for {}", user.getId());
      List<FeedCategory> categories = feedCategoryDAO.findAll(user);
      List<FeedSubscription> subscriptions = feedSubscriptionDAO.findAll(user);
      Map<Long, UnreadCount> unreadCount = feedSubscriptionService.getUnreadCount(user);

      root = buildCategory(null, categories, subscriptions, unreadCount);
      root.setId("all");
      root.setName("All");
      cache.setUserRootCategory(user, root);
    }

    return Response.ok(root).build();
  }

  private Category buildCategory(Long id, List<FeedCategory> categories, List<FeedSubscription> subscriptions,
      Map<Long, UnreadCount> unreadCount) {
    Category category = new Category();
    category.setId(String.valueOf(id));
    category.setExpanded(true);

    for (FeedCategory c : categories) {
      if ((id == null && c.getParent() == null) || (c.getParent() != null && Objects.equals(c.getParent().getId(), id))) {
        Category child = buildCategory(c.getId(), categories, subscriptions, unreadCount);
        child.setId(String.valueOf(c.getId()));
        child.setName(c.getName());
        child.setPosition(c.getPosition());
        if (c.getParent() != null && c.getParent().getId() != null) {
          child.setParentId(String.valueOf(c.getParent().getId()));
        }
        child.setExpanded(!c.isCollapsed());
        category.getChildren().add(child);
      }
    }
    Collections.sort(category.getChildren(), new Comparator<Category>() {
      @Override
      public int compare(Category o1, Category o2) {
        return ObjectUtils.compare(o1.getPosition(), o2.getPosition());
      }
    });

    for (FeedSubscription subscription : subscriptions) {
      if ((id == null && subscription.getCategory() == null)
          || (subscription.getCategory() != null && Objects.equals(subscription.getCategory().getId(), id))) {
        UnreadCount uc = unreadCount.get(subscription.getId());
        Subscription sub = Subscription.build(subscription, config.getApplicationSettings().getPublicUrl(), uc);
        category.getFeeds().add(sub);
      }
    }
    Collections.sort(category.getFeeds(), new Comparator<Subscription>() {
      @Override
      public int compare(Subscription o1, Subscription o2) {
        return ObjectUtils.compare(o1.getPosition(), o2.getPosition());
      }
    });
    return category;
  }

}
TOP

Related Classes of com.commafeed.frontend.resource.CategoryREST

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.