From ef4dfe2ff5605faf3aacf6515a2af40cea390cb6 Mon Sep 17 00:00:00 2001 From: Athou Date: Wed, 3 Jul 2013 12:42:01 +0200 Subject: [PATCH] cache unread count --- .../commafeed/backend/cache/CacheService.java | 7 ++- .../backend/cache/NoopCacheService.java | 14 ++++- .../backend/cache/RedisCacheService.java | 51 ++++++++++++++++++- .../commafeed/backend/feeds/OPMLImporter.java | 2 +- .../services/FeedSubscriptionService.java | 26 ++++++++-- .../backend/services/FeedUpdateService.java | 2 +- .../frontend/rest/resources/CategoryREST.java | 21 ++++---- .../frontend/rest/resources/EntryREST.java | 2 +- .../frontend/rest/resources/FeedREST.java | 10 ++-- .../frontend/rest/resources/UserREST.java | 2 +- 10 files changed, 112 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/commafeed/backend/cache/CacheService.java b/src/main/java/com/commafeed/backend/cache/CacheService.java index 401c964a..232cd1b9 100644 --- a/src/main/java/com/commafeed/backend/cache/CacheService.java +++ b/src/main/java/com/commafeed/backend/cache/CacheService.java @@ -1,6 +1,7 @@ package com.commafeed.backend.cache; import java.util.List; +import java.util.Map; import org.apache.commons.codec.digest.DigestUtils; @@ -23,7 +24,11 @@ public abstract class CacheService { public abstract Category getRootCategory(User user); public abstract void setRootCategory(User user, Category category); + + public abstract Map getUnreadCounts(User user); - public abstract void invalidateRootCategory(User... users); + public abstract void setUnreadCounts(User user, Map map); + + public abstract void invalidateUserData(User... users); } diff --git a/src/main/java/com/commafeed/backend/cache/NoopCacheService.java b/src/main/java/com/commafeed/backend/cache/NoopCacheService.java index 3197132a..cd2b841f 100644 --- a/src/main/java/com/commafeed/backend/cache/NoopCacheService.java +++ b/src/main/java/com/commafeed/backend/cache/NoopCacheService.java @@ -2,6 +2,7 @@ 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; @@ -34,7 +35,18 @@ public class NoopCacheService extends CacheService { } @Override - public void invalidateRootCategory(User... users) { + public Map getUnreadCounts(User user) { + return null; + } + + @Override + public void setUnreadCounts(User user, Map map) { } + + @Override + public void invalidateUserData(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 d3cd5d8f..231b6272 100644 --- a/src/main/java/com/commafeed/backend/cache/RedisCacheService.java +++ b/src/main/java/com/commafeed/backend/cache/RedisCacheService.java @@ -1,6 +1,7 @@ package com.commafeed.backend.cache; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -21,13 +22,15 @@ 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 JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost"); private ObjectMapper mapper = new ObjectMapper(); @@ -103,7 +106,45 @@ public class RedisCacheService extends CacheService { } @Override - public void invalidateRootCategory(User... users) { + public Map getUnreadCounts(User user) { + Map map = 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); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + pool.returnResource(jedis); + } + return map; + } + + @Override + public void setUnreadCounts(User user, Map map) { + Jedis jedis = pool.getResource(); + try { + String key = buildRedisUnreadCountKey(user); + + Pipeline pipe = jedis.pipelined(); + pipe.del(key); + pipe.set(key, mapper.writeValueAsString(map)); + pipe.expire(key, (int) TimeUnit.MINUTES.toSeconds(30)); + pipe.sync(); + } catch (JsonProcessingException e) { + log.error(e.getMessage(), e); + } finally { + pool.returnResource(jedis); + } + } + + @Override + public void invalidateUserData(User... users) { Jedis jedis = pool.getResource(); try { Pipeline pipe = jedis.pipelined(); @@ -111,6 +152,8 @@ public class RedisCacheService extends CacheService { for (User user : users) { String key = buildRedisRootCategoryKey(user); pipe.del(key); + key = buildRedisUnreadCountKey(user); + pipe.del(key); } } pipe.sync(); @@ -123,6 +166,10 @@ public class RedisCacheService extends CacheService { return "root_cat:" + Models.getId(user); } + private String buildRedisUnreadCountKey(User user) { + return "unread_count:" + Models.getId(user); + } + private String buildRedisEntryKey(Feed feed) { return "feed:" + feed.getId(); } diff --git a/src/main/java/com/commafeed/backend/feeds/OPMLImporter.java b/src/main/java/com/commafeed/backend/feeds/OPMLImporter.java index 6857a539..3ef0bc8f 100644 --- a/src/main/java/com/commafeed/backend/feeds/OPMLImporter.java +++ b/src/main/java/com/commafeed/backend/feeds/OPMLImporter.java @@ -92,6 +92,6 @@ public class OPMLImporter { log.error("error while importing {}: {}", outline.getXmlUrl(), e.getMessage()); } } - cache.invalidateRootCategory(user); + cache.invalidateUserData(user); } } diff --git a/src/main/java/com/commafeed/backend/services/FeedSubscriptionService.java b/src/main/java/com/commafeed/backend/services/FeedSubscriptionService.java index 4ec1ea25..d33d140d 100644 --- a/src/main/java/com/commafeed/backend/services/FeedSubscriptionService.java +++ b/src/main/java/com/commafeed/backend/services/FeedSubscriptionService.java @@ -1,6 +1,7 @@ package com.commafeed.backend.services; import java.util.List; +import java.util.Map; import javax.ejb.ApplicationException; import javax.inject.Inject; @@ -9,6 +10,7 @@ import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.commafeed.backend.cache.CacheService; import com.commafeed.backend.dao.FeedEntryDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO; @@ -19,12 +21,14 @@ import com.commafeed.backend.model.FeedCategory; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntryStatus; import com.commafeed.backend.model.FeedSubscription; +import com.commafeed.backend.model.Models; import com.commafeed.backend.model.User; import com.google.api.client.util.Lists; public class FeedSubscriptionService { - private static Logger log = LoggerFactory.getLogger(FeedSubscriptionService.class); + private static Logger log = LoggerFactory + .getLogger(FeedSubscriptionService.class); @SuppressWarnings("serial") @ApplicationException @@ -52,6 +56,9 @@ public class FeedSubscriptionService { @Inject FeedRefreshTaskGiver taskGiver; + @Inject + CacheService cache; + public Feed subscribe(User user, String url, String title, FeedCategory category) { @@ -83,7 +90,8 @@ public class FeedSubscriptionService { if (newSubscription) { try { List statuses = Lists.newArrayList(); - List allEntries = feedEntryDAO.findByFeed(feed, 0, 10); + List allEntries = feedEntryDAO.findByFeed(feed, 0, + 10); for (FeedEntry entry : allEntries) { FeedEntryStatus status = new FeedEntryStatus(); status.setEntry(entry); @@ -93,10 +101,22 @@ public class FeedSubscriptionService { } feedEntryStatusDAO.saveOrUpdate(statuses); } catch (Exception e) { - log.error("could not fetch initial statuses when importing {} : {}", feed.getUrl(), e.getMessage()); + log.error( + "could not fetch initial statuses when importing {} : {}", + feed.getUrl(), e.getMessage()); } } taskGiver.add(feed); return feed; } + + public Map getUnreadCount(User user) { + Map map = cache.getUnreadCounts(user); + if (map == null) { + log.debug("unread count cache miss for {}", Models.getId(user)); + map = feedEntryStatusDAO.getUnreadCount(user); + cache.setUnreadCounts(user, map); + } + return map; + } } diff --git a/src/main/java/com/commafeed/backend/services/FeedUpdateService.java b/src/main/java/com/commafeed/backend/services/FeedUpdateService.java index 89c728b8..a592b986 100644 --- a/src/main/java/com/commafeed/backend/services/FeedUpdateService.java +++ b/src/main/java/com/commafeed/backend/services/FeedUpdateService.java @@ -75,7 +75,7 @@ public class FeedUpdateService { users.add(sub.getUser()); } - cache.invalidateRootCategory(users.toArray(new User[0])); + cache.invalidateUserData(users.toArray(new User[0])); feedEntryDAO.saveOrUpdate(update); feedEntryStatusDAO.saveOrUpdate(statusUpdateList); metricsBean.entryUpdated(statusUpdateList.size()); diff --git a/src/main/java/com/commafeed/frontend/rest/resources/CategoryREST.java b/src/main/java/com/commafeed/frontend/rest/resources/CategoryREST.java index 59c963f7..557902ee 100644 --- a/src/main/java/com/commafeed/frontend/rest/resources/CategoryREST.java +++ b/src/main/java/com/commafeed/frontend/rest/resources/CategoryREST.java @@ -33,6 +33,7 @@ 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; +import com.commafeed.backend.services.FeedSubscriptionService; import com.commafeed.frontend.SecurityCheck; import com.commafeed.frontend.model.Category; import com.commafeed.frontend.model.Entries; @@ -73,6 +74,9 @@ public class CategoryREST extends AbstractResourceREST { @Inject FeedSubscriptionDAO feedSubscriptionDAO; + @Inject + FeedSubscriptionService feedSubscriptionService; + @Inject CacheService cache; @@ -231,7 +235,7 @@ public class CategoryREST extends AbstractResourceREST { feedEntryStatusDAO.markCategoryEntries(getUser(), categories, olderThan); } - cache.invalidateRootCategory(getUser()); + cache.invalidateUserData(getUser()); return Response.ok(Status.OK).build(); } @@ -254,7 +258,7 @@ public class CategoryREST extends AbstractResourceREST { cat.setParent(parent); } feedCategoryDAO.saveOrUpdate(cat); - cache.invalidateRootCategory(getUser()); + cache.invalidateUserData(getUser()); return Response.ok().build(); } @@ -285,7 +289,7 @@ public class CategoryREST extends AbstractResourceREST { feedCategoryDAO.saveOrUpdate(categories); feedCategoryDAO.delete(cat); - cache.invalidateRootCategory(getUser()); + cache.invalidateUserData(getUser()); return Response.ok().build(); } else { return Response.status(Status.NOT_FOUND).build(); @@ -350,7 +354,7 @@ public class CategoryREST extends AbstractResourceREST { } feedCategoryDAO.saveOrUpdate(category); - cache.invalidateRootCategory(getUser()); + cache.invalidateUserData(getUser()); return Response.ok(Status.OK).build(); } @@ -368,7 +372,7 @@ public class CategoryREST extends AbstractResourceREST { } category.setCollapsed(req.isCollapse()); feedCategoryDAO.saveOrUpdate(category); - cache.invalidateRootCategory(getUser()); + cache.invalidateUserData(getUser()); return Response.ok(Status.OK).build(); } @@ -377,7 +381,7 @@ public class CategoryREST extends AbstractResourceREST { @ApiOperation(value = "Get unread count for feed subscriptions", responseClass = "List[com.commafeed.frontend.model.UnreadCount]") public Response getUnreadCount() { List list = Lists.newArrayList(); - Map unreadCount = feedEntryStatusDAO + Map unreadCount = feedSubscriptionService .getUnreadCount(getUser()); for (Map.Entry e : unreadCount.entrySet()) { list.add(new UnreadCount(e.getKey(), e.getValue())); @@ -397,11 +401,10 @@ public class CategoryREST extends AbstractResourceREST { List categories = feedCategoryDAO.findAll(user); List subscriptions = feedSubscriptionDAO .findAll(getUser()); - Map unreadCount = feedEntryStatusDAO + Map unreadCount = feedSubscriptionService .getUnreadCount(getUser()); - root = buildCategory(null, categories, subscriptions, - unreadCount); + root = buildCategory(null, categories, subscriptions, unreadCount); root.setId("all"); root.setName("All"); cache.setRootCategory(user, root); diff --git a/src/main/java/com/commafeed/frontend/rest/resources/EntryREST.java b/src/main/java/com/commafeed/frontend/rest/resources/EntryREST.java index bf07a76e..2e508732 100644 --- a/src/main/java/com/commafeed/frontend/rest/resources/EntryREST.java +++ b/src/main/java/com/commafeed/frontend/rest/resources/EntryREST.java @@ -51,7 +51,7 @@ public class EntryREST extends AbstractResourceREST { feedEntryService.markEntry(getUser(), Long.valueOf(req.getId()), req.getFeedId(), req.isRead()); - cache.invalidateRootCategory(getUser()); + cache.invalidateUserData(getUser()); return Response.ok(Status.OK).build(); } diff --git a/src/main/java/com/commafeed/frontend/rest/resources/FeedREST.java b/src/main/java/com/commafeed/frontend/rest/resources/FeedREST.java index ac9ccd2f..15058f0f 100644 --- a/src/main/java/com/commafeed/frontend/rest/resources/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/rest/resources/FeedREST.java @@ -292,7 +292,7 @@ public class FeedREST extends AbstractResourceREST { if (subscription != null) { feedEntryStatusDAO.markSubscriptionEntries(subscription, olderThan); } - cache.invalidateRootCategory(getUser()); + cache.invalidateUserData(getUser()); return Response.ok(Status.OK).build(); } @@ -379,7 +379,7 @@ public class FeedREST extends AbstractResourceREST { .entity("Failed to subscribe to URL " + url + ": " + e.getMessage()).build(); } - cache.invalidateRootCategory(getUser()); + cache.invalidateUserData(getUser()); return Response.ok(Status.OK).build(); } @@ -424,7 +424,7 @@ public class FeedREST extends AbstractResourceREST { req.getId()); if (sub != null) { feedSubscriptionDAO.delete(sub); - cache.invalidateRootCategory(getUser()); + cache.invalidateUserData(getUser()); return Response.ok(Status.OK).build(); } else { return Response.status(Status.NOT_FOUND).build(); @@ -484,7 +484,7 @@ public class FeedREST extends AbstractResourceREST { } else { feedSubscriptionDAO.saveOrUpdate(subscription); } - cache.invalidateRootCategory(getUser()); + cache.invalidateUserData(getUser()); return Response.ok(Status.OK).build(); } @@ -523,7 +523,7 @@ public class FeedREST extends AbstractResourceREST { .status(Status.INTERNAL_SERVER_ERROR) .entity(e.getMessage()).build()); } - cache.invalidateRootCategory(getUser()); + cache.invalidateUserData(getUser()); return Response.temporaryRedirect( URI.create(applicationSettingsService.get().getPublicUrl())) .build(); diff --git a/src/main/java/com/commafeed/frontend/rest/resources/UserREST.java b/src/main/java/com/commafeed/frontend/rest/resources/UserREST.java index a4c56519..2737f4ff 100644 --- a/src/main/java/com/commafeed/frontend/rest/resources/UserREST.java +++ b/src/main/java/com/commafeed/frontend/rest/resources/UserREST.java @@ -198,7 +198,7 @@ public class UserREST extends AbstractResourceREST { return Response.status(Status.FORBIDDEN).build(); } userService.unregister(getUser()); - cache.invalidateRootCategory(getUser()); + cache.invalidateUserData(getUser()); return Response.ok().build(); } }