package com.commafeed.backend.dao; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Map; import javax.ejb.Stateless; import javax.inject.Inject; import javax.persistence.Query; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Join; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.ObjectUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.commafeed.backend.FixedSizeSortedSet; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntryContent; import com.commafeed.backend.model.FeedEntryContent_; import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedEntryStatus_; import com.commafeed.backend.model.FeedEntry_; import com.commafeed.backend.model.FeedFeedEntry; import com.commafeed.backend.model.FeedFeedEntry_; import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.Models; import com.commafeed.backend.model.User; import com.commafeed.backend.model.UserSettings.ReadingOrder; import com.commafeed.backend.services.ApplicationSettingsService; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @Stateless public class FeedEntryStatusDAO extends GenericDAO { protected static Logger log = LoggerFactory .getLogger(FeedEntryStatusDAO.class); private static final Comparator ENTRY_COMPARATOR_DESC = new Comparator() { @Override public int compare(FeedEntry o1, FeedEntry o2) { return ObjectUtils.compare(o2.getUpdated(), o1.getUpdated()); }; }; private static final Comparator ENTRY_COMPARATOR_ASC = new Comparator() { @Override public int compare(FeedEntry o1, FeedEntry o2) { return ObjectUtils.compare(o1.getUpdated(), o2.getUpdated()); }; }; private static final Comparator STATUS_COMPARATOR_DESC = new Comparator() { @Override public int compare(FeedEntryStatus o1, FeedEntryStatus o2) { return ObjectUtils.compare(o2.getEntryUpdated(), o1.getEntryUpdated()); }; }; private static final Comparator STATUS_COMPARATOR_ASC = new Comparator() { @Override public int compare(FeedEntryStatus o1, FeedEntryStatus o2) { return ObjectUtils.compare(o1.getEntryUpdated(), o2.getEntryUpdated()); }; }; @Inject ApplicationSettingsService applicationSettingsService; public FeedEntryStatus getStatus(FeedSubscription sub, FeedEntry entry) { CriteriaQuery query = builder.createQuery(getType()); Root root = query.from(getType()); Predicate p1 = builder.equal(root.get(FeedEntryStatus_.entry), entry); Predicate p2 = builder.equal(root.get(FeedEntryStatus_.subscription), sub); query.where(p1, p2); List statuses = em.createQuery(query).getResultList(); FeedEntryStatus status = Iterables.getFirst(statuses, null); if (status == null) { status = new FeedEntryStatus(sub.getUser(), sub, entry); status.setRead(true); } return status; } public List findStarred(User user, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) { CriteriaQuery query = builder.createQuery(getType()); Root root = query.from(getType()); List predicates = Lists.newArrayList(); predicates.add(builder.equal(root.get(FeedEntryStatus_.user), user)); predicates.add(builder.equal(root.get(FeedEntryStatus_.starred), true)); query.where(predicates.toArray(new Predicate[0])); if (newerThan != null) { predicates.add(builder.greaterThanOrEqualTo( root.get(FeedEntryStatus_.entryInserted), newerThan)); } orderStatusesBy(query, root, order); TypedQuery q = em.createQuery(query); limit(q, offset, limit); setTimeout(q); return lazyLoadContent(includeContent, q.getResultList()); } public List findBySubscriptions( List subscriptions, String keywords, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) { int capacity = offset + limit; Comparator comparator = order == ReadingOrder.desc ? ENTRY_COMPARATOR_DESC : ENTRY_COMPARATOR_ASC; FixedSizeSortedSet set = new FixedSizeSortedSet( capacity < 0 ? Integer.MAX_VALUE : capacity, comparator); for (FeedSubscription sub : subscriptions) { CriteriaQuery query = builder .createQuery(FeedEntry.class); Root root = query.from(FeedEntry.class); Join ffeJoin = root .join(FeedEntry_.feedRelationships); List predicates = Lists.newArrayList(); predicates.add(builder.equal(ffeJoin.get(FeedFeedEntry_.feed), sub.getFeed())); if (newerThan != null) { predicates.add(builder.greaterThanOrEqualTo( root.get(FeedEntry_.inserted), newerThan)); } if (keywords != null) { Join contentJoin = root .join(FeedEntry_.content); String joinedKeywords = StringUtils.join(keywords.toLowerCase() .split(" "), "%"); joinedKeywords = "%" + joinedKeywords + "%"; Predicate content = builder.like(builder.lower(contentJoin .get(FeedEntryContent_.content)), joinedKeywords); Predicate title = builder .like(builder.lower(contentJoin .get(FeedEntryContent_.title)), joinedKeywords); predicates.add(builder.or(content, title)); } if (order != null && !set.isEmpty() && set.isFull()) { Predicate filter = null; FeedEntry last = set.last(); if (order == ReadingOrder.desc) { filter = builder.greaterThan( ffeJoin.get(FeedFeedEntry_.entryUpdated), last.getUpdated()); } else { filter = builder.lessThan( ffeJoin.get(FeedFeedEntry_.entryUpdated), last.getUpdated()); } predicates.add(filter); } query.where(predicates.toArray(new Predicate[0])); orderEntriesBy(query, ffeJoin, order); TypedQuery q = em.createQuery(query); limit(q, 0, capacity); setTimeout(q); List list = q.getResultList(); for (FeedEntry entry : list) { entry.setSubscription(sub); } set.addAll(list); } List entries = set.asList(); int size = entries.size(); if (size < offset) { return Lists.newArrayList(); } entries = entries.subList(Math.max(offset, 0), size); List results = Lists.newArrayList(); for (FeedEntry entry : entries) { FeedSubscription subscription = entry.getSubscription(); results.add(getStatus(subscription, entry)); } return lazyLoadContent(includeContent, results); } public List findUnreadBySubscriptions( List subscriptions, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) { int capacity = offset + limit; Comparator comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC : STATUS_COMPARATOR_ASC; FixedSizeSortedSet set = new FixedSizeSortedSet( capacity < 0 ? Integer.MAX_VALUE : capacity, comparator); for (FeedSubscription sub : subscriptions) { CriteriaQuery query = builder .createQuery(getType()); Root root = query.from(getType()); List predicates = Lists.newArrayList(); predicates.add(builder.equal( root.get(FeedEntryStatus_.subscription), sub)); predicates.add(builder.isFalse(root.get(FeedEntryStatus_.read))); if (newerThan != null) { predicates.add(builder.greaterThanOrEqualTo( root.get(FeedEntryStatus_.entryInserted), newerThan)); } if (order != null && !set.isEmpty() && set.isFull()) { Predicate filter = null; FeedEntryStatus last = set.last(); if (order == ReadingOrder.desc) { filter = builder.greaterThan( root.get(FeedEntryStatus_.entryUpdated), last.getEntryUpdated()); } else { filter = builder.lessThan( root.get(FeedEntryStatus_.entryUpdated), last.getEntryUpdated()); } predicates.add(filter); } query.where(predicates.toArray(new Predicate[0])); orderStatusesBy(query, root, order); TypedQuery q = em.createQuery(query); limit(q, -1, limit); setTimeout(q); List list = q.getResultList(); set.addAll(list); } List entries = set.asList(); int size = entries.size(); if (size < offset) { return Lists.newArrayList(); } entries = entries.subList(Math.max(offset, 0), size); return lazyLoadContent(includeContent, entries); } public List findAllUnread(User user, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) { CriteriaQuery query = builder.createQuery(getType()); Root root = query.from(getType()); List predicates = Lists.newArrayList(); predicates.add(builder.equal(root.get(FeedEntryStatus_.user), user)); predicates.add(builder.isFalse(root.get(FeedEntryStatus_.read))); if (newerThan != null) { predicates.add(builder.greaterThanOrEqualTo( root.get(FeedEntryStatus_.entryInserted), newerThan)); } query.where(predicates.toArray(new Predicate[0])); orderStatusesBy(query, root, order); TypedQuery q = em.createQuery(query); limit(q, offset, limit); setTimeout(q); return lazyLoadContent(includeContent, q.getResultList()); } /** * Map between subscriptionId and unread count */ @SuppressWarnings("rawtypes") public Map getUnreadCount(User user) { Map map = Maps.newHashMap(); Query query = em.createNamedQuery("EntryStatus.unreadCounts"); query.setParameter("user", user); setTimeout(query); List resultList = query.getResultList(); for (Object o : resultList) { Object[] array = (Object[]) o; map.put((Long) array[0], (Long) array[1]); } return map; } private List lazyLoadContent(boolean includeContent, List results) { if (includeContent) { for (FeedEntryStatus status : results) { Models.initialize(status.getSubscription().getFeed()); Models.initialize(status.getEntry().getContent()); } } return results; } private void orderEntriesBy(CriteriaQuery query, Path ffeJoin, ReadingOrder order) { orderBy(query, ffeJoin.get(FeedFeedEntry_.entryUpdated), order); } private void orderStatusesBy(CriteriaQuery query, Path statusJoin, ReadingOrder order) { orderBy(query, statusJoin.get(FeedEntryStatus_.entryUpdated), order); } private void orderBy(CriteriaQuery query, Path date, ReadingOrder order) { if (order != null) { if (order == ReadingOrder.asc) { query.orderBy(builder.asc(date)); } else { query.orderBy(builder.desc(date)); } } } protected void setTimeout(Query query) { setTimeout(query, applicationSettingsService.get().getQueryTimeout()); } public void markAllEntries(User user, Date olderThan) { List statuses = findAllUnread(user, null, -1, -1, null, false); markList(statuses, olderThan); } public void markSubscriptionEntries(List subscriptions, Date olderThan) { List statuses = findUnreadBySubscriptions( subscriptions, null, -1, -1, null, false); markList(statuses, olderThan); } public void markStarredEntries(User user, Date olderThan) { List statuses = findStarred(user, null, -1, -1, null, false); markList(statuses, olderThan); } private void markList(List statuses, Date olderThan) { List list = Lists.newArrayList(); for (FeedEntryStatus status : statuses) { if (!status.isRead()) { Date inserted = status.getEntry().getInserted(); if (olderThan == null || inserted == null || olderThan.after(inserted)) { if (status.isStarred()) { status.setRead(true); list.add(status); } else { delete(status); } } } } saveOrUpdate(list); } }