refactored the way entries and statuses are fetched

This commit is contained in:
Athou
2013-07-19 11:17:19 +02:00
parent af274f797f
commit 079345b2e0
11 changed files with 362 additions and 395 deletions

View File

@@ -0,0 +1,58 @@
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 (size() == maxSize) {
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;
}
@SuppressWarnings("unchecked")
public List<E> asList() {
return (List<E>) Lists.newArrayList(toArray());
}
}

View File

@@ -1,5 +1,6 @@
package com.commafeed.backend.dao;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -7,22 +8,18 @@ import java.util.Map;
import javax.ejb.Stateless;
import javax.inject.Inject;
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 org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedCategory;
import com.commafeed.backend.FixedSizeSortedSet;
import com.commafeed.backend.model.FeedEntry;
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.FeedSubscription;
import com.commafeed.backend.model.FeedSubscription_;
import com.commafeed.backend.model.Feed_;
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.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -49,10 +43,38 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
protected static Logger log = LoggerFactory
.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
ApplicationSettingsService applicationSettingsService;
public FeedEntryStatus findByEntry(FeedEntry entry, FeedSubscription sub) {
public FeedEntryStatus getStatus(FeedSubscription sub, FeedEntry entry) {
CriteriaQuery<FeedEntryStatus> query = builder.createQuery(getType());
Root<FeedEntryStatus> root = query.from(getType());
@@ -64,108 +86,12 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
query.where(p1, p2);
List<FeedEntryStatus> statuses = em.createQuery(query).getResultList();
return Iterables.getFirst(statuses, null);
}
public List<FeedEntryStatus> findByEntries(List<FeedEntry> entries,
FeedSubscription sub) {
List<FeedEntryStatus> results = Lists.newArrayList();
if (CollectionUtils.isEmpty(entries)) {
return results;
FeedEntryStatus status = Iterables.getFirst(statuses, null);
if (status == null) {
status = new FeedEntryStatus(sub.getUser(), sub, entry);
status.setRead(true);
}
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);
return status;
}
public List<FeedEntryStatus> findStarred(User user, Date newerThan,
@@ -176,8 +102,7 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
List<Predicate> predicates = Lists.newArrayList();
predicates
.add(builder.equal(root.get(FeedEntryStatus_.user), user));
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]));
@@ -194,251 +119,150 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
return lazyLoadContent(includeContent, q.getResultList());
}
public List<FeedEntryStatus> findAll(User user, Date newerThan, int offset,
int limit, ReadingOrder order, boolean includeContent) {
public List<FeedEntryStatus> findBySubscriptions(
List<FeedSubscription> subscriptions, String keywords,
Date newerThan, int offset, int limit, ReadingOrder order,
boolean includeContent) {
CriteriaQuery<Tuple> query = builder.createTupleQuery();
Root<FeedEntry> root = query.from(FeedEntry.class);
int capacity = offset + limit;
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);
Join<Feed, FeedSubscription> subJoin = feedJoin
.join(Feed_.subscriptions);
List<Predicate> predicates = Lists.newArrayList();
predicates.add(builder.equal(ffeJoin.get(FeedFeedEntry_.feed),
sub.getFeed()));
Selection<FeedEntry> entryAlias = root.alias("entry");
Selection<FeedSubscription> subAlias = subJoin.alias("subscription");
query.multiselect(entryAlias, subAlias);
if (newerThan != null) {
predicates.add(builder.greaterThanOrEqualTo(
root.get(FeedEntry_.inserted), newerThan));
}
List<Predicate> predicates = Lists.newArrayList();
if (keywords != null) {
Join<FeedEntry, FeedEntryContent> contentJoin = root
.join(FeedEntry_.content);
predicates
.add(builder.equal(subJoin.get(FeedSubscription_.user), user));
String joinedKeywords = StringUtils.join(keywords.toLowerCase()
.split(" "), "%");
joinedKeywords = "%" + joinedKeywords + "%";
if (newerThan != null) {
predicates.add(builder.greaterThanOrEqualTo(
root.get(FeedEntry_.inserted), newerThan));
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()) {
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]));
orderEntriesBy(query, root, order);
List<FeedEntry> entries = set.asList();
int size = entries.size();
if (size < offset) {
return Lists.newArrayList();
}
TypedQuery<Tuple> q = em.createQuery(query);
limit(q, offset, limit);
setTimeout(q);
entries = entries.subList(Math.max(offset, 0), size);
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);
for (FeedEntry entry : entries) {
FeedSubscription subscription = entry.getSubscription();
results.add(getStatus(subscription, entry));
}
return lazyLoadContent(includeContent, results);
}
public List<FeedEntryStatus> findAllUnread(User user, ReadingOrder order,
boolean includeContent) {
return findAllUnread(user, null, -1, -1, order, includeContent);
}
public List<FeedEntryStatus> findAllUnread(User user, 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_.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<FeedEntryStatus> q = em.createQuery(query);
limit(q, offset, limit);
setTimeout(q);
return lazyLoadContent(includeContent, q.getResultList());
}
public List<FeedEntryStatus> findBySubscription(
FeedSubscription subscription, Date newerThan, int offset,
public List<FeedEntryStatus> findUnreadBySubscriptions(
List<FeedSubscription> subscriptions, Date newerThan, int offset,
int limit, ReadingOrder order, boolean includeContent) {
CriteriaQuery<FeedEntry> query = builder.createQuery(FeedEntry.class);
Root<FeedEntry> root = query.from(FeedEntry.class);
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());
Join<FeedEntry, FeedFeedEntry> ffeJoin = root.join(FeedEntry_.feedRelationships);
List<Predicate> predicates = Lists.newArrayList();
List<Predicate> predicates = Lists.newArrayList();
predicates.add(builder.equal(
root.get(FeedEntryStatus_.subscription), sub));
predicates.add(builder.isFalse(root.get(FeedEntryStatus_.read)));
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);
if (newerThan != null) {
predicates.add(builder.greaterThanOrEqualTo(
root.get(FeedEntryStatus_.entryInserted), newerThan));
}
results.add(status);
if (order != null && !set.isEmpty()) {
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);
}
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));
List<FeedEntryStatus> entries = set.asList();
int size = entries.size();
if (size < offset) {
return Lists.newArrayList();
}
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());
entries = entries.subList(Math.max(offset, 0), size);
return lazyLoadContent(includeContent, entries);
}
/**
@@ -469,13 +293,13 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
return results;
}
private void orderEntriesBy(CriteriaQuery<?> query, Path<FeedEntry> entryJoin,
ReadingOrder order) {
orderBy(query, entryJoin.get(FeedEntry_.updated), order);
private void orderEntriesBy(CriteriaQuery<?> query,
Path<FeedFeedEntry> ffeJoin, ReadingOrder order) {
orderBy(query, ffeJoin.get(FeedFeedEntry_.entryUpdated), order);
}
private void orderStatusesBy(CriteriaQuery<?> query, Path<FeedEntryStatus> statusJoin,
ReadingOrder order) {
private void orderStatusesBy(CriteriaQuery<?> query,
Path<FeedEntryStatus> statusJoin, ReadingOrder order) {
orderBy(query, statusJoin.get(FeedEntryStatus_.entryUpdated), order);
}
@@ -494,27 +318,16 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
setTimeout(query, applicationSettingsService.get().getQueryTimeout());
}
public void markSubscriptionEntries(FeedSubscription subscription,
public void markSubscriptionEntries(List<FeedSubscription> subscriptions,
Date olderThan) {
List<FeedEntryStatus> statuses = findUnreadBySubscription(subscription,
null, false);
markList(statuses, olderThan);
}
public void markCategoryEntries(User user, List<FeedCategory> categories,
Date olderThan) {
List<FeedEntryStatus> statuses = findUnreadByCategories(categories,
null, false);
List<FeedEntryStatus> statuses = findUnreadBySubscriptions(
subscriptions, null, -1, -1, null, false);
markList(statuses, olderThan);
}
public void markStarredEntries(User user, Date olderThan) {
List<FeedEntryStatus> statuses = findStarred(user, null, false);
markList(statuses, olderThan);
}
public void markAllEntries(User user, Date olderThan) {
List<FeedEntryStatus> statuses = findAllUnread(user, null, false);
List<FeedEntryStatus> statuses = findStarred(user, null, -1, -1, null,
false);
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.User;
import com.commafeed.backend.model.User_;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@Stateless
public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
@@ -117,6 +119,27 @@ public class FeedSubscriptionDAO extends GenericDAO<FeedSubscription> {
initRelations(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) {
for (FeedSubscription sub : list) {

View File

@@ -14,6 +14,7 @@ import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
@@ -53,6 +54,12 @@ public class FeedEntry extends AbstractModel {
@OneToMany(mappedBy = "entry", cascade = CascadeType.REMOVE)
private Set<FeedEntryStatus> statuses;
/**
* useful placeholder for the subscription, not persisted
*/
@Transient
private FeedSubscription subscription;
public String getGuid() {
return guid;
}
@@ -125,4 +132,12 @@ public class FeedEntry extends AbstractModel {
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;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Cacheable;
import javax.persistence.Entity;
@@ -9,6 +10,8 @@ import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
@@ -30,6 +33,9 @@ public class FeedFeedEntry implements Serializable {
@JoinColumn(name = "FEEDENTRY_ID")
private FeedEntry entry;
@Temporal(TemporalType.TIMESTAMP)
private Date entryUpdated;
public FeedFeedEntry() {
}
@@ -37,6 +43,7 @@ public class FeedFeedEntry implements Serializable {
public FeedFeedEntry(Feed feed, FeedEntry entry) {
this.feed = feed;
this.entry = entry;
this.entryUpdated = entry.getUpdated();
}
public Feed getFeed() {
@@ -55,4 +62,12 @@ public class FeedFeedEntry implements Serializable {
this.entry = entry;
}
public Date getEntryUpdated() {
return entryUpdated;
}
public void setEntryUpdated(Date entryUpdated) {
this.entryUpdated = entryUpdated;
}
}

View File

@@ -30,10 +30,10 @@ public class FeedEntryService {
FeedEntry entry = new FeedEntry();
entry.setId(entryId);
FeedEntryStatus status = feedEntryStatusDAO.findByEntry(entry, sub);
FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry);
if (read) {
if (status != null) {
if (status.getId() != null) {
if (status.isStarred()) {
status.setRead(true);
feedEntryStatusDAO.saveOrUpdate(status);
@@ -42,7 +42,7 @@ public class FeedEntryService {
}
}
} else {
if (status == null) {
if (status.getId() == null) {
status = new FeedEntryStatus(user, sub, entry);
status.setSubscription(sub);
}
@@ -64,10 +64,10 @@ public class FeedEntryService {
FeedEntry entry = new FeedEntry();
entry.setId(entryId);
FeedEntryStatus status = feedEntryStatusDAO.findByEntry(entry, sub);
FeedEntryStatus status = feedEntryStatusDAO.getStatus(sub, entry);
if (!starred) {
if (status != null) {
if (status.getId() != null) {
if (!status.isRead()) {
status.setStarred(false);
feedEntryStatusDAO.saveOrUpdate(status);
@@ -76,7 +76,7 @@ public class FeedEntryService {
}
}
} else {
if (status == null) {
if (status.getId() == null) {
status = new FeedEntryStatus(user, sub, entry);
status.setSubscription(sub);
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.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO;
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.UserRole.Role;
import com.commafeed.backend.model.UserSettings.ReadingOrder;
@@ -35,13 +37,16 @@ public class NextUnreadRedirectPage extends WebPage {
@Inject
FeedEntryStatusDAO feedEntryStatusDAO;
@Inject
FeedSubscriptionDAO feedSubscriptionDAO;
public NextUnreadRedirectPage(PageParameters params) {
String categoryId = params.get(PARAM_CATEGORYID).toString();
String orderParam = params.get(PARAM_READINGORDER).toString();
User user = CommaFeedSession.get().getUser();
ReadingOrder order = ReadingOrder.desc;
if (StringUtils.equals(orderParam, "asc")) {
order = ReadingOrder.asc;
}
@@ -49,16 +54,20 @@ public class NextUnreadRedirectPage extends WebPage {
List<FeedEntryStatus> statuses = null;
if (StringUtils.isBlank(categoryId)
|| CategoryREST.ALL.equals(categoryId)) {
statuses = feedEntryStatusDAO.findAllUnread(user, null, 0, 1,
order, true);
List<FeedSubscription> subscriptions = feedSubscriptionDAO
.findAll(user);
statuses = feedEntryStatusDAO.findBySubscriptions(subscriptions,
null, null, 0, 1, order, true);
} else {
FeedCategory category = feedCategoryDAO.findById(user,
Long.valueOf(categoryId));
if (category != null) {
List<FeedCategory> children = feedCategoryDAO
.findAllChildrenCategories(user, category);
statuses = feedEntryStatusDAO.findUnreadByCategories(children,
null, 0, 1, order, true);
List<FeedSubscription> subscriptions = feedSubscriptionDAO
.findByCategories(user, children);
statuses = feedEntryStatusDAO.findUnreadBySubscriptions(
subscriptions, null, 0, 1, order, true);
}
}

View File

@@ -107,12 +107,15 @@ public class CategoryREST extends AbstractResourceREST {
if (ALL.equals(id)) {
entries.setName("All");
List<FeedEntryStatus> list = null;
List<FeedSubscription> subscriptions = feedSubscriptionDAO
.findAll(getUser());
if (unreadOnly) {
list = feedEntryStatusDAO.findAllUnread(getUser(),
newerThanDate, offset, limit + 1, order, true);
list = feedEntryStatusDAO.findUnreadBySubscriptions(
subscriptions, newerThanDate, offset, limit + 1, order,
true);
} else {
list = feedEntryStatusDAO.findAll(getUser(), newerThanDate,
offset, limit + 1, order, true);
list = feedEntryStatusDAO.findBySubscriptions(subscriptions,
null, newerThanDate, offset, limit + 1, order, true);
}
for (FeedEntryStatus status : list) {
entries.getEntries().add(
@@ -132,20 +135,20 @@ public class CategoryREST extends AbstractResourceREST {
.get().isImageProxyEnabled()));
}
} else {
FeedCategory feedCategory = feedCategoryDAO.findById(getUser(),
FeedCategory parent = feedCategoryDAO.findById(getUser(),
Long.valueOf(id));
if (feedCategory != null) {
List<FeedCategory> childrenCategories = feedCategoryDAO
.findAllChildrenCategories(getUser(), feedCategory);
if (parent != null) {
List<FeedCategory> categories = feedCategoryDAO
.findAllChildrenCategories(getUser(), parent);
List<FeedSubscription> subs = feedSubscriptionDAO
.findByCategories(getUser(), categories);
List<FeedEntryStatus> list = null;
if (unreadOnly) {
list = feedEntryStatusDAO.findUnreadByCategories(
childrenCategories, newerThanDate, offset,
limit + 1, order, true);
list = feedEntryStatusDAO.findUnreadBySubscriptions(subs,
newerThanDate, offset, limit + 1, order, true);
} else {
list = feedEntryStatusDAO.findByCategories(
childrenCategories, newerThanDate, offset,
limit + 1, order, true);
list = feedEntryStatusDAO.findBySubscriptions(subs, null,
newerThanDate, offset, limit + 1, order, true);
}
for (FeedEntryStatus status : list) {
entries.getEntries().add(
@@ -154,7 +157,7 @@ public class CategoryREST extends AbstractResourceREST {
applicationSettingsService.get()
.isImageProxyEnabled()));
}
entries.setName(feedCategory.getName());
entries.setName(parent.getName());
}
}
@@ -223,17 +226,20 @@ public class CategoryREST extends AbstractResourceREST {
req.getOlderThan());
if (ALL.equals(req.getId())) {
feedEntryStatusDAO.markAllEntries(getUser(), olderThan);
List<FeedSubscription> subscriptions = feedSubscriptionDAO
.findAll(getUser());
feedEntryStatusDAO
.markSubscriptionEntries(subscriptions, olderThan);
} else if (STARRED.equals(req.getId())) {
feedEntryStatusDAO.markStarredEntries(getUser(), olderThan);
} else {
FeedCategory parent = feedCategoryDAO.findById(getUser(),
Long.valueOf(req.getId()));
List<FeedCategory> categories = feedCategoryDAO
.findAllChildrenCategories(
getUser(),
feedCategoryDAO.findById(getUser(),
Long.valueOf(req.getId())));
feedEntryStatusDAO.markCategoryEntries(getUser(), categories,
olderThan);
.findAllChildrenCategories(getUser(), parent);
List<FeedSubscription> subs = feedSubscriptionDAO.findByCategories(
getUser(), categories);
feedEntryStatusDAO.markSubscriptionEntries(subs, olderThan);
}
cache.invalidateUserData(getUser());
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.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO;
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.frontend.model.Entries;
import com.commafeed.frontend.model.Entry;
@@ -38,6 +41,9 @@ public class EntryREST extends AbstractResourceREST {
@Inject
FeedEntryStatusDAO feedEntryStatusDAO;
@Inject
FeedSubscriptionDAO feedSubscriptionDAO;
@Inject
CacheService cache;
@@ -67,7 +73,7 @@ public class EntryREST extends AbstractResourceREST {
for (MarkRequest r : req.getRequests()) {
markFeedEntry(r);
}
return Response.ok(Status.OK).build();
}
@@ -100,8 +106,10 @@ public class EntryREST extends AbstractResourceREST {
Entries entries = new Entries();
List<Entry> list = Lists.newArrayList();
List<FeedSubscription> subs = feedSubscriptionDAO.findAll(getUser());
List<FeedEntryStatus> entriesStatus = feedEntryStatusDAO
.findByKeywords(getUser(), keywords, offset, limit);
.findBySubscriptions(subs, keywords, null, offset, limit,
ReadingOrder.desc, true);
for (FeedEntryStatus status : entriesStatus) {
list.add(Entry.build(status, applicationSettingsService.get()
.getPublicUrl(), applicationSettingsService.get()

View File

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

View File

@@ -321,5 +321,22 @@
<changeSet author="athou" id="drop-fes-index-2">
<dropIndex tableName="FEEDENTRYSTATUSES" indexName="sub_entry_read_index" />
</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>