package com.commafeed.backend.dao; import java.util.Date; import java.util.List; import java.util.Map; import javax.ejb.Stateless; import javax.inject.Inject; import javax.persistence.NoResultException; import javax.persistence.Query; import javax.persistence.Tuple; 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 javax.persistence.criteria.Selection; import javax.persistence.criteria.SetJoin; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.hibernate.Hibernate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.FeedCategory; 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.FeedSubscription; import com.commafeed.backend.model.FeedSubscription_; import com.commafeed.backend.model.Feed_; import com.commafeed.backend.model.User; import com.commafeed.backend.model.UserSettings.ReadingOrder; import com.commafeed.backend.services.ApplicationSettingsService; import com.google.common.base.Function; 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); @Inject ApplicationSettingsService applicationSettingsService; @SuppressWarnings("unchecked") public FeedEntryStatus findById(User user, Long id) { CriteriaQuery query = builder.createQuery(getType()); Root root = query.from(getType()); Join join = (Join) root .fetch(FeedEntryStatus_.subscription); Predicate p1 = builder.equal(root.get(FeedEntryStatus_.id), id); Predicate p2 = builder.equal(join.get(FeedSubscription_.user), user); query.where(p1, p2); FeedEntryStatus status = null; try { status = em.createQuery(query).getSingleResult(); } catch (NoResultException e) { status = null; } return status; } public FeedEntryStatus findByEntry(FeedEntry entry, FeedSubscription sub) { 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(); return Iterables.getFirst(statuses, null); } public List findByEntries(List entries, FeedSubscription sub) { List results = Lists.newArrayList(); if (CollectionUtils.isEmpty(entries)) { return results; } CriteriaQuery query = builder.createQuery(getType()); Root root = query.from(getType()); Predicate p1 = root.get(FeedEntryStatus_.entry).in(entries); Predicate p2 = builder.equal(root.get(FeedEntryStatus_.subscription), sub); query.where(p1, p2); Map existing = Maps.uniqueIndex( em.createQuery(query).getResultList(), new Function() { @Override public Long apply(FeedEntryStatus input) { return input.getEntry().getId(); } }); for (FeedEntry entry : entries) { FeedEntryStatus s = existing.get(entry.getId()); if (s == null) { s = new FeedEntryStatus(); s.setEntry(entry); s.setSubscription(sub); s.setRead(true); } results.add(s); } return results; } public List findByKeywords(User user, String keywords, int offset, int limit) { String joinedKeywords = StringUtils.join( keywords.toLowerCase().split(" "), "%"); joinedKeywords = "%" + joinedKeywords + "%"; CriteriaQuery query = builder.createTupleQuery(); Root root = query.from(FeedEntry.class); SetJoin feedJoin = root.join(FeedEntry_.feeds); SetJoin subJoin = feedJoin .join(Feed_.subscriptions); Join contentJoin = root .join(FeedEntry_.content); Selection entryAlias = root.alias("entry"); Selection subAlias = subJoin.alias("subscription"); query.multiselect(entryAlias, subAlias); List predicates = Lists.newArrayList(); predicates .add(builder.equal(subJoin.get(FeedSubscription_.user), user)); 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)); query.where(predicates.toArray(new Predicate[0])); orderBy(query, root, ReadingOrder.desc); TypedQuery q = em.createQuery(query); limit(q, offset, limit); setTimeout(q); List list = q.getResultList(); List results = Lists.newArrayList(); for (Tuple tuple : list) { FeedEntry entry = tuple.get(entryAlias); FeedSubscription subscription = tuple.get(subAlias); FeedEntryStatus status = findByEntry(entry, subscription); if (status == null) { status = new FeedEntryStatus(); status.setEntry(entry); status.setRead(true); status.setSubscription(subscription); } results.add(status); } return lazyLoadContent(true, results); } public List findStarred(User user, ReadingOrder order, boolean includeContent) { return findStarred(user, null, -1, -1, order, includeContent); } 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(); Join entryJoin = root .join(FeedEntryStatus_.entry); Join subJoin = root .join(FeedEntryStatus_.subscription); predicates .add(builder.equal(subJoin.get(FeedSubscription_.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( entryJoin.get(FeedEntry_.inserted), newerThan)); } orderBy(query, entryJoin, order); TypedQuery q = em.createQuery(query); limit(q, offset, limit); setTimeout(q); return lazyLoadContent(includeContent, q.getResultList()); } public List findAll(User user, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) { CriteriaQuery query = builder.createTupleQuery(); Root root = query.from(FeedEntry.class); SetJoin feedJoin = root.join(FeedEntry_.feeds); SetJoin subJoin = feedJoin .join(Feed_.subscriptions); Selection entryAlias = root.alias("entry"); Selection subAlias = subJoin.alias("subscription"); query.multiselect(entryAlias, subAlias); List predicates = Lists.newArrayList(); predicates .add(builder.equal(subJoin.get(FeedSubscription_.user), user)); if (newerThan != null) { predicates.add(builder.greaterThanOrEqualTo( root.get(FeedEntry_.inserted), newerThan)); } query.where(predicates.toArray(new Predicate[0])); orderBy(query, root, order); TypedQuery q = em.createQuery(query); limit(q, offset, limit); setTimeout(q); List list = q.getResultList(); List results = Lists.newArrayList(); for (Tuple tuple : list) { FeedEntry entry = tuple.get(entryAlias); FeedSubscription subscription = tuple.get(subAlias); FeedEntryStatus status = findByEntry(entry, subscription); if (status == null) { status = new FeedEntryStatus(); status.setEntry(entry); status.setRead(true); status.setSubscription(subscription); } results.add(status); } return lazyLoadContent(includeContent, results); } public List findAllUnread(User user, ReadingOrder order, boolean includeContent) { return findAllUnread(user, null, -1, -1, order, includeContent); } 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(); Join entryJoin = root .join(FeedEntryStatus_.entry); Join subJoin = root .join(FeedEntryStatus_.subscription); predicates .add(builder.equal(subJoin.get(FeedSubscription_.user), user)); predicates.add(builder.isFalse(root.get(FeedEntryStatus_.read))); if (newerThan != null) { predicates.add(builder.greaterThanOrEqualTo( entryJoin.get(FeedEntry_.inserted), newerThan)); } query.where(predicates.toArray(new Predicate[0])); orderBy(query, entryJoin, order); TypedQuery q = em.createQuery(query); limit(q, offset, limit); setTimeout(q); return lazyLoadContent(includeContent, q.getResultList()); } public List findBySubscription( FeedSubscription subscription, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) { CriteriaQuery query = builder.createQuery(FeedEntry.class); Root root = query.from(FeedEntry.class); SetJoin feedJoin = root.join(FeedEntry_.feeds); SetJoin subJoin = feedJoin .join(Feed_.subscriptions); List predicates = Lists.newArrayList(); predicates.add(builder.equal(subJoin.get(FeedSubscription_.id), subscription.getId())); if (newerThan != null) { predicates.add(builder.greaterThanOrEqualTo( root.get(FeedEntry_.inserted), newerThan)); } query.where(predicates.toArray(new Predicate[0])); orderBy(query, root, order); TypedQuery q = em.createQuery(query); limit(q, offset, limit); setTimeout(q); List list = q.getResultList(); return lazyLoadContent(includeContent, findByEntries(list, subscription)); } public List findUnreadBySubscription( FeedSubscription subscription, ReadingOrder order, boolean includeContent) { return findUnreadBySubscription(subscription, null, -1, -1, order, includeContent); } public List findUnreadBySubscription( FeedSubscription subscription, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) { CriteriaQuery query = builder.createQuery(getType()); Root root = query.from(getType()); List predicates = Lists.newArrayList(); Join entryJoin = root .join(FeedEntryStatus_.entry); predicates.add(builder.equal(root.get(FeedEntryStatus_.subscription), subscription)); predicates.add(builder.isFalse(root.get(FeedEntryStatus_.read))); if (newerThan != null) { predicates.add(builder.greaterThanOrEqualTo( entryJoin.get(FeedEntry_.inserted), newerThan)); } query.where(predicates.toArray(new Predicate[0])); orderBy(query, entryJoin, order); TypedQuery q = em.createQuery(query); limit(q, offset, limit); setTimeout(q); return lazyLoadContent(includeContent, q.getResultList()); } public List findByCategories( List categories, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) { CriteriaQuery query = builder.createTupleQuery(); Root root = query.from(FeedEntry.class); SetJoin feedJoin = root.join(FeedEntry_.feeds); SetJoin subJoin = feedJoin .join(Feed_.subscriptions); Selection entryAlias = root.alias("entry"); Selection subAlias = subJoin.alias("subscription"); query.multiselect(entryAlias, subAlias); List predicates = Lists.newArrayList(); if (categories.size() == 1) { predicates.add(builder.equal(subJoin .get(FeedSubscription_.category), categories.iterator() .next())); } else { predicates.add(subJoin.get(FeedSubscription_.category).in( categories)); } if (newerThan != null) { predicates.add(builder.greaterThanOrEqualTo( root.get(FeedEntry_.inserted), newerThan)); } query.where(predicates.toArray(new Predicate[0])); orderBy(query, root, order); TypedQuery q = em.createQuery(query); limit(q, offset, limit); setTimeout(q); List list = q.getResultList(); List results = Lists.newArrayList(); for (Tuple tuple : list) { FeedEntry entry = tuple.get(entryAlias); FeedSubscription subscription = tuple.get(subAlias); FeedEntryStatus status = findByEntry(entry, subscription); if (status == null) { status = new FeedEntryStatus(); status.setEntry(entry); status.setSubscription(subscription); status.setRead(true); } results.add(status); } return lazyLoadContent(includeContent, results); } public List findUnreadByCategories( List categories, ReadingOrder order, boolean includeContent) { return findUnreadByCategories(categories, null, -1, -1, order, includeContent); } public List findUnreadByCategories( List categories, Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) { CriteriaQuery query = builder.createQuery(getType()); Root root = query.from(getType()); List predicates = Lists.newArrayList(); Join entryJoin = root .join(FeedEntryStatus_.entry); Join subJoin = root .join(FeedEntryStatus_.subscription); if (categories.size() == 1) { predicates.add(builder.equal(subJoin .get(FeedSubscription_.category), categories.iterator() .next())); } else { predicates.add(subJoin.get(FeedSubscription_.category).in( categories)); } predicates.add(builder.isFalse(root.get(FeedEntryStatus_.read))); if (newerThan != null) { predicates.add(builder.greaterThanOrEqualTo( entryJoin.get(FeedEntry_.inserted), newerThan)); } query.where(predicates.toArray(new Predicate[0])); orderBy(query, entryJoin, 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) { Hibernate.initialize(status.getSubscription().getFeed()); Hibernate.initialize(status.getEntry().getContent()); } } return results; } private void orderBy(CriteriaQuery query, Path entryJoin, ReadingOrder order) { if (order != null) { Path orderPath = entryJoin.get(FeedEntry_.updated); if (order == ReadingOrder.asc) { query.orderBy(builder.asc(orderPath)); } else { query.orderBy(builder.desc(orderPath)); } } } private void setTimeout(Query query) { int queryTimeout = applicationSettingsService.get().getQueryTimeout(); if (queryTimeout > 0) { query.setHint("javax.persistence.query.timeout", queryTimeout); } } public void markSubscriptionEntries(FeedSubscription subscription, Date olderThan) { List statuses = findUnreadBySubscription(subscription, null, false); markList(statuses, olderThan); } public void markCategoryEntries(User user, List categories, Date olderThan) { List statuses = findUnreadByCategories(categories, null, false); markList(statuses, olderThan); } public void markStarredEntries(User user, Date olderThan) { List statuses = findStarred(user, null, false); markList(statuses, olderThan); } public void markAllEntries(User user, Date olderThan) { List statuses = findAllUnread(user, 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); } }