diff --git a/README.md b/README.md
index 51d545cc..ba665121 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,12 @@ Deploy on your own server (using TomEE, a lightweight JavaEE6 container based on
[Safari extension](https://github.com/Athou/commafeed-safari)
+Warning - updating from version 1.0.0
+-------------------------------------
+If you're updating from version 1.0.0, feed history will be deleted. See why [here](https://www.commafeed.com/announcement/20130725.html).
+
+The last commit with no data loss has been [tagged](https://github.com/Athou/commafeed/tree/1.0.0).
+
Deployment on OpenShift
-----------------------
diff --git a/dev/EclipseCodeFormatter.xml b/dev/EclipseCodeFormatter.xml
new file mode 100644
index 00000000..2077b6df
--- /dev/null
+++ b/dev/EclipseCodeFormatter.xml
@@ -0,0 +1,283 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
index e00dd03d..76dc29e7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.commafeed
commafeed
- 1.0.0
+ 1.2.0
war
CommaFeed
diff --git a/src/main/java/com/commafeed/backend/DatabaseCleaner.java b/src/main/java/com/commafeed/backend/DatabaseCleaner.java
index fc5c036d..b853eabf 100644
--- a/src/main/java/com/commafeed/backend/DatabaseCleaner.java
+++ b/src/main/java/com/commafeed/backend/DatabaseCleaner.java
@@ -1,6 +1,7 @@
package com.commafeed.backend;
import java.util.Calendar;
+import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -10,7 +11,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.commafeed.backend.dao.FeedDAO;
+import com.commafeed.backend.dao.FeedEntryContentDAO;
import com.commafeed.backend.dao.FeedEntryDAO;
+import com.commafeed.backend.dao.FeedEntryStatusDAO;
import com.commafeed.backend.dao.FeedSubscriptionDAO;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedSubscription;
@@ -29,6 +32,12 @@ public class DatabaseCleaner {
@Inject
FeedSubscriptionDAO feedSubscriptionDAO;
+ @Inject
+ FeedEntryContentDAO feedEntryContentDAO;
+
+ @Inject
+ FeedEntryStatusDAO feedEntryStatusDAO;
+
@Inject
ApplicationSettingsService applicationSettingsService;
@@ -45,16 +54,16 @@ public class DatabaseCleaner {
return total;
}
- public long cleanEntriesWithoutFeeds() {
+ public long cleanContentsWithoutEntries() {
long total = 0;
int deleted = -1;
do {
- deleted = feedEntryDAO.deleteWithoutFeeds(100);
+ deleted = feedEntryContentDAO.deleteWithoutEntries(10);
total += deleted;
- log.info("removed {} entries without feeds", total);
+ log.info("removed {} feeds without subscriptions", total);
} while (deleted != 0);
- log.info("cleanup done: {} entries without feeds deleted", total);
+ log.info("cleanup done: {} feeds without subscriptions deleted", total);
return total;
}
@@ -88,4 +97,10 @@ public class DatabaseCleaner {
}
feedDAO.saveOrUpdate(into);
}
+
+ public void cleanStatusesOlderThan(Date olderThan) {
+ log.info("cleaning old read statuses");
+ int deleted = feedEntryStatusDAO.deleteOldStatuses(olderThan);
+ log.info("cleaned {} read statuses", deleted);
+ }
}
diff --git a/src/main/java/com/commafeed/backend/DatabaseUpdater.java b/src/main/java/com/commafeed/backend/DatabaseUpdater.java
index a4240b69..4ed458e3 100644
--- a/src/main/java/com/commafeed/backend/DatabaseUpdater.java
+++ b/src/main/java/com/commafeed/backend/DatabaseUpdater.java
@@ -33,18 +33,14 @@ public class DatabaseUpdater {
try {
Thread currentThread = Thread.currentThread();
ClassLoader classLoader = currentThread.getContextClassLoader();
- ResourceAccessor accessor = new ClassLoaderResourceAccessor(
- classLoader);
+ ResourceAccessor accessor = new ClassLoaderResourceAccessor(classLoader);
context = new InitialContext();
- DataSource dataSource = (DataSource) context
- .lookup(datasourceName);
+ DataSource dataSource = (DataSource) context.lookup(datasourceName);
connection = dataSource.getConnection();
JdbcConnection jdbcConnection = new JdbcConnection(connection);
- Database database = DatabaseFactory.getInstance()
- .findCorrectDatabaseImplementation(
- jdbcConnection);
+ Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(jdbcConnection);
if (database instanceof PostgresDatabase) {
database = new PostgresDatabase() {
@@ -56,9 +52,7 @@ public class DatabaseUpdater {
database.setConnection(jdbcConnection);
}
- Liquibase liq = new Liquibase(
- "changelogs/db.changelog-master.xml", accessor,
- database);
+ Liquibase liq = new Liquibase("changelogs/db.changelog-master.xml", accessor, database);
liq.update("prod");
} finally {
if (context != null) {
diff --git a/src/main/java/com/commafeed/backend/FixedSizeSortedSet.java b/src/main/java/com/commafeed/backend/FixedSizeSortedSet.java
index f0a597ee..0d4ea24b 100644
--- a/src/main/java/com/commafeed/backend/FixedSizeSortedSet.java
+++ b/src/main/java/com/commafeed/backend/FixedSizeSortedSet.java
@@ -1,62 +1,44 @@
package com.commafeed.backend;
-import java.util.Collection;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import java.util.TreeSet;
-import org.apache.commons.collections.CollectionUtils;
+public class FixedSizeSortedSet {
-import com.google.common.collect.Lists;
-
-public class FixedSizeSortedSet extends TreeSet {
-
- private static final long serialVersionUID = 1L;
+ private List inner;
private final Comparator super E> comparator;
- private final int maxSize;
+ private final int capacity;
- public FixedSizeSortedSet(int maxSize, Comparator super E> comparator) {
- super(comparator);
- this.maxSize = maxSize;
+ public FixedSizeSortedSet(int capacity, Comparator super E> comparator) {
+ this.inner = new ArrayList(Math.max(0, capacity));
+ this.capacity = capacity < 0 ? Integer.MAX_VALUE : capacity;
this.comparator = comparator;
}
- @Override
- public boolean add(E e) {
+ public void add(E e) {
+ int position = Math.abs(Collections.binarySearch(inner, e, comparator) + 1);
if (isFull()) {
- E last = last();
- int comparison = comparator.compare(e, last);
- if (comparison < 0) {
- remove(last);
- return super.add(e);
- } else {
- return false;
+ if (position < inner.size()) {
+ inner.remove(inner.size() - 1);
+ inner.add(position, e);
}
} else {
- return super.add(e);
+ inner.add(position, 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 E last() {
+ return inner.get(inner.size() - 1);
}
-
+
public boolean isFull() {
- return size() == maxSize;
+ return inner.size() == capacity;
}
- @SuppressWarnings("unchecked")
public List asList() {
- return (List) Lists.newArrayList(toArray());
+ return inner;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/commafeed/backend/HttpGetter.java b/src/main/java/com/commafeed/backend/HttpGetter.java
index 80afae9c..20504126 100644
--- a/src/main/java/com/commafeed/backend/HttpGetter.java
+++ b/src/main/java/com/commafeed/backend/HttpGetter.java
@@ -56,9 +56,7 @@ public class HttpGetter {
static {
try {
SSL_CONTEXT = SSLContext.getInstance("TLS");
- SSL_CONTEXT.init(new KeyManager[0],
- new TrustManager[] { new DefaultTrustManager() },
- new SecureRandom());
+ SSL_CONTEXT.init(new KeyManager[0], new TrustManager[] { new DefaultTrustManager() }, new SecureRandom());
} catch (Exception e) {
log.error("Could not configure ssl context");
}
@@ -66,8 +64,7 @@ public class HttpGetter {
private static final X509HostnameVerifier VERIFIER = new DefaultHostnameVerifier();
- public HttpResult getBinary(String url, int timeout) throws ClientProtocolException,
- IOException, NotModifiedException {
+ public HttpResult getBinary(String url, int timeout) throws ClientProtocolException, IOException, NotModifiedException {
return getBinary(url, null, null, timeout);
}
@@ -85,8 +82,8 @@ public class HttpGetter {
* @throws NotModifiedException
* if the url hasn't changed since we asked for it last time
*/
- public HttpResult getBinary(String url, String lastModified, String eTag, int timeout)
- throws ClientProtocolException, IOException, NotModifiedException {
+ public HttpResult getBinary(String url, String lastModified, String eTag, int timeout) throws ClientProtocolException, IOException,
+ NotModifiedException {
HttpResult result = null;
long start = System.currentTimeMillis();
@@ -112,8 +109,7 @@ public class HttpGetter {
if (code == HttpStatus.SC_NOT_MODIFIED) {
throw new NotModifiedException("304 http code");
} else if (code >= 300) {
- throw new HttpResponseException(code,
- "Server returned HTTP error code " + code);
+ throw new HttpResponseException(code, "Server returned HTTP error code " + code);
}
} catch (HttpResponseException e) {
@@ -123,14 +119,11 @@ public class HttpGetter {
throw e;
}
}
- Header lastModifiedHeader = response
- .getFirstHeader(HttpHeaders.LAST_MODIFIED);
+ Header lastModifiedHeader = response.getFirstHeader(HttpHeaders.LAST_MODIFIED);
Header eTagHeader = response.getFirstHeader(HttpHeaders.ETAG);
- String lastModifiedResponse = lastModifiedHeader == null ? null
- : StringUtils.trimToNull(lastModifiedHeader.getValue());
- if (lastModified != null
- && StringUtils.equals(lastModified, lastModifiedResponse)) {
+ String lastModifiedResponse = lastModifiedHeader == null ? null : StringUtils.trimToNull(lastModifiedHeader.getValue());
+ if (lastModified != null && StringUtils.equals(lastModified, lastModifiedResponse)) {
throw new NotModifiedException("lastModifiedHeader is the same");
}
@@ -147,10 +140,8 @@ public class HttpGetter {
long duration = System.currentTimeMillis() - start;
Header contentType = entity.getContentType();
- result = new HttpResult(content, contentType == null ? null
- : contentType.getValue(), lastModifiedHeader == null ? null
- : lastModifiedHeader.getValue(), eTagHeader == null ? null
- : eTagHeader.getValue(), duration);
+ result = new HttpResult(content, contentType == null ? null : contentType.getValue(), lastModifiedHeader == null ? null
+ : lastModifiedHeader.getValue(), eTagHeader == null ? null : eTagHeader.getValue(), duration);
} finally {
client.getConnectionManager().shutdown();
}
@@ -165,8 +156,7 @@ public class HttpGetter {
private String eTag;
private long duration;
- public HttpResult(byte[] content, String contentType,
- String lastModifiedSince, String eTag, long duration) {
+ public HttpResult(byte[] content, String contentType, String lastModifiedSince, String eTag, long duration) {
this.content = content;
this.contentType = contentType;
this.lastModifiedSince = lastModifiedSince;
@@ -209,8 +199,7 @@ public class HttpGetter {
HttpProtocolParams.setContentCharset(params, UTF8);
HttpConnectionParams.setConnectionTimeout(params, timeout);
HttpConnectionParams.setSoTimeout(params, timeout);
- client.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0,
- false));
+ client.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));
return new DecompressingHttpClient(client);
}
@@ -225,13 +214,11 @@ public class HttpGetter {
private static class DefaultTrustManager implements X509TrustManager {
@Override
- public void checkClientTrusted(X509Certificate[] arg0, String arg1)
- throws CertificateException {
+ public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
@Override
- public void checkServerTrusted(X509Certificate[] arg0, String arg1)
- throws CertificateException {
+ public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
@Override
@@ -240,21 +227,18 @@ public class HttpGetter {
}
}
- private static class DefaultHostnameVerifier implements
- X509HostnameVerifier {
+ private static class DefaultHostnameVerifier implements X509HostnameVerifier {
@Override
public void verify(String string, SSLSocket ssls) throws IOException {
}
@Override
- public void verify(String string, X509Certificate xc)
- throws SSLException {
+ public void verify(String string, X509Certificate xc) throws SSLException {
}
@Override
- public void verify(String string, String[] strings, String[] strings1)
- throws SSLException {
+ public void verify(String string, String[] strings, String[] strings1) throws SSLException {
}
@Override
diff --git a/src/main/java/com/commafeed/backend/MetricsBean.java b/src/main/java/com/commafeed/backend/MetricsBean.java
index 0eed4510..fd3035c5 100644
--- a/src/main/java/com/commafeed/backend/MetricsBean.java
+++ b/src/main/java/com/commafeed/backend/MetricsBean.java
@@ -55,13 +55,11 @@ public class MetricsBean {
}
- public void entryUpdated(int statusesCount) {
+ public void entryInserted() {
thisHour.entriesInserted++;
thisMinute.entriesInserted++;
- thisHour.statusesInserted += statusesCount;
- thisMinute.statusesInserted += statusesCount;
}
public void entryCacheHit() {
@@ -107,7 +105,6 @@ public class MetricsBean {
private int feedsRefreshed;
private int feedsUpdated;
private int entriesInserted;
- private int statusesInserted;
private int threadWaited;
private int pushNotificationsReceived;
private int pushFeedsQueued;
@@ -138,14 +135,6 @@ public class MetricsBean {
this.entriesInserted = entriesInserted;
}
- public int getStatusesInserted() {
- return statusesInserted;
- }
-
- public void setStatusesInserted(int statusesInserted) {
- this.statusesInserted = statusesInserted;
- }
-
public int getThreadWaited() {
return threadWaited;
}
diff --git a/src/main/java/com/commafeed/backend/ScheduledTasks.java b/src/main/java/com/commafeed/backend/ScheduledTasks.java
new file mode 100644
index 00000000..d9e58680
--- /dev/null
+++ b/src/main/java/com/commafeed/backend/ScheduledTasks.java
@@ -0,0 +1,37 @@
+package com.commafeed.backend;
+
+import java.util.Date;
+
+import javax.ejb.Schedule;
+import javax.ejb.Stateless;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.commafeed.backend.services.ApplicationSettingsService;
+
+@Stateless
+public class ScheduledTasks {
+ protected final static Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
+
+ @Inject
+ ApplicationSettingsService applicationSettingsService;
+
+ @Inject
+ DatabaseCleaner cleaner;
+
+ @PersistenceContext
+ EntityManager em;
+
+ // every day at midnight
+ @Schedule(hour = "0", persistent = false)
+ private void cleanupOldStatuses() {
+ Date threshold = applicationSettingsService.get().getUnreadThreshold();
+ if (threshold != null) {
+ cleaner.cleanStatusesOlderThan(threshold);
+ }
+ }
+}
diff --git a/src/main/java/com/commafeed/backend/StartupBean.java b/src/main/java/com/commafeed/backend/StartupBean.java
index fb0c43d6..3f0d5189 100644
--- a/src/main/java/com/commafeed/backend/StartupBean.java
+++ b/src/main/java/com/commafeed/backend/StartupBean.java
@@ -17,7 +17,7 @@ import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.commafeed.backend.dao.UserDAO;
+import com.commafeed.backend.dao.ApplicationSettingsDAO;
import com.commafeed.backend.feeds.FeedRefreshTaskGiver;
import com.commafeed.backend.model.ApplicationSettings;
import com.commafeed.backend.model.UserRole.Role;
@@ -38,7 +38,7 @@ public class StartupBean {
DatabaseUpdater databaseUpdater;
@Inject
- UserDAO userDAO;
+ ApplicationSettingsDAO applicationSettingsDAO;
@Inject
UserService userService;
@@ -58,7 +58,7 @@ public class StartupBean {
startupTime = System.currentTimeMillis();
databaseUpdater.update();
- if (userDAO.getCount() == 0) {
+ if (applicationSettingsDAO.getCount() == 0) {
initialData();
}
applicationSettingsService.applyLogLevel();
@@ -79,8 +79,7 @@ public class StartupBean {
IOUtils.closeQuietly(is);
}
for (Object key : props.keySet()) {
- supportedLanguages.put(key.toString(),
- props.getProperty(key.toString()));
+ supportedLanguages.put(key.toString(), props.getProperty(key.toString()));
}
}
@@ -92,11 +91,8 @@ public class StartupBean {
applicationSettingsService.save(settings);
try {
- userService.register(USERNAME_ADMIN, "admin",
- "admin@commafeed.com",
- Arrays.asList(Role.ADMIN, Role.USER), true);
- userService.register(USERNAME_DEMO, "demo", "demo@commafeed.com",
- Arrays.asList(Role.USER), true);
+ userService.register(USERNAME_ADMIN, "admin", "admin@commafeed.com", Arrays.asList(Role.ADMIN, Role.USER), true);
+ userService.register(USERNAME_DEMO, "demo", "demo@commafeed.com", Arrays.asList(Role.USER), true);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
diff --git a/src/main/java/com/commafeed/backend/cache/CacheService.java b/src/main/java/com/commafeed/backend/cache/CacheService.java
index 232cd1b9..13c19c00 100644
--- a/src/main/java/com/commafeed/backend/cache/CacheService.java
+++ b/src/main/java/com/commafeed/backend/cache/CacheService.java
@@ -1,34 +1,38 @@
package com.commafeed.backend.cache;
import java.util.List;
-import java.util.Map;
import org.apache.commons.codec.digest.DigestUtils;
import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry;
+import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User;
import com.commafeed.frontend.model.Category;
public abstract class CacheService {
+ // feed entries for faster refresh
public abstract List getLastEntries(Feed feed);
public abstract void setLastEntries(Feed feed, List entries);
public String buildUniqueEntryKey(Feed feed, FeedEntry entry) {
- return DigestUtils.sha1Hex(entry.getGuid() +
- entry.getUrl());
+ return DigestUtils.sha1Hex(entry.getGuid() + entry.getUrl());
}
- public abstract Category getRootCategory(User user);
+ // user categories
+ public abstract Category getUserRootCategory(User user);
- public abstract void setRootCategory(User user, Category category);
-
- public abstract Map getUnreadCounts(User user);
+ public abstract void setUserRootCategory(User user, Category category);
- public abstract void setUnreadCounts(User user, Map map);
+ public abstract void invalidateUserRootCategory(User... users);
- public abstract void invalidateUserData(User... users);
+ // unread count
+ public abstract Long getUnreadCount(FeedSubscription sub);
+
+ public abstract void setUnreadCount(FeedSubscription sub, Long count);
+
+ public abstract void invalidateUnreadCount(FeedSubscription... subs);
}
diff --git a/src/main/java/com/commafeed/backend/cache/NoopCacheService.java b/src/main/java/com/commafeed/backend/cache/NoopCacheService.java
index cd2b841f..39f7c0de 100644
--- a/src/main/java/com/commafeed/backend/cache/NoopCacheService.java
+++ b/src/main/java/com/commafeed/backend/cache/NoopCacheService.java
@@ -2,12 +2,12 @@ package com.commafeed.backend.cache;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Alternative;
import com.commafeed.backend.model.Feed;
+import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.User;
import com.commafeed.frontend.model.Category;
@@ -25,27 +25,32 @@ public class NoopCacheService extends CacheService {
}
@Override
- public Category getRootCategory(User user) {
+ public Long getUnreadCount(FeedSubscription sub) {
return null;
}
@Override
- public void setRootCategory(User user, Category category) {
+ public void setUnreadCount(FeedSubscription sub, Long count) {
}
@Override
- public Map getUnreadCounts(User user) {
+ public void invalidateUnreadCount(FeedSubscription... subs) {
+
+ }
+
+ @Override
+ public Category getUserRootCategory(User user) {
return null;
}
@Override
- public void setUnreadCounts(User user, Map map) {
+ public void setUserRootCategory(User user, Category category) {
}
@Override
- public void invalidateUserData(User... users) {
+ public void invalidateUserRootCategory(User... users) {
}
diff --git a/src/main/java/com/commafeed/backend/cache/RedisCacheService.java b/src/main/java/com/commafeed/backend/cache/RedisCacheService.java
index 231b6272..a7ea2f06 100644
--- a/src/main/java/com/commafeed/backend/cache/RedisCacheService.java
+++ b/src/main/java/com/commafeed/backend/cache/RedisCacheService.java
@@ -1,7 +1,6 @@
package com.commafeed.backend.cache;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -17,23 +16,22 @@ import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Pipeline;
import com.commafeed.backend.model.Feed;
+import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.Models;
import com.commafeed.backend.model.User;
import com.commafeed.frontend.model.Category;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.type.MapType;
import com.google.api.client.util.Lists;
@Alternative
@ApplicationScoped
public class RedisCacheService extends CacheService {
- private static final Logger log = LoggerFactory
- .getLogger(RedisCacheService.class);
+ private static final Logger log = LoggerFactory.getLogger(RedisCacheService.class);
+ private static ObjectMapper mapper = new ObjectMapper();
private JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost");
- private ObjectMapper mapper = new ObjectMapper();
@Override
public List getLastEntries(Feed feed) {
@@ -70,11 +68,11 @@ public class RedisCacheService extends CacheService {
}
@Override
- public Category getRootCategory(User user) {
+ public Category getUserRootCategory(User user) {
Category cat = null;
Jedis jedis = pool.getResource();
try {
- String key = buildRedisRootCategoryKey(user);
+ String key = buildRedisUserRootCategoryKey(user);
String json = jedis.get(key);
if (json != null) {
cat = mapper.readValue(json, Category.class);
@@ -88,10 +86,10 @@ public class RedisCacheService extends CacheService {
}
@Override
- public void setRootCategory(User user, Category category) {
+ public void setUserRootCategory(User user, Category category) {
Jedis jedis = pool.getResource();
try {
- String key = buildRedisRootCategoryKey(user);
+ String key = buildRedisUserRootCategoryKey(user);
Pipeline pipe = jedis.pipelined();
pipe.del(key);
@@ -106,37 +104,35 @@ public class RedisCacheService extends CacheService {
}
@Override
- public Map getUnreadCounts(User user) {
- Map map = null;
+ public Long getUnreadCount(FeedSubscription sub) {
+ Long count = null;
Jedis jedis = pool.getResource();
try {
- String key = buildRedisUnreadCountKey(user);
- String json = jedis.get(key);
- if (json != null) {
- MapType type = mapper.getTypeFactory().constructMapType(
- Map.class, Long.class, Long.class);
- map = mapper.readValue(json, type);
+ String key = buildRedisUnreadCountKey(sub);
+ String countString = jedis.get(key);
+ if (countString != null) {
+ count = Long.valueOf(countString);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
pool.returnResource(jedis);
}
- return map;
+ return count;
}
@Override
- public void setUnreadCounts(User user, Map map) {
+ public void setUnreadCount(FeedSubscription sub, Long count) {
Jedis jedis = pool.getResource();
try {
- String key = buildRedisUnreadCountKey(user);
+ String key = buildRedisUnreadCountKey(sub);
Pipeline pipe = jedis.pipelined();
pipe.del(key);
- pipe.set(key, mapper.writeValueAsString(map));
+ pipe.set(key, String.valueOf(count));
pipe.expire(key, (int) TimeUnit.MINUTES.toSeconds(30));
pipe.sync();
- } catch (JsonProcessingException e) {
+ } catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
pool.returnResource(jedis);
@@ -144,15 +140,13 @@ public class RedisCacheService extends CacheService {
}
@Override
- public void invalidateUserData(User... users) {
+ public void invalidateUserRootCategory(User... users) {
Jedis jedis = pool.getResource();
try {
Pipeline pipe = jedis.pipelined();
if (users != null) {
for (User user : users) {
- String key = buildRedisRootCategoryKey(user);
- pipe.del(key);
- key = buildRedisUnreadCountKey(user);
+ String key = buildRedisUserRootCategoryKey(user);
pipe.del(key);
}
}
@@ -162,16 +156,33 @@ public class RedisCacheService extends CacheService {
}
}
- private String buildRedisRootCategoryKey(User user) {
- return "root_cat:" + Models.getId(user);
- }
-
- private String buildRedisUnreadCountKey(User user) {
- return "unread_count:" + Models.getId(user);
+ @Override
+ public void invalidateUnreadCount(FeedSubscription... subs) {
+ Jedis jedis = pool.getResource();
+ try {
+ Pipeline pipe = jedis.pipelined();
+ if (subs != null) {
+ for (FeedSubscription sub : subs) {
+ String key = buildRedisUnreadCountKey(sub);
+ pipe.del(key);
+ }
+ }
+ pipe.sync();
+ } finally {
+ pool.returnResource(jedis);
+ }
}
private String buildRedisEntryKey(Feed feed) {
- return "feed:" + feed.getId();
+ return "f:" + Models.getId(feed);
+ }
+
+ private String buildRedisUserRootCategoryKey(User user) {
+ return "c:" + Models.getId(user);
+ }
+
+ private String buildRedisUnreadCountKey(FeedSubscription sub) {
+ return "u:" + Models.getId(sub);
}
}
diff --git a/src/main/java/com/commafeed/backend/dao/FeedCategoryDAO.java b/src/main/java/com/commafeed/backend/dao/FeedCategoryDAO.java
index 72a1029e..62a2d8bf 100644
--- a/src/main/java/com/commafeed/backend/dao/FeedCategoryDAO.java
+++ b/src/main/java/com/commafeed/backend/dao/FeedCategoryDAO.java
@@ -26,8 +26,7 @@ public class FeedCategoryDAO extends GenericDAO {
CriteriaQuery query = builder.createQuery(getType());
Root root = query.from(getType());
- Join userJoin = (Join) root
- .fetch(FeedCategory_.user);
+ Join userJoin = (Join) root.fetch(FeedCategory_.user);
query.where(builder.equal(userJoin.get(User_.id), user.getId()));
@@ -38,14 +37,12 @@ public class FeedCategoryDAO extends GenericDAO {
CriteriaQuery query = builder.createQuery(getType());
Root root = query.from(getType());
- Predicate p1 = builder.equal(
- root.get(FeedCategory_.user).get(User_.id), user.getId());
+ Predicate p1 = builder.equal(root.get(FeedCategory_.user).get(User_.id), user.getId());
Predicate p2 = builder.equal(root.get(FeedCategory_.id), id);
query.where(p1, p2);
- return Iterables.getFirst(cache(em.createQuery(query)).getResultList(),
- null);
+ return Iterables.getFirst(cache(em.createQuery(query)).getResultList(), null);
}
public FeedCategory findByName(User user, String name, FeedCategory parent) {
@@ -60,8 +57,7 @@ public class FeedCategoryDAO extends GenericDAO {
if (parent == null) {
predicates.add(builder.isNull(root.get(FeedCategory_.parent)));
} else {
- predicates
- .add(builder.equal(root.get(FeedCategory_.parent), parent));
+ predicates.add(builder.equal(root.get(FeedCategory_.parent), parent));
}
query.where(predicates.toArray(new Predicate[0]));
@@ -85,8 +81,7 @@ public class FeedCategoryDAO extends GenericDAO {
if (parent == null) {
predicates.add(builder.isNull(root.get(FeedCategory_.parent)));
} else {
- predicates
- .add(builder.equal(root.get(FeedCategory_.parent), parent));
+ predicates.add(builder.equal(root.get(FeedCategory_.parent), parent));
}
query.where(predicates.toArray(new Predicate[0]));
@@ -94,8 +89,7 @@ public class FeedCategoryDAO extends GenericDAO {
return em.createQuery(query).getResultList();
}
- public List findAllChildrenCategories(User user,
- FeedCategory parent) {
+ public List findAllChildrenCategories(User user, FeedCategory parent) {
List list = Lists.newArrayList();
List all = findAll(user);
for (FeedCategory cat : all) {
diff --git a/src/main/java/com/commafeed/backend/dao/FeedDAO.java b/src/main/java/com/commafeed/backend/dao/FeedDAO.java
index 3813ebec..3af5f1e1 100644
--- a/src/main/java/com/commafeed/backend/dao/FeedDAO.java
+++ b/src/main/java/com/commafeed/backend/dao/FeedDAO.java
@@ -39,23 +39,17 @@ public class FeedDAO extends GenericDAO {
public List feeds;
}
- private List getUpdatablePredicates(Root root,
- Date threshold) {
+ private List getUpdatablePredicates(Root root, Date threshold) {
- Predicate hasSubscriptions = builder.isNotEmpty(root
- .get(Feed_.subscriptions));
+ Predicate hasSubscriptions = builder.isNotEmpty(root.get(Feed_.subscriptions));
Predicate neverUpdated = builder.isNull(root.get(Feed_.lastUpdated));
- Predicate updatedBeforeThreshold = builder.lessThan(
- root.get(Feed_.lastUpdated), threshold);
+ Predicate updatedBeforeThreshold = builder.lessThan(root.get(Feed_.lastUpdated), threshold);
- Predicate disabledDateIsNull = builder.isNull(root
- .get(Feed_.disabledUntil));
- Predicate disabledDateIsInPast = builder.lessThan(
- root.get(Feed_.disabledUntil), new Date());
+ Predicate disabledDateIsNull = builder.isNull(root.get(Feed_.disabledUntil));
+ Predicate disabledDateIsInPast = builder.lessThan(root.get(Feed_.disabledUntil), new Date());
- return Lists.newArrayList(hasSubscriptions,
- builder.or(neverUpdated, updatedBeforeThreshold),
+ return Lists.newArrayList(hasSubscriptions, builder.or(neverUpdated, updatedBeforeThreshold),
builder.or(disabledDateIsNull, disabledDateIsInPast));
}
@@ -64,8 +58,7 @@ public class FeedDAO extends GenericDAO {
Root root = query.from(getType());
query.select(builder.count(root));
- query.where(getUpdatablePredicates(root, threshold).toArray(
- new Predicate[0]));
+ query.where(getUpdatablePredicates(root, threshold).toArray(new Predicate[0]));
TypedQuery q = em.createQuery(query);
return q.getSingleResult();
@@ -75,8 +68,7 @@ public class FeedDAO extends GenericDAO {
CriteriaQuery query = builder.createQuery(getType());
Root root = query.from(getType());
- query.where(getUpdatablePredicates(root, threshold).toArray(
- new Predicate[0]));
+ query.where(getUpdatablePredicates(root, threshold).toArray(new Predicate[0]));
query.orderBy(builder.asc(root.get(Feed_.lastUpdated)));
@@ -94,11 +86,9 @@ public class FeedDAO extends GenericDAO {
}
String normalized = FeedUtils.normalizeURL(url);
- feeds = findByField(Feed_.normalizedUrlHash,
- DigestUtils.sha1Hex(normalized));
+ feeds = findByField(Feed_.normalizedUrlHash, DigestUtils.sha1Hex(normalized));
feed = Iterables.getFirst(feeds, null);
- if (feed != null
- && StringUtils.equals(normalized, feed.getNormalizedUrl())) {
+ if (feed != null && StringUtils.equals(normalized, feed.getNormalizedUrl())) {
return feed;
}
@@ -110,8 +100,7 @@ public class FeedDAO extends GenericDAO {
}
public void deleteRelationships(Feed feed) {
- Query relationshipDeleteQuery = em
- .createNamedQuery("Feed.deleteEntryRelationships");
+ Query relationshipDeleteQuery = em.createNamedQuery("Feed.deleteEntryRelationships");
relationshipDeleteQuery.setParameter("feedId", feed.getId());
relationshipDeleteQuery.executeUpdate();
}
@@ -120,8 +109,7 @@ public class FeedDAO extends GenericDAO {
CriteriaQuery query = builder.createQuery(getType());
Root root = query.from(getType());
- SetJoin join = root.join(Feed_.subscriptions,
- JoinType.LEFT);
+ SetJoin join = root.join(Feed_.subscriptions, JoinType.LEFT);
query.where(builder.isNull(join.get(FeedSubscription_.id)));
TypedQuery q = em.createQuery(query);
q.setMaxResults(max);
@@ -138,8 +126,7 @@ public class FeedDAO extends GenericDAO {
}
public static enum DuplicateMode {
- NORMALIZED_URL(Feed_.normalizedUrlHash), LAST_CONTENT(
- Feed_.lastContentHash), PUSH_TOPIC(Feed_.pushTopicHash);
+ NORMALIZED_URL(Feed_.normalizedUrlHash), LAST_CONTENT(Feed_.lastContentHash), PUSH_TOPIC(Feed_.pushTopicHash);
private SingularAttribute path;
private DuplicateMode(SingularAttribute path) {
@@ -151,8 +138,7 @@ public class FeedDAO extends GenericDAO {
}
}
- public List findDuplicates(DuplicateMode mode, int offset,
- int limit, long minCount) {
+ public List findDuplicates(DuplicateMode mode, int offset, int limit, long minCount) {
CriteriaQuery query = builder.createQuery(String.class);
Root root = query.from(getType());
diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryContentDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryContentDAO.java
new file mode 100644
index 00000000..4a244e28
--- /dev/null
+++ b/src/main/java/com/commafeed/backend/dao/FeedEntryContentDAO.java
@@ -0,0 +1,48 @@
+package com.commafeed.backend.dao;
+
+import java.util.List;
+
+import javax.persistence.TypedQuery;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Join;
+import javax.persistence.criteria.JoinType;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+
+import com.commafeed.backend.model.FeedEntry;
+import com.commafeed.backend.model.FeedEntryContent;
+import com.commafeed.backend.model.FeedEntryContent_;
+import com.commafeed.backend.model.FeedEntry_;
+import com.google.common.collect.Iterables;
+
+public class FeedEntryContentDAO extends GenericDAO {
+
+ public FeedEntryContent findExisting(String contentHash, String titleHash) {
+
+ CriteriaQuery query = builder.createQuery(getType());
+ Root root = query.from(getType());
+
+ Predicate p1 = builder.equal(root.get(FeedEntryContent_.contentHash), contentHash);
+ Predicate p2 = builder.equal(root.get(FeedEntryContent_.titleHash), titleHash);
+
+ query.where(p1, p2);
+ TypedQuery q = em.createQuery(query);
+ return Iterables.getFirst(q.getResultList(), null);
+
+ }
+
+ public int deleteWithoutEntries(int max) {
+ CriteriaQuery query = builder.createQuery(getType());
+ Root root = query.from(getType());
+
+ Join join = root.join(FeedEntryContent_.entries, JoinType.LEFT);
+ query.where(builder.isNull(join.get(FeedEntry_.id)));
+ TypedQuery q = em.createQuery(query);
+ q.setMaxResults(max);
+
+ List list = q.getResultList();
+ int deleted = list.size();
+ return deleted;
+
+ }
+}
diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java
index 77c28006..8b50dd2e 100644
--- a/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java
+++ b/src/main/java/com/commafeed/backend/dao/FeedEntryDAO.java
@@ -4,77 +4,38 @@ import java.util.Date;
import java.util.List;
import javax.ejb.Stateless;
-import javax.inject.Inject;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaQuery;
-import javax.persistence.criteria.JoinType;
+import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
-import javax.persistence.criteria.SetJoin;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.commafeed.backend.model.Feed;
import com.commafeed.backend.model.FeedEntry;
import com.commafeed.backend.model.FeedEntry_;
-import com.commafeed.backend.model.FeedFeedEntry;
-import com.commafeed.backend.model.FeedFeedEntry_;
-import com.commafeed.backend.services.ApplicationSettingsService;
+import com.commafeed.backend.model.Feed_;
import com.google.common.collect.Iterables;
@Stateless
public class FeedEntryDAO extends GenericDAO {
- @Inject
- ApplicationSettingsService applicationSettingsService;
+ protected static final Logger log = LoggerFactory.getLogger(FeedEntryDAO.class);
- protected static final Logger log = LoggerFactory
- .getLogger(FeedEntryDAO.class);
+ public FeedEntry findExisting(String guid, String url, Long feedId) {
- public static class EntryWithFeed {
- public FeedEntry entry;
- public FeedFeedEntry ffe;
-
- public EntryWithFeed(FeedEntry entry, FeedFeedEntry ffe) {
- this.entry = entry;
- this.ffe = ffe;
- }
- }
-
- public EntryWithFeed findExisting(String guid, String url, Long feedId) {
-
- TypedQuery q = em.createNamedQuery(
- "EntryStatus.existing", EntryWithFeed.class);
- q.setParameter("guidHash", DigestUtils.sha1Hex(guid));
- q.setParameter("url", url);
- q.setParameter("feedId", feedId);
-
- EntryWithFeed result = null;
- List list = q.getResultList();
- for (EntryWithFeed ewf : list) {
- if (ewf.entry != null && ewf.ffe != null) {
- result = ewf;
- break;
- }
- }
- if (result == null) {
- result = Iterables.getFirst(list, null);
- }
- return result;
- }
-
- public List findByFeed(Feed feed, int offset, int limit) {
CriteriaQuery query = builder.createQuery(getType());
Root root = query.from(getType());
- SetJoin feedsJoin = root.join(FeedEntry_.feedRelationships);
- query.where(builder.equal(feedsJoin.get(FeedFeedEntry_.feed), feed));
- query.orderBy(builder.desc(feedsJoin.get(FeedFeedEntry_.entryUpdated)));
- TypedQuery q = em.createQuery(query);
- limit(q, offset, limit);
- setTimeout(q, applicationSettingsService.get().getQueryTimeout());
- return q.getResultList();
+ Predicate p1 = builder.equal(root.get(FeedEntry_.guidHash), DigestUtils.sha1Hex(guid));
+ Predicate p2 = builder.equal(root.get(FeedEntry_.url), url);
+ Predicate p3 = builder.equal(root.get(FeedEntry_.feed).get(Feed_.id), feedId);
+
+ query.where(p1, p2, p3);
+
+ List list = em.createQuery(query).getResultList();
+ return Iterables.getFirst(list, null);
}
public int delete(Date olderThan, int max) {
@@ -90,20 +51,4 @@ public class FeedEntryDAO extends GenericDAO {
delete(list);
return deleted;
}
-
- public int deleteWithoutFeeds(int max) {
- CriteriaQuery query = builder.createQuery(getType());
- Root root = query.from(getType());
-
- SetJoin join = root.join(FeedEntry_.feedRelationships,
- JoinType.LEFT);
- query.where(builder.isNull(join.get(FeedFeedEntry_.feed)));
- TypedQuery q = em.createQuery(query);
- q.setMaxResults(max);
-
- List list = q.getResultList();
- int deleted = list.size();
- delete(list);
- return deleted;
- }
}
diff --git a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java
index e509a03e..a4ea8155 100644
--- a/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java
+++ b/src/main/java/com/commafeed/backend/dao/FeedEntryStatusDAO.java
@@ -10,25 +10,30 @@ import javax.inject.Inject;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaQuery;
-import javax.persistence.criteria.Join;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
-import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ObjectUtils;
+import org.hibernate.Criteria;
+import org.hibernate.criterion.CriteriaSpecification;
+import org.hibernate.criterion.Disjunction;
+import org.hibernate.criterion.MatchMode;
+import org.hibernate.criterion.Order;
+import org.hibernate.criterion.ProjectionList;
+import org.hibernate.criterion.Projections;
+import org.hibernate.criterion.Restrictions;
+import org.hibernate.sql.JoinType;
+import org.hibernate.transform.Transformers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.commafeed.backend.FixedSizeSortedSet;
import com.commafeed.backend.model.FeedEntry;
-import com.commafeed.backend.model.FeedEntryContent;
import com.commafeed.backend.model.FeedEntryContent_;
import com.commafeed.backend.model.FeedEntryStatus;
import com.commafeed.backend.model.FeedEntryStatus_;
import com.commafeed.backend.model.FeedEntry_;
-import com.commafeed.backend.model.FeedFeedEntry;
-import com.commafeed.backend.model.FeedFeedEntry_;
import com.commafeed.backend.model.FeedSubscription;
import com.commafeed.backend.model.Models;
import com.commafeed.backend.model.User;
@@ -36,27 +41,14 @@ import com.commafeed.backend.model.UserSettings.ReadingOrder;
import com.commafeed.backend.services.ApplicationSettingsService;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
@Stateless
public class FeedEntryStatusDAO extends GenericDAO {
- protected static Logger log = LoggerFactory
- .getLogger(FeedEntryStatusDAO.class);
+ protected static Logger log = LoggerFactory.getLogger(FeedEntryStatusDAO.class);
- private static final Comparator ENTRY_COMPARATOR_DESC = new Comparator() {
- @Override
- public int compare(FeedEntry o1, FeedEntry o2) {
- return ObjectUtils.compare(o2.getUpdated(), o1.getUpdated());
- };
- };
-
- private static final Comparator ENTRY_COMPARATOR_ASC = new Comparator() {
- @Override
- public int compare(FeedEntry o1, FeedEntry o2) {
- return ObjectUtils.compare(o1.getUpdated(), o2.getUpdated());
- };
- };
+ private static final String ALIAS_STATUS = "status";
+ private static final String ALIAS_ENTRY = "entry";
private static final Comparator STATUS_COMPARATOR_DESC = new Comparator() {
@Override
@@ -81,22 +73,30 @@ public class FeedEntryStatusDAO extends GenericDAO {
Root root = query.from(getType());
Predicate p1 = builder.equal(root.get(FeedEntryStatus_.entry), entry);
- Predicate p2 = builder.equal(root.get(FeedEntryStatus_.subscription),
- sub);
+ Predicate p2 = builder.equal(root.get(FeedEntryStatus_.subscription), sub);
query.where(p1, p2);
List statuses = em.createQuery(query).getResultList();
FeedEntryStatus status = Iterables.getFirst(statuses, null);
+
+ return handleStatus(status, sub, entry);
+ }
+
+ private FeedEntryStatus handleStatus(FeedEntryStatus status, FeedSubscription sub, FeedEntry entry) {
if (status == null) {
+ Date unreadThreshold = applicationSettingsService.get().getUnreadThreshold();
+ boolean read = unreadThreshold == null ? false : entry.getUpdated().before(unreadThreshold);
status = new FeedEntryStatus(sub.getUser(), sub, entry);
- status.setRead(true);
+ status.setRead(read);
+ status.setMarkable(!read);
+ } else {
+ status.setMarkable(true);
}
return status;
}
- public List findStarred(User user, Date newerThan,
- int offset, int limit, ReadingOrder order, boolean 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());
@@ -108,8 +108,7 @@ public class FeedEntryStatusDAO extends GenericDAO {
query.where(predicates.toArray(new Predicate[0]));
if (newerThan != null) {
- predicates.add(builder.greaterThanOrEqualTo(
- root.get(FeedEntryStatus_.entryInserted), newerThan));
+ predicates.add(builder.greaterThanOrEqualTo(root.get(FeedEntryStatus_.entryInserted), newerThan));
}
orderStatusesBy(query, root, order);
@@ -117,200 +116,129 @@ public class FeedEntryStatusDAO extends GenericDAO {
TypedQuery q = em.createQuery(query);
limit(q, offset, limit);
setTimeout(q);
- return lazyLoadContent(includeContent, q.getResultList());
+ List statuses = q.getResultList();
+ for (FeedEntryStatus status : statuses) {
+ status = handleStatus(status, status.getSubscription(), status.getEntry());
+ }
+ return lazyLoadContent(includeContent, statuses);
}
- public List findBySubscriptions(
- List subscriptions, String keywords,
- Date newerThan, int offset, int limit, ReadingOrder order,
- boolean includeContent) {
+ private Criteria buildSearchCriteria(FeedSubscription sub, boolean unreadOnly, String keywords, Date newerThan, int offset, int limit,
+ ReadingOrder order, boolean includeContent, Date last) {
+ Criteria criteria = getSession().createCriteria(FeedEntry.class, ALIAS_ENTRY);
- int capacity = offset + limit;
- Comparator comparator = order == ReadingOrder.desc ? ENTRY_COMPARATOR_DESC
- : ENTRY_COMPARATOR_ASC;
- FixedSizeSortedSet set = new FixedSizeSortedSet(
- capacity < 0 ? Integer.MAX_VALUE : capacity, comparator);
- for (FeedSubscription sub : subscriptions) {
- CriteriaQuery query = builder
- .createQuery(FeedEntry.class);
- Root root = query.from(FeedEntry.class);
- Join ffeJoin = root
- .join(FeedEntry_.feedRelationships);
+ criteria.add(Restrictions.eq(FeedEntry_.feed.getName(), sub.getFeed()));
- List predicates = Lists.newArrayList();
- predicates.add(builder.equal(ffeJoin.get(FeedFeedEntry_.feed),
- sub.getFeed()));
+ if (keywords != null) {
+ Criteria contentJoin = criteria.createCriteria(FeedEntry_.content.getName(), "content", JoinType.INNER_JOIN);
- if (newerThan != null) {
- predicates.add(builder.greaterThanOrEqualTo(
- root.get(FeedEntry_.inserted), newerThan));
+ for (String keyword : keywords.split(" ")) {
+ Disjunction or = Restrictions.disjunction();
+ or.add(Restrictions.ilike(FeedEntryContent_.content.getName(), keyword, MatchMode.ANYWHERE));
+ or.add(Restrictions.ilike(FeedEntryContent_.title.getName(), keyword, MatchMode.ANYWHERE));
+ contentJoin.add(or);
}
-
- if (keywords != null) {
- Join contentJoin = root
- .join(FeedEntry_.content);
-
- String joinedKeywords = StringUtils.join(keywords.toLowerCase()
- .split(" "), "%");
- joinedKeywords = "%" + joinedKeywords + "%";
-
- Predicate content = builder.like(builder.lower(contentJoin
- .get(FeedEntryContent_.content)), joinedKeywords);
- Predicate title = builder
- .like(builder.lower(contentJoin
- .get(FeedEntryContent_.title)), joinedKeywords);
- predicates.add(builder.or(content, title));
- }
-
- if (order != null && !set.isEmpty() && set.isFull()) {
- Predicate filter = null;
- FeedEntry last = set.last();
- if (order == ReadingOrder.desc) {
- filter = builder.greaterThan(
- ffeJoin.get(FeedFeedEntry_.entryUpdated),
- last.getUpdated());
- } else {
- filter = builder.lessThan(
- ffeJoin.get(FeedFeedEntry_.entryUpdated),
- last.getUpdated());
- }
- predicates.add(filter);
- }
- query.where(predicates.toArray(new Predicate[0]));
- orderEntriesBy(query, ffeJoin, order);
- TypedQuery q = em.createQuery(query);
- limit(q, 0, capacity);
- setTimeout(q);
-
- List list = q.getResultList();
- for (FeedEntry entry : list) {
- entry.setSubscription(sub);
- }
- set.addAll(list);
}
+ Criteria statusJoin = criteria.createCriteria(FeedEntry_.statuses.getName(), ALIAS_STATUS, JoinType.LEFT_OUTER_JOIN,
+ Restrictions.eq(FeedEntryStatus_.subscription.getName(), sub));
- List entries = set.asList();
- int size = entries.size();
- if (size < offset) {
- return Lists.newArrayList();
- }
+ if (unreadOnly) {
- entries = entries.subList(Math.max(offset, 0), size);
+ Disjunction or = Restrictions.disjunction();
+ or.add(Restrictions.isNull(FeedEntryStatus_.read.getName()));
+ or.add(Restrictions.eq(FeedEntryStatus_.read.getName(), false));
+ statusJoin.add(or);
- List results = Lists.newArrayList();
- for (FeedEntry entry : entries) {
- FeedSubscription subscription = entry.getSubscription();
- results.add(getStatus(subscription, entry));
- }
-
- return lazyLoadContent(includeContent, results);
- }
-
- public List findUnreadBySubscriptions(
- List subscriptions, Date newerThan, int offset,
- int limit, ReadingOrder order, boolean includeContent) {
-
- int capacity = offset + limit;
- Comparator comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC
- : STATUS_COMPARATOR_ASC;
- FixedSizeSortedSet set = new FixedSizeSortedSet(
- capacity < 0 ? Integer.MAX_VALUE : capacity, comparator);
- for (FeedSubscription sub : subscriptions) {
- CriteriaQuery query = builder
- .createQuery(getType());
- Root root = query.from(getType());
-
- List predicates = Lists.newArrayList();
-
- predicates.add(builder.equal(
- root.get(FeedEntryStatus_.subscription), sub));
- predicates.add(builder.isFalse(root.get(FeedEntryStatus_.read)));
-
- if (newerThan != null) {
- predicates.add(builder.greaterThanOrEqualTo(
- root.get(FeedEntryStatus_.entryInserted), newerThan));
+ Date unreadThreshold = applicationSettingsService.get().getUnreadThreshold();
+ if (unreadThreshold != null) {
+ criteria.add(Restrictions.ge(FeedEntry_.updated.getName(), unreadThreshold));
}
-
- if (order != null && !set.isEmpty() && set.isFull()) {
- Predicate filter = null;
- FeedEntryStatus last = set.last();
- if (order == ReadingOrder.desc) {
- filter = builder.greaterThan(
- root.get(FeedEntryStatus_.entryUpdated),
- last.getEntryUpdated());
- } else {
- filter = builder.lessThan(
- root.get(FeedEntryStatus_.entryUpdated),
- last.getEntryUpdated());
- }
- predicates.add(filter);
- }
- query.where(predicates.toArray(new Predicate[0]));
- orderStatusesBy(query, root, order);
-
- TypedQuery q = em.createQuery(query);
- limit(q, -1, limit);
- setTimeout(q);
-
- List list = q.getResultList();
- set.addAll(list);
}
- List entries = set.asList();
- int size = entries.size();
- if (size < offset) {
- return Lists.newArrayList();
- }
-
- entries = entries.subList(Math.max(offset, 0), size);
- return lazyLoadContent(includeContent, entries);
- }
-
- public List findAllUnread(User user, Date newerThan,
- int offset, int limit, ReadingOrder order, boolean includeContent) {
-
- CriteriaQuery query = builder.createQuery(getType());
- Root root = query.from(getType());
-
- List predicates = Lists.newArrayList();
-
- predicates.add(builder.equal(root.get(FeedEntryStatus_.user), user));
- predicates.add(builder.isFalse(root.get(FeedEntryStatus_.read)));
-
if (newerThan != null) {
- predicates.add(builder.greaterThanOrEqualTo(
- root.get(FeedEntryStatus_.entryInserted), newerThan));
+ criteria.add(Restrictions.ge(FeedEntry_.inserted.getName(), newerThan));
}
- query.where(predicates.toArray(new Predicate[0]));
- orderStatusesBy(query, root, order);
-
- TypedQuery q = em.createQuery(query);
- limit(q, offset, limit);
- setTimeout(q);
-
- return lazyLoadContent(includeContent, q.getResultList());
- }
-
- /**
- * Map between subscriptionId and unread count
- */
- @SuppressWarnings("rawtypes")
- public Map getUnreadCount(User user) {
- Map map = Maps.newHashMap();
- Query query = em.createNamedQuery("EntryStatus.unreadCounts");
- query.setParameter("user", user);
- setTimeout(query);
- List resultList = query.getResultList();
- for (Object o : resultList) {
- Object[] array = (Object[]) o;
- map.put((Long) array[0], (Long) array[1]);
+ if (last != null) {
+ if (order == ReadingOrder.desc) {
+ criteria.add(Restrictions.gt(FeedEntry_.updated.getName(), last));
+ } else {
+ criteria.add(Restrictions.lt(FeedEntry_.updated.getName(), last));
+ }
}
- return map;
+
+ if (order != null) {
+ Order o = null;
+ if (order == ReadingOrder.asc) {
+ o = Order.asc(FeedEntry_.updated.getName());
+ } else {
+ o = Order.desc(FeedEntry_.updated.getName());
+ }
+ criteria.addOrder(o);
+ }
+ if (offset > -1) {
+ criteria.setFirstResult(offset);
+ }
+ if (limit > -1) {
+ criteria.setMaxResults(limit);
+ }
+ int timeout = applicationSettingsService.get().getQueryTimeout();
+ if (timeout > 0) {
+ criteria.setTimeout(timeout);
+ }
+ return criteria;
}
- private List lazyLoadContent(boolean includeContent,
- List results) {
+ @SuppressWarnings("unchecked")
+ public List findBySubscriptions(List subscriptions, boolean unreadOnly, String keywords,
+ Date newerThan, int offset, int limit, ReadingOrder order, boolean includeContent) {
+
+ int capacity = offset + limit;
+ Comparator comparator = order == ReadingOrder.desc ? STATUS_COMPARATOR_DESC : STATUS_COMPARATOR_ASC;
+ FixedSizeSortedSet set = new FixedSizeSortedSet(capacity, comparator);
+ for (FeedSubscription sub : subscriptions) {
+ Date last = (order != null && set.isFull()) ? set.last().getEntryUpdated() : null;
+ Criteria criteria = buildSearchCriteria(sub, unreadOnly, keywords, newerThan, -1, capacity, order, includeContent, last);
+ criteria.setResultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP);
+ List