Merge pull request #452 from Athou/refactor

Refactor
This commit is contained in:
Athou
2013-07-19 03:57:10 -07:00
11 changed files with 381 additions and 376 deletions

View File

@@ -0,0 +1,62 @@
package com.commafeed.backend;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.TreeSet;
import org.apache.commons.collections.CollectionUtils;
import com.google.common.collect.Lists;
public class FixedSizeSortedSet<E> extends TreeSet<E> {
private static final long serialVersionUID = 1L;
private final Comparator<? super E> comparator;
private final int maxSize;
public FixedSizeSortedSet(int maxSize, Comparator<? super E> comparator) {
super(comparator);
this.maxSize = maxSize;
this.comparator = comparator;
}
@Override
public boolean add(E e) {
if (isFull()) {
E last = last();
int comparison = comparator.compare(e, last);
if (comparison < 0) {
remove(last);
return super.add(e);
} else {
return false;
}
} else {
return super.add(e);
}
}
@Override
public boolean addAll(Collection<? extends E> c) {
if (CollectionUtils.isEmpty(c)) {
return false;
}
boolean success = true;
for (E e : c) {
success &= add(e);
}
return success;
}
public boolean isFull() {
return size() == maxSize;
}
@SuppressWarnings("unchecked")
public List<E> asList() {
return (List<E>) Lists.newArrayList(toArray());
}
}

View File

@@ -1,5 +1,6 @@
package com.commafeed.backend.dao; package com.commafeed.backend.dao;
import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -7,22 +8,18 @@ import java.util.Map;
import javax.ejb.Stateless; import javax.ejb.Stateless;
import javax.inject.Inject; import javax.inject.Inject;
import javax.persistence.Query; import javax.persistence.Query;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join; import javax.persistence.criteria.Join;
import javax.persistence.criteria.Path; import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root; import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.commafeed.backend.model.Feed; import com.commafeed.backend.FixedSizeSortedSet;
import com.commafeed.backend.model.FeedCategory;
import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntryContent; import com.commafeed.backend.model.FeedEntryContent;
import com.commafeed.backend.model.FeedEntryContent_; import com.commafeed.backend.model.FeedEntryContent_;
@@ -32,13 +29,10 @@ import com.commafeed.backend.model.FeedEntry_;
import com.commafeed.backend.model.FeedFeedEntry; import com.commafeed.backend.model.FeedFeedEntry;
import com.commafeed.backend.model.FeedFeedEntry_; import com.commafeed.backend.model.FeedFeedEntry_;
import com.commafeed.backend.model.FeedSubscription; import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.FeedSubscription_;
import com.commafeed.backend.model.Feed_;
import com.commafeed.backend.model.Models; import com.commafeed.backend.model.Models;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserSettings.ReadingOrder; import com.commafeed.backend.model.UserSettings.ReadingOrder;
import com.commafeed.backend.services.ApplicationSettingsService; import com.commafeed.backend.services.ApplicationSettingsService;
import com.google.common.base.Function;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@@ -49,10 +43,38 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
protected static Logger log = LoggerFactory protected static Logger log = LoggerFactory
.getLogger(FeedEntryStatusDAO.class); .getLogger(FeedEntryStatusDAO.class);
private static final Comparator<FeedEntry> ENTRY_COMPARATOR_DESC = new Comparator<FeedEntry>() {
@Override
public int compare(FeedEntry o1, FeedEntry o2) {
return o2.getUpdated().compareTo(o1.getUpdated());
};
};
private static final Comparator<FeedEntry> ENTRY_COMPARATOR_ASC = new Comparator<FeedEntry>() {
@Override
public int compare(FeedEntry o1, FeedEntry o2) {
return o1.getUpdated().compareTo(o2.getUpdated());
};
};
private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_DESC = new Comparator<FeedEntryStatus>() {
@Override
public int compare(FeedEntryStatus o1, FeedEntryStatus o2) {
return o2.getEntryUpdated().compareTo(o1.getEntryUpdated());
};
};
private static final Comparator<FeedEntryStatus> STATUS_COMPARATOR_ASC = new Comparator<FeedEntryStatus>() {
@Override
public int compare(FeedEntryStatus o1, FeedEntryStatus o2) {
return o2.getEntryUpdated().compareTo(o1.getEntryUpdated());
};
};
@Inject @Inject
ApplicationSettingsService applicationSettingsService; ApplicationSettingsService applicationSettingsService;
public FeedEntryStatus findByEntry(FeedEntry entry, FeedSubscription sub) { public FeedEntryStatus getStatus(FeedSubscription sub, FeedEntry entry) {
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType()); CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
Root<FeedEntryStatus> root = query.from(getType()); Root<FeedEntryStatus> root = query.from(getType());
@@ -64,108 +86,12 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
query.where(p1, p2); query.where(p1, p2);
List<FeedEntryStatus> statuses = em.createQuery(query).getResultList(); List<FeedEntryStatus> statuses = em.createQuery(query).getResultList();
return Iterables.getFirst(statuses, null); FeedEntryStatus status = Iterables.getFirst(statuses, null);
} if (status == null) {
status = new FeedEntryStatus(sub.getUser(), sub, entry);
public List<FeedEntryStatus> findByEntries(List<FeedEntry> entries, status.setRead(true);
FeedSubscription sub) {
List<FeedEntryStatus> results = Lists.newArrayList();
if (CollectionUtils.isEmpty(entries)) {
return results;
} }
return status;
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
Root<FeedEntryStatus> 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<Long, FeedEntryStatus> existing = Maps.uniqueIndex(
em.createQuery(query).getResultList(),
new Function<FeedEntryStatus, Long>() {
@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(sub.getUser(), sub, entry);
s.setSubscription(sub);
s.setRead(true);
}
results.add(s);
}
return results;
}
public List<FeedEntryStatus> findByKeywords(User user, String keywords,
int offset, int limit) {
String joinedKeywords = StringUtils.join(
keywords.toLowerCase().split(" "), "%");
joinedKeywords = "%" + joinedKeywords + "%";
CriteriaQuery<Tuple> query = builder.createTupleQuery();
Root<FeedEntry> root = query.from(FeedEntry.class);
Join<FeedFeedEntry, Feed> feedJoin = root.join(FeedEntry_.feedRelationships).join(FeedFeedEntry_.feed);
Join<Feed, FeedSubscription> subJoin = feedJoin
.join(Feed_.subscriptions);
Join<FeedEntry, FeedEntryContent> contentJoin = root
.join(FeedEntry_.content);
Selection<FeedEntry> entryAlias = root.alias("entry");
Selection<FeedSubscription> subAlias = subJoin.alias("subscription");
query.multiselect(entryAlias, subAlias);
List<Predicate> 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]));
orderEntriesBy(query, root, ReadingOrder.desc);
TypedQuery<Tuple> q = em.createQuery(query);
limit(q, offset, limit);
setTimeout(q);
List<Tuple> list = q.getResultList();
List<FeedEntryStatus> 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(user, subscription, entry);
status.setRead(true);
status.setSubscription(subscription);
}
results.add(status);
}
return lazyLoadContent(true, results);
}
public List<FeedEntryStatus> findStarred(User user, ReadingOrder order,
boolean includeContent) {
return findStarred(user, null, -1, -1, order, includeContent);
} }
public List<FeedEntryStatus> findStarred(User user, Date newerThan, public List<FeedEntryStatus> findStarred(User user, Date newerThan,
@@ -176,8 +102,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
List<Predicate> predicates = Lists.newArrayList(); List<Predicate> predicates = Lists.newArrayList();
predicates predicates.add(builder.equal(root.get(FeedEntryStatus_.user), user));
.add(builder.equal(root.get(FeedEntryStatus_.user), user));
predicates.add(builder.equal(root.get(FeedEntryStatus_.starred), true)); predicates.add(builder.equal(root.get(FeedEntryStatus_.starred), true));
query.where(predicates.toArray(new Predicate[0])); query.where(predicates.toArray(new Predicate[0]));
@@ -194,69 +119,161 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
return lazyLoadContent(includeContent, q.getResultList()); return lazyLoadContent(includeContent, q.getResultList());
} }
public List<FeedEntryStatus> findAll(User user, Date newerThan, int offset, public List<FeedEntryStatus> findBySubscriptions(
int limit, ReadingOrder order, boolean includeContent) { List<FeedSubscription> subscriptions, String keywords,
Date newerThan, int offset, int limit, ReadingOrder order,
boolean includeContent) {
CriteriaQuery<Tuple> query = builder.createTupleQuery(); int capacity = offset + limit;
Root<FeedEntry> root = query.from(FeedEntry.class); Comparator<FeedEntry> comparator = order == ReadingOrder.desc ? ENTRY_COMPARATOR_DESC
: ENTRY_COMPARATOR_ASC;
FixedSizeSortedSet<FeedEntry> set = new FixedSizeSortedSet<FeedEntry>(
capacity < 0 ? Integer.MAX_VALUE : capacity, comparator);
for (FeedSubscription sub : subscriptions) {
CriteriaQuery<FeedEntry> query = builder
.createQuery(FeedEntry.class);
Root<FeedEntry> root = query.from(FeedEntry.class);
Join<FeedEntry, FeedFeedEntry> ffeJoin = root
.join(FeedEntry_.feedRelationships);
Join<FeedFeedEntry, Feed> feedJoin = root.join(FeedEntry_.feedRelationships).join(FeedFeedEntry_.feed); List<Predicate> predicates = Lists.newArrayList();
Join<Feed, FeedSubscription> subJoin = feedJoin predicates.add(builder.equal(ffeJoin.get(FeedFeedEntry_.feed),
.join(Feed_.subscriptions); sub.getFeed()));
Selection<FeedEntry> entryAlias = root.alias("entry"); if (newerThan != null) {
Selection<FeedSubscription> subAlias = subJoin.alias("subscription"); predicates.add(builder.greaterThanOrEqualTo(
query.multiselect(entryAlias, subAlias); root.get(FeedEntry_.inserted), newerThan));
}
List<Predicate> predicates = Lists.newArrayList(); if (keywords != null) {
Join<FeedEntry, FeedEntryContent> contentJoin = root
.join(FeedEntry_.content);
predicates String joinedKeywords = StringUtils.join(keywords.toLowerCase()
.add(builder.equal(subJoin.get(FeedSubscription_.user), user)); .split(" "), "%");
joinedKeywords = "%" + joinedKeywords + "%";
if (newerThan != null) { Predicate content = builder.like(builder.lower(contentJoin
predicates.add(builder.greaterThanOrEqualTo( .get(FeedEntryContent_.content)), joinedKeywords);
root.get(FeedEntry_.inserted), newerThan)); 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<FeedEntry> q = em.createQuery(query);
limit(q, 0, capacity);
setTimeout(q);
List<FeedEntry> list = q.getResultList();
for (FeedEntry entry : list) {
entry.setSubscription(sub);
}
set.addAll(list);
} }
query.where(predicates.toArray(new Predicate[0])); List<FeedEntry> entries = set.asList();
orderEntriesBy(query, root, order); int size = entries.size();
if (size < offset) {
return Lists.newArrayList();
}
TypedQuery<Tuple> q = em.createQuery(query); entries = entries.subList(Math.max(offset, 0), size);
limit(q, offset, limit);
setTimeout(q);
List<Tuple> list = q.getResultList();
List<FeedEntryStatus> results = Lists.newArrayList(); List<FeedEntryStatus> results = Lists.newArrayList();
for (Tuple tuple : list) { for (FeedEntry entry : entries) {
FeedEntry entry = tuple.get(entryAlias); FeedSubscription subscription = entry.getSubscription();
FeedSubscription subscription = tuple.get(subAlias); results.add(getStatus(subscription, entry));
FeedEntryStatus status = findByEntry(entry, subscription);
if (status == null) {
status = new FeedEntryStatus(user, subscription, entry);
status.setRead(true);
status.setSubscription(subscription);
}
results.add(status);
} }
return lazyLoadContent(includeContent, results); return lazyLoadContent(includeContent, results);
} }
public List<FeedEntryStatus> findAllUnread(User user, ReadingOrder order, public List<FeedEntryStatus> findUnreadBySubscriptions(
boolean includeContent) { List<FeedSubscription> subscriptions, Date newerThan, int offset,
return findAllUnread(user, null, -1, -1, order, includeContent); int limit, ReadingOrder order, boolean includeContent) {
int capacity = offset + limit;
Comparator<FeedEntryStatus> comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC
: STATUS_COMPARATOR_ASC;
FixedSizeSortedSet<FeedEntryStatus> set = new FixedSizeSortedSet<FeedEntryStatus>(
capacity < 0 ? Integer.MAX_VALUE : capacity, comparator);
for (FeedSubscription sub : subscriptions) {
CriteriaQuery<FeedEntryStatus> query = builder
.createQuery(getType());
Root<FeedEntryStatus> root = query.from(getType());
List<Predicate> 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<FeedEntryStatus> q = em.createQuery(query);
limit(q, -1, limit);
setTimeout(q);
List<FeedEntryStatus> list = q.getResultList();
set.addAll(list);
}
List<FeedEntryStatus> 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<FeedEntryStatus> findAllUnread(User user, Date newerThan, public List<FeedEntryStatus> findAllUnread(User user, Date newerThan,
int offset, int limit, ReadingOrder order, boolean includeContent) { int offset, int limit, ReadingOrder order, boolean includeContent) {
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType()); CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
Root<FeedEntryStatus> root = query.from(getType()); Root<FeedEntryStatus> root = query.from(getType());
List<Predicate> predicates = Lists.newArrayList(); List<Predicate> predicates = Lists.newArrayList();
predicates predicates.add(builder.equal(root.get(FeedEntryStatus_.user), user));
.add(builder.equal(root.get(FeedEntryStatus_.user), user));
predicates.add(builder.isFalse(root.get(FeedEntryStatus_.read))); predicates.add(builder.isFalse(root.get(FeedEntryStatus_.read)));
if (newerThan != null) { if (newerThan != null) {
@@ -270,174 +287,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
TypedQuery<FeedEntryStatus> q = em.createQuery(query); TypedQuery<FeedEntryStatus> q = em.createQuery(query);
limit(q, offset, limit); limit(q, offset, limit);
setTimeout(q); setTimeout(q);
return lazyLoadContent(includeContent, q.getResultList());
}
public List<FeedEntryStatus> findBySubscription(
FeedSubscription subscription, Date newerThan, int offset,
int limit, ReadingOrder order, boolean includeContent) {
CriteriaQuery<FeedEntry> query = builder.createQuery(FeedEntry.class);
Root<FeedEntry> root = query.from(FeedEntry.class);
Join<FeedEntry, FeedFeedEntry> ffeJoin = root.join(FeedEntry_.feedRelationships);
List<Predicate> predicates = Lists.newArrayList();
predicates.add(builder.equal(ffeJoin.get(FeedFeedEntry_.feed),
subscription.getFeed()));
if (newerThan != null) {
predicates.add(builder.greaterThanOrEqualTo(
root.get(FeedEntry_.inserted), newerThan));
}
query.where(predicates.toArray(new Predicate[0]));
orderEntriesBy(query, root, order);
TypedQuery<FeedEntry> q = em.createQuery(query);
limit(q, offset, limit);
setTimeout(q);
List<FeedEntry> list = q.getResultList();
return lazyLoadContent(includeContent,
findByEntries(list, subscription));
}
public List<FeedEntryStatus> findUnreadBySubscription(
FeedSubscription subscription, ReadingOrder order,
boolean includeContent) {
return findUnreadBySubscription(subscription, null, -1, -1, order,
includeContent);
}
public List<FeedEntryStatus> findUnreadBySubscription(
FeedSubscription subscription, Date newerThan, int offset,
int limit, ReadingOrder order, boolean includeContent) {
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
Root<FeedEntryStatus> root = query.from(getType());
List<Predicate> predicates = Lists.newArrayList();
predicates.add(builder.equal(root.get(FeedEntryStatus_.subscription),
subscription));
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<FeedEntryStatus> q = em.createQuery(query);
limit(q, offset, limit);
setTimeout(q);
return lazyLoadContent(includeContent, q.getResultList());
}
public List<FeedEntryStatus> findByCategories(
List<FeedCategory> categories, Date newerThan, int offset,
int limit, ReadingOrder order, boolean includeContent) {
CriteriaQuery<Tuple> query = builder.createTupleQuery();
Root<FeedEntry> root = query.from(FeedEntry.class);
Join<FeedFeedEntry, Feed> feedJoin = root.join(FeedEntry_.feedRelationships).join(FeedFeedEntry_.feed);
Join<Feed, FeedSubscription> subJoin = feedJoin
.join(Feed_.subscriptions);
Selection<FeedEntry> entryAlias = root.alias("entry");
Selection<FeedSubscription> subAlias = subJoin.alias("subscription");
query.multiselect(entryAlias, subAlias);
List<Predicate> 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]));
orderEntriesBy(query, root, order);
TypedQuery<Tuple> q = em.createQuery(query);
limit(q, offset, limit);
setTimeout(q);
List<Tuple> list = q.getResultList();
List<FeedEntryStatus> 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(subscription.getUser(), subscription, entry);
status.setSubscription(subscription);
status.setRead(true);
}
results.add(status);
}
return lazyLoadContent(includeContent, results);
}
public List<FeedEntryStatus> findUnreadByCategories(
List<FeedCategory> categories, ReadingOrder order,
boolean includeContent) {
return findUnreadByCategories(categories, null, -1, -1, order,
includeContent);
}
public List<FeedEntryStatus> findUnreadByCategories(
List<FeedCategory> categories, Date newerThan, int offset,
int limit, ReadingOrder order, boolean includeContent) {
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
Root<FeedEntryStatus> root = query.from(getType());
List<Predicate> predicates = Lists.newArrayList();
Join<FeedEntryStatus, FeedSubscription> 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(
root.get(FeedEntryStatus_.entryInserted), newerThan));
}
query.where(predicates.toArray(new Predicate[0]));
orderStatusesBy(query, root, order);
TypedQuery<FeedEntryStatus> q = em.createQuery(query);
limit(q, offset, limit);
setTimeout(q);
return lazyLoadContent(includeContent, q.getResultList()); return lazyLoadContent(includeContent, q.getResultList());
} }
@@ -469,13 +319,13 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
return results; return results;
} }
private void orderEntriesBy(CriteriaQuery<?> query, Path<FeedEntry> entryJoin, private void orderEntriesBy(CriteriaQuery<?> query,
ReadingOrder order) { Path<FeedFeedEntry> ffeJoin, ReadingOrder order) {
orderBy(query, entryJoin.get(FeedEntry_.updated), order); orderBy(query, ffeJoin.get(FeedFeedEntry_.entryUpdated), order);
} }
private void orderStatusesBy(CriteriaQuery<?> query, Path<FeedEntryStatus> statusJoin, private void orderStatusesBy(CriteriaQuery<?> query,
ReadingOrder order) { Path<FeedEntryStatus> statusJoin, ReadingOrder order) {
orderBy(query, statusJoin.get(FeedEntryStatus_.entryUpdated), order); orderBy(query, statusJoin.get(FeedEntryStatus_.entryUpdated), order);
} }
@@ -494,27 +344,22 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
setTimeout(query, applicationSettingsService.get().getQueryTimeout()); setTimeout(query, applicationSettingsService.get().getQueryTimeout());
} }
public void markSubscriptionEntries(FeedSubscription subscription, public void markAllEntries(User user, Date olderThan) {
Date olderThan) { List<FeedEntryStatus> statuses = findAllUnread(user, null, -1, -1,
List<FeedEntryStatus> statuses = findUnreadBySubscription(subscription,
null, false); null, false);
markList(statuses, olderThan); markList(statuses, olderThan);
} }
public void markCategoryEntries(User user, List<FeedCategory> categories, public void markSubscriptionEntries(List<FeedSubscription> subscriptions,
Date olderThan) { Date olderThan) {
List<FeedEntryStatus> statuses = findUnreadByCategories(categories, List<FeedEntryStatus> statuses = findUnreadBySubscriptions(
null, false); subscriptions, null, -1, -1, null, false);
markList(statuses, olderThan); markList(statuses, olderThan);
} }
public void markStarredEntries(User user, Date olderThan) { public void markStarredEntries(User user, Date olderThan) {
List<FeedEntryStatus> statuses = findStarred(user, null, false); List<FeedEntryStatus> statuses = findStarred(user, null, -1, -1, null,
markList(statuses, olderThan); false);
}
public void markAllEntries(User user, Date olderThan) {
List<FeedEntryStatus> statuses = findAllUnread(user, null, false);
markList(statuses, olderThan); markList(statuses, olderThan);
} }

View File

@@ -17,7 +17,9 @@ import com.commafeed.backend.model.Feed_;
import com.commafeed.backend.model.Models; import com.commafeed.backend.model.Models;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.commafeed.backend.model.User_; import com.commafeed.backend.model.User_;
import com.google.common.base.Function;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@Stateless @Stateless
public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> { public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
@@ -117,6 +119,27 @@ public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
initRelations(list); initRelations(list);
return list; return list;
} }
public List<FeedSubscription> findByCategories(User user,
List<FeedCategory> categories) {
List<Long> categoryIds = Lists.transform(categories,
new Function<FeedCategory, Long>() {
@Override
public Long apply(FeedCategory input) {
return input.getId();
}
});
List<FeedSubscription> subscriptions = Lists.newArrayList();
for (FeedSubscription sub : findAll(user)) {
if (sub.getCategory() != null
&& categoryIds.contains(sub.getCategory().getId())) {
subscriptions.add(sub);
}
}
return subscriptions;
}
private void initRelations(List<FeedSubscription> list) { private void initRelations(List<FeedSubscription> list) {
for (FeedSubscription sub : list) { for (FeedSubscription sub : list) {

View File

@@ -14,6 +14,7 @@ import javax.persistence.OneToOne;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import javax.persistence.Transient;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
@@ -53,6 +54,12 @@ public class FeedEntry extends AbstractModel {
@OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE) @OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE)
private Set<FeedEntryStatus> statuses; private Set<FeedEntryStatus> statuses;
/**
* useful placeholder for the subscription, not persisted
*/
@Transient
private FeedSubscription subscription;
public String getGuid() { public String getGuid() {
return guid; return guid;
} }
@@ -125,4 +132,12 @@ public class FeedEntry extends AbstractModel {
this.feedRelationships = feedRelationships; this.feedRelationships = feedRelationships;
} }
public FeedSubscription getSubscription() {
return subscription;
}
public void setSubscription(FeedSubscription subscription) {
this.subscription = subscription;
}
} }

View File

@@ -1,6 +1,7 @@
package com.commafeed.backend.model; package com.commafeed.backend.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date;
import javax.persistence.Cacheable; import javax.persistence.Cacheable;
import javax.persistence.Entity; import javax.persistence.Entity;
@@ -9,6 +10,8 @@ import javax.persistence.Id;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.Cache; import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.CacheConcurrencyStrategy;
@@ -30,6 +33,9 @@ public class FeedFeedEntry implements Serializable {
@JoinColumn(name = "FEEDENTRY_ID") @JoinColumn(name = "FEEDENTRY_ID")
private FeedEntry entry; private FeedEntry entry;
@Temporal(TemporalType.TIMESTAMP)
private Date entryUpdated;
public FeedFeedEntry() { public FeedFeedEntry() {
} }
@@ -37,6 +43,7 @@ public class FeedFeedEntry implements Serializable {
public FeedFeedEntry(Feed feed, FeedEntry entry) { public FeedFeedEntry(Feed feed, FeedEntry entry) {
this.feed = feed; this.feed = feed;
this.entry = entry; this.entry = entry;
this.entryUpdated = entry.getUpdated();
} }
public Feed getFeed() { public Feed getFeed() {
@@ -55,4 +62,12 @@ public class FeedFeedEntry implements Serializable {
this.entry = entry; this.entry = entry;
} }
public Date getEntryUpdated() {
return entryUpdated;
}
public void setEntryUpdated(Date entryUpdated) {
this.entryUpdated = entryUpdated;
}
} }

View File

@@ -3,6 +3,7 @@ package com.commafeed.backend.services;
import javax.ejb.Stateless; import javax.ejb.Stateless;
import javax.inject.Inject; import javax.inject.Inject;
import com.commafeed.backend.dao.FeedEntryDAO;
import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntry;
@@ -18,6 +19,9 @@ public class FeedEntryService {
@Inject @Inject
FeedSubscriptionDAO feedSubscriptionDAO; FeedSubscriptionDAO feedSubscriptionDAO;
@Inject
FeedEntryDAO feedEntryDAO;
public void markEntry(User user, Long entryId, Long subscriptionId, public void markEntry(User user, Long entryId, Long subscriptionId,
boolean read) { boolean read) {
@@ -27,13 +31,15 @@ public class FeedEntryService {
return; return;
} }
FeedEntry entry = new FeedEntry(); FeedEntry entry = feedEntryDAO.findById(entryId);
entry.setId(entryId); if (entry == null) {
return;
}
FeedEntryStatus status = feedEntryStatusDAO.findByEntry(entry, sub); FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry);
if (read) { if (read) {
if (status != null) { if (status.getId() != null) {
if (status.isStarred()) { if (status.isStarred()) {
status.setRead(true); status.setRead(true);
feedEntryStatusDAO.saveOrUpdate(status); feedEntryStatusDAO.saveOrUpdate(status);
@@ -42,7 +48,7 @@ public class FeedEntryService {
} }
} }
} else { } else {
if (status == null) { if (status.getId() == null) {
status = new FeedEntryStatus(user, sub, entry); status = new FeedEntryStatus(user, sub, entry);
status.setSubscription(sub); status.setSubscription(sub);
} }
@@ -61,13 +67,15 @@ public class FeedEntryService {
return; return;
} }
FeedEntry entry = new FeedEntry(); FeedEntry entry = feedEntryDAO.findById(entryId);
entry.setId(entryId); if (entry == null) {
return;
}
FeedEntryStatus status = feedEntryStatusDAO.findByEntry(entry, sub); FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry);
if (!starred) { if (!starred) {
if (status != null) { if (status.getId() != null) {
if (!status.isRead()) { if (!status.isRead()) {
status.setStarred(false); status.setStarred(false);
feedEntryStatusDAO.saveOrUpdate(status); feedEntryStatusDAO.saveOrUpdate(status);
@@ -76,7 +84,7 @@ public class FeedEntryService {
} }
} }
} else { } else {
if (status == null) { if (status.getId() == null) {
status = new FeedEntryStatus(user, sub, entry); status = new FeedEntryStatus(user, sub, entry);
status.setSubscription(sub); status.setSubscription(sub);
status.setRead(true); status.setRead(true);

View File

@@ -12,8 +12,10 @@ import org.apache.wicket.request.mapper.parameter.PageParameters;
import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.FeedCategoryDAO;
import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.model.FeedCategory; import com.commafeed.backend.model.FeedCategory;
import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedEntryStatus;
import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User; import com.commafeed.backend.model.User;
import com.commafeed.backend.model.UserRole.Role; import com.commafeed.backend.model.UserRole.Role;
import com.commafeed.backend.model.UserSettings.ReadingOrder; import com.commafeed.backend.model.UserSettings.ReadingOrder;
@@ -35,13 +37,16 @@ public class NextUnreadRedirectPage extends WebPage {
@Inject @Inject
FeedEntryStatusDAO feedEntryStatusDAO; FeedEntryStatusDAO feedEntryStatusDAO;
@Inject
FeedSubscriptionDAO feedSubscriptionDAO;
public NextUnreadRedirectPage(PageParameters params) { public NextUnreadRedirectPage(PageParameters params) {
String categoryId = params.get(PARAM_CATEGORYID).toString(); String categoryId = params.get(PARAM_CATEGORYID).toString();
String orderParam = params.get(PARAM_READINGORDER).toString(); String orderParam = params.get(PARAM_READINGORDER).toString();
User user = CommaFeedSession.get().getUser(); User user = CommaFeedSession.get().getUser();
ReadingOrder order = ReadingOrder.desc; ReadingOrder order = ReadingOrder.desc;
if (StringUtils.equals(orderParam, "asc")) { if (StringUtils.equals(orderParam, "asc")) {
order = ReadingOrder.asc; order = ReadingOrder.asc;
} }
@@ -57,8 +62,10 @@ public class NextUnreadRedirectPage extends WebPage {
if (category != null) { if (category != null) {
List<FeedCategory> children = feedCategoryDAO List<FeedCategory> children = feedCategoryDAO
.findAllChildrenCategories(user, category); .findAllChildrenCategories(user, category);
statuses = feedEntryStatusDAO.findUnreadByCategories(children, List<FeedSubscription> subscriptions = feedSubscriptionDAO
null, 0, 1, order, true); .findByCategories(user, children);
statuses = feedEntryStatusDAO.findUnreadBySubscriptions(
subscriptions, null, 0, 1, order, true);
} }
} }

View File

@@ -107,12 +107,14 @@ public class CategoryREST extends AbstractResourceREST {
if (ALL.equals(id)) { if (ALL.equals(id)) {
entries.setName("All"); entries.setName("All");
List<FeedEntryStatus> list = null; List<FeedEntryStatus> list = null;
List<FeedSubscription> subscriptions = feedSubscriptionDAO
.findAll(getUser());
if (unreadOnly) { if (unreadOnly) {
list = feedEntryStatusDAO.findAllUnread(getUser(), list = feedEntryStatusDAO.findAllUnread(getUser(),
newerThanDate, offset, limit + 1, order, true); newerThanDate, offset, limit + 1, order, true);
} else { } else {
list = feedEntryStatusDAO.findAll(getUser(), newerThanDate, list = feedEntryStatusDAO.findBySubscriptions(subscriptions,
offset, limit + 1, order, true); null, newerThanDate, offset, limit + 1, order, true);
} }
for (FeedEntryStatus status : list) { for (FeedEntryStatus status : list) {
entries.getEntries().add( entries.getEntries().add(
@@ -132,20 +134,20 @@ public class CategoryREST extends AbstractResourceREST {
.get().isImageProxyEnabled())); .get().isImageProxyEnabled()));
} }
} else { } else {
FeedCategory feedCategory = feedCategoryDAO.findById(getUser(), FeedCategory parent = feedCategoryDAO.findById(getUser(),
Long.valueOf(id)); Long.valueOf(id));
if (feedCategory != null) { if (parent != null) {
List<FeedCategory> childrenCategories = feedCategoryDAO List<FeedCategory> categories = feedCategoryDAO
.findAllChildrenCategories(getUser(), feedCategory); .findAllChildrenCategories(getUser(), parent);
List<FeedSubscription> subs = feedSubscriptionDAO
.findByCategories(getUser(), categories);
List<FeedEntryStatus> list = null; List<FeedEntryStatus> list = null;
if (unreadOnly) { if (unreadOnly) {
list = feedEntryStatusDAO.findUnreadByCategories( list = feedEntryStatusDAO.findUnreadBySubscriptions(subs,
childrenCategories, newerThanDate, offset, newerThanDate, offset, limit + 1, order, true);
limit + 1, order, true);
} else { } else {
list = feedEntryStatusDAO.findByCategories( list = feedEntryStatusDAO.findBySubscriptions(subs, null,
childrenCategories, newerThanDate, offset, newerThanDate, offset, limit + 1, order, true);
limit + 1, order, true);
} }
for (FeedEntryStatus status : list) { for (FeedEntryStatus status : list) {
entries.getEntries().add( entries.getEntries().add(
@@ -154,7 +156,7 @@ public class CategoryREST extends AbstractResourceREST {
applicationSettingsService.get() applicationSettingsService.get()
.isImageProxyEnabled())); .isImageProxyEnabled()));
} }
entries.setName(feedCategory.getName()); entries.setName(parent.getName());
} }
} }
@@ -227,13 +229,13 @@ public class CategoryREST extends AbstractResourceREST {
} else if (STARRED.equals(req.getId())) { } else if (STARRED.equals(req.getId())) {
feedEntryStatusDAO.markStarredEntries(getUser(), olderThan); feedEntryStatusDAO.markStarredEntries(getUser(), olderThan);
} else { } else {
FeedCategory parent = feedCategoryDAO.findById(getUser(),
Long.valueOf(req.getId()));
List<FeedCategory> categories = feedCategoryDAO List<FeedCategory> categories = feedCategoryDAO
.findAllChildrenCategories( .findAllChildrenCategories(getUser(), parent);
getUser(), List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(
feedCategoryDAO.findById(getUser(), getUser(), categories);
Long.valueOf(req.getId()))); feedEntryStatusDAO.markSubscriptionEntries(subs, olderThan);
feedEntryStatusDAO.markCategoryEntries(getUser(), categories,
olderThan);
} }
cache.invalidateUserData(getUser()); cache.invalidateUserData(getUser());
return Response.ok(Status.OK).build(); return Response.ok(Status.OK).build();

View File

@@ -15,7 +15,10 @@ import org.apache.commons.lang.StringUtils;
import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.cache.CacheService;
import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedEntryStatus;
import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.UserSettings.ReadingOrder;
import com.commafeed.backend.services.FeedEntryService; import com.commafeed.backend.services.FeedEntryService;
import com.commafeed.frontend.model.Entries; import com.commafeed.frontend.model.Entries;
import com.commafeed.frontend.model.Entry; import com.commafeed.frontend.model.Entry;
@@ -38,6 +41,9 @@ public class EntryREST extends AbstractResourceREST {
@Inject @Inject
FeedEntryStatusDAO feedEntryStatusDAO; FeedEntryStatusDAO feedEntryStatusDAO;
@Inject
FeedSubscriptionDAO feedSubscriptionDAO;
@Inject @Inject
CacheService cache; CacheService cache;
@@ -67,7 +73,7 @@ public class EntryREST extends AbstractResourceREST {
for (MarkRequest r : req.getRequests()) { for (MarkRequest r : req.getRequests()) {
markFeedEntry(r); markFeedEntry(r);
} }
return Response.ok(Status.OK).build(); return Response.ok(Status.OK).build();
} }
@@ -100,8 +106,10 @@ public class EntryREST extends AbstractResourceREST {
Entries entries = new Entries(); Entries entries = new Entries();
List<Entry> list = Lists.newArrayList(); List<Entry> list = Lists.newArrayList();
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(getUser());
List<FeedEntryStatus> entriesStatus = feedEntryStatusDAO List<FeedEntryStatus> entriesStatus = feedEntryStatusDAO
.findByKeywords(getUser(), keywords, offset, limit); .findBySubscriptions(subs, keywords, null, offset, limit,
ReadingOrder.desc, true);
for (FeedEntryStatus status : entriesStatus) { for (FeedEntryStatus status : entriesStatus) {
list.add(Entry.build(status, applicationSettingsService.get() list.add(Entry.build(status, applicationSettingsService.get()
.getPublicUrl(), applicationSettingsService.get() .getPublicUrl(), applicationSettingsService.get()

View File

@@ -2,6 +2,7 @@ package com.commafeed.frontend.rest.resources;
import java.io.StringWriter; import java.io.StringWriter;
import java.net.URI; import java.net.URI;
import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
@@ -153,12 +154,13 @@ public class FeedREST extends AbstractResourceREST {
List<FeedEntryStatus> list = null; List<FeedEntryStatus> list = null;
if (unreadOnly) { if (unreadOnly) {
list = feedEntryStatusDAO.findUnreadBySubscription( list = feedEntryStatusDAO.findUnreadBySubscriptions(
subscription, newerThanDate, offset, limit + 1, order, Arrays.asList(subscription), newerThanDate, offset,
true); limit + 1, order, true);
} else { } else {
list = feedEntryStatusDAO.findBySubscription(subscription, list = feedEntryStatusDAO.findBySubscriptions(
newerThanDate, offset, limit + 1, order, true); Arrays.asList(subscription), null, newerThanDate,
offset, limit + 1, order, true);
} }
for (FeedEntryStatus status : list) { for (FeedEntryStatus status : list) {
@@ -292,7 +294,8 @@ public class FeedREST extends AbstractResourceREST {
FeedSubscription subscription = feedSubscriptionDAO.findById(getUser(), FeedSubscription subscription = feedSubscriptionDAO.findById(getUser(),
Long.valueOf(req.getId())); Long.valueOf(req.getId()));
if (subscription != null) { if (subscription != null) {
feedEntryStatusDAO.markSubscriptionEntries(subscription, olderThan); feedEntryStatusDAO.markSubscriptionEntries(
Arrays.asList(subscription), olderThan);
} }
cache.invalidateUserData(getUser()); cache.invalidateUserData(getUser());
return Response.ok(Status.OK).build(); return Response.ok(Status.OK).build();

View File

@@ -321,5 +321,22 @@
<changeSet author="athou" id="drop-fes-index-2"> <changeSet author="athou" id="drop-fes-index-2">
<dropIndex tableName="FEEDENTRYSTATUSES" indexName="sub_entry_read_index" /> <dropIndex tableName="FEEDENTRYSTATUSES" indexName="sub_entry_read_index" />
</changeSet> </changeSet>
<changeSet author="athou" id="add-entry-updated-to-ffe">
<addColumn tableName="FEED_FEEDENTRIES">
<column name="entryUpdated" type="DATETIME"></column>
</addColumn>
</changeSet>
<changeSet author="athou" id="populate-entry-dates">
<sql>update FEED_FEEDENTRIES SET entryUpdated = (select e.updated from FEEDENTRIES e where e.id = FEED_FEEDENTRIES.feedentry_id)</sql>
</changeSet>
<changeSet author="athou" id="create-ffe-entry-updated-index">
<createIndex tableName="FEED_FEEDENTRIES" indexName="feed_updated_index">
<column name="FEED_ID"></column>
<column name="entryUpdated"></column>
</createIndex>
</changeSet>
</databaseChangeLog> </databaseChangeLog>