From bf90c6723fc3d027cd99458aa221a2b42541b8ce Mon Sep 17 00:00:00 2001 From: Costantino Perciante Date: Mon, 23 May 2016 15:43:44 +0000 Subject: [PATCH] Added cache that keeps hashtags for scope with a TTL of 5 minutes git-svn-id: http://svn.research-infrastructures.eu/public/d4science/gcube/trunk/portlets/user/top-topics@128785 82a268e6-3cf1-43bd-a215-b396298e98cf --- .../user/topics/server/TopicServiceImpl.java | 222 ++++++++++-------- .../topics/server/cache/CacheInterface.java | 26 ++ .../topics/server/cache/CacheUtilities.java | 26 ++ .../topics/server/cache/CacheValueBean.java | 40 ++++ .../topics/server/cache/TopicsCacheImpl.java | 77 ++++++ 5 files changed, 291 insertions(+), 100 deletions(-) create mode 100644 src/main/java/org/gcube/portlets/user/topics/server/cache/CacheInterface.java create mode 100644 src/main/java/org/gcube/portlets/user/topics/server/cache/CacheUtilities.java create mode 100644 src/main/java/org/gcube/portlets/user/topics/server/cache/CacheValueBean.java create mode 100644 src/main/java/org/gcube/portlets/user/topics/server/cache/TopicsCacheImpl.java diff --git a/src/main/java/org/gcube/portlets/user/topics/server/TopicServiceImpl.java b/src/main/java/org/gcube/portlets/user/topics/server/TopicServiceImpl.java index 477c9b1..ea0ed7b 100644 --- a/src/main/java/org/gcube/portlets/user/topics/server/TopicServiceImpl.java +++ b/src/main/java/org/gcube/portlets/user/topics/server/TopicServiceImpl.java @@ -21,6 +21,7 @@ import org.gcube.portal.databook.server.DBCassandraAstyanaxImpl; import org.gcube.portal.databook.server.DatabookStore; import org.gcube.portal.databook.shared.Feed; import org.gcube.portlets.user.topics.client.TopicService; +import org.gcube.portlets.user.topics.server.cache.TopicsCacheImpl; import org.gcube.portlets.user.topics.shared.HashTagAndOccurrence; import org.gcube.portlets.user.topics.shared.HashtagsWrapper; import org.gcube.vomanagement.usermanagement.GroupManager; @@ -43,7 +44,7 @@ public class TopicServiceImpl extends RemoteServiceServlet implements TopicServi public static final String TEST_USER = "test.user"; private static final int WINDOW_SIZE_IN_MONTHS = 6; // it must not exceed 12 - + /** * The Cassandra store interface */ @@ -87,130 +88,151 @@ public class TopicServiceImpl extends RemoteServiceServlet implements TopicServi */ @Override public HashtagsWrapper getHashtags() { - ArrayList hashtagsChart = new ArrayList<>(); + ArrayList hashtagsChart; ASLSession session = getASLSession(); - - long timestampStart = System.currentTimeMillis(); - - // get the reference time - Calendar referenceTime = Calendar.getInstance(); - int currentMonth = referenceTime.get(Calendar.MONTH); // jan = 0, ..... dec = 11 - referenceTime.set(Calendar.MONTH, currentMonth - WINDOW_SIZE_IN_MONTHS); // the year is automatically decreased if needed - - // print it - SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); - _log.debug("Reference time for trending topics is " + format.format(referenceTime.getTime())); String userName = session.getUsername(); boolean isInfrastructure = isInfrastructureScope(); - - try { - //in case the portal is restarted and you have the social home open it will get test.user (no callback to set session info) - //this check just return nothing if that happens - if (userName.compareTo("test.user") == 0) { - _log.debug("Found " + userName + " returning nothing"); - return null; - } - ArrayList toSort = new ArrayList(); - if (isInfrastructure) { - _log.debug("****** retrieving hashtags for user VREs"); + String currentScope = session.getScope(); - // different vres could have a same hashtag, we need to merge them - Map hashtags = new HashMap(); + long timestampStart = System.currentTimeMillis(); + + // get cache reference + TopicsCacheImpl topicsCache = TopicsCacheImpl.getCacheInstance(); + + hashtagsChart = topicsCache.get(currentScope); + + if(hashtagsChart != null){ - // we need a map for the couple - // it is needed because later we need to retrieve the most recent feed among the ones - // containing the hashtag itself - Map> hashtagsInVres = new HashMap>(); - - GroupManager gm = new LiferayGroupManager(); - UserManager um = new LiferayUserManager(); - GCubeUser user = um.getUserByUsername(userName); + _log.debug("TopTopics for scope " + currentScope + " retrieved from the cache"); + + } + else{ + + _log.debug("TopTopics for scope " + currentScope + " are not in the cache, evaluating them..."); + + // create array + hashtagsChart = new ArrayList(); + + // get the reference time + Calendar referenceTime = Calendar.getInstance(); + int currentMonth = referenceTime.get(Calendar.MONTH); // jan = 0, ..... dec = 11 + referenceTime.set(Calendar.MONTH, currentMonth - WINDOW_SIZE_IN_MONTHS); // the year is automatically decreased if needed - List groups = gm.listGroupsByUser(user.getUserId()); - for (GCubeGroup group : groups) { - if (gm.isVRE(group.getGroupId())) { - String vreid = gm.getInfrastructureScope(group.getGroupId()); //get the scope - Map map = store.getVREHashtagsWithOccurrenceFilteredByTime(vreid, referenceTime.getTimeInMillis()); + // print it + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + _log.debug("Reference time for trending topics is " + format.format(referenceTime.getTime())); - // merge the values if needed - for (String hashtag : map.keySet()) { + try { + //in case the portal is restarted and you have the social home open it will get test.user (no callback to set session info) + //this check just return nothing if that happens + if (userName.compareTo("test.user") == 0) { + _log.debug("Found " + userName + " returning nothing"); + return null; + } + ArrayList toSort = new ArrayList(); + if (isInfrastructure) { + _log.debug("****** retrieving hashtags for user VREs"); - if(hashtags.containsKey(hashtag)){ + // different vres could have a same hashtag, we need to merge them + Map hashtags = new HashMap(); - int currentValue = hashtags.get(hashtag); - int newValue = currentValue + map.get(hashtag); - - // remove and re-add - hashtags.remove(hashtag); - hashtags.put(hashtag, newValue); + // we need a map for the couple + // it is needed because later we need to retrieve the most recent feed among the ones + // containing the hashtag itself + Map> hashtagsInVres = new HashMap>(); - // get the current list of vres in which the hashtag is present and add this new one - List vres = hashtagsInVres.get(hashtag); - vres.add(vreid); - hashtagsInVres.remove(hashtag); - hashtagsInVres.put(hashtag, vres); + GroupManager gm = new LiferayGroupManager(); + UserManager um = new LiferayUserManager(); + GCubeUser user = um.getUserByUsername(userName); - }else{ + List groups = gm.listGroupsByUser(user.getUserId()); + for (GCubeGroup group : groups) { + if (gm.isVRE(group.getGroupId())) { + String vreid = gm.getInfrastructureScope(group.getGroupId()); //get the scope + Map map = store.getVREHashtagsWithOccurrenceFilteredByTime(vreid, referenceTime.getTimeInMillis()); - hashtags.put(hashtag, map.get(hashtag)); + // merge the values if needed + for (String hashtag : map.keySet()) { - // put in the hashmap hashtagsInVres too - List vres = new ArrayList(); - vres.add(vreid); - hashtagsInVres.put(hashtag, vres); + if(hashtags.containsKey(hashtag)){ + + int currentValue = hashtags.get(hashtag); + int newValue = currentValue + map.get(hashtag); + + // remove and re-add + hashtags.remove(hashtag); + hashtags.put(hashtag, newValue); + + // get the current list of vres in which the hashtag is present and add this new one + List vres = hashtagsInVres.get(hashtag); + vres.add(vreid); + hashtagsInVres.remove(hashtag); + hashtagsInVres.put(hashtag, vres); + + }else{ + + hashtags.put(hashtag, map.get(hashtag)); + + // put in the hashmap hashtagsInVres too + List vres = new ArrayList(); + vres.add(vreid); + hashtagsInVres.put(hashtag, vres); + } } } } + + // now we need to evaluate score for each element + Map weights = evaluateWeight(hashtags, WINDOW_SIZE_IN_MONTHS, currentMonth, referenceTime, null, hashtagsInVres); + + // at the end build the list + for (String hashtag : hashtags.keySet()) { + toSort.add(new HashTagAndOccurrence(hashtag, hashtags.get(hashtag), weights.get(hashtag))); + } + + } + //else must be in a VRE scope + else { + String scope = session.getScope(); + _log.debug("****** retrieving hashtags for scope " + scope); + Map hashtags = store.getVREHashtagsWithOccurrenceFilteredByTime(scope, referenceTime.getTimeInMillis()); + // now we need to evaluate the wiehgt for each element + Map weights = evaluateWeight(hashtags, WINDOW_SIZE_IN_MONTHS, currentMonth, referenceTime, scope, null); + for (String hashtag : hashtags.keySet()) { + toSort.add(new HashTagAndOccurrence(hashtag, hashtags.get(hashtag), weights.get(hashtag))); + } } - // now we need to evaluate score for each element - Map weights = evaluateWeight(hashtags, WINDOW_SIZE_IN_MONTHS, currentMonth, referenceTime, null, hashtagsInVres); + _log.debug("Number of topics retrieved is " + toSort.size()); - // at the end build the list - for (String hashtag : hashtags.keySet()) { - toSort.add(new HashTagAndOccurrence(hashtag, hashtags.get(hashtag), weights.get(hashtag))); + Collections.sort(toSort); // sort for weight + + for (HashTagAndOccurrence wrapper : toSort) { + + _log.debug("Entry is " + wrapper.toString() + " with weight " + wrapper.getWeight()); + + String hashtag = wrapper.getHashtag(); + + String href="\"?"+ + new String(Base64.encodeBase64(GCubeSocialNetworking.HASHTAG_OID.getBytes()))+"="+ + new String(Base64.encodeBase64(hashtag.getBytes()))+"\""; + String hashtagLink = ""+hashtag+""; + hashtagsChart.add(hashtagLink); } - + + // update the cache + topicsCache.insert(currentScope, hashtagsChart); } - //else must be in a VRE scope - else { - String scope = session.getScope(); - _log.debug("****** retrieving hashtags for scope " + scope); - Map hashtags = store.getVREHashtagsWithOccurrenceFilteredByTime(scope, referenceTime.getTimeInMillis()); - // now we need to evaluate the wiehgt for each element - Map weights = evaluateWeight(hashtags, WINDOW_SIZE_IN_MONTHS, currentMonth, referenceTime, scope, null); - for (String hashtag : hashtags.keySet()) { - toSort.add(new HashTagAndOccurrence(hashtag, hashtags.get(hashtag), weights.get(hashtag))); - } - } - - _log.debug("Number of topics retrieved is " + toSort.size()); - - Collections.sort(toSort); // sort for weight - - for (HashTagAndOccurrence wrapper : toSort) { - - _log.debug("Entry is " + wrapper.toString() + " with weight " + wrapper.getWeight()); - - String hashtag = wrapper.getHashtag(); - - String href="\"?"+ - new String(Base64.encodeBase64(GCubeSocialNetworking.HASHTAG_OID.getBytes()))+"="+ - new String(Base64.encodeBase64(hashtag.getBytes()))+"\""; - String hashtagLink = ""+hashtag+""; - hashtagsChart.add(hashtagLink); + catch (Exception e) { + e.printStackTrace(); + return null; } } - catch (Exception e) { - e.printStackTrace(); - return null; - } - - + long timestampEnd = System.currentTimeMillis() - timestampStart; - _log.debug("Overall time to retrieve hastags is " + timestampEnd); - + _log.debug("Overall time to retrieve hastags is " + timestampEnd + "ms"); + return new HashtagsWrapper(isInfrastructure, hashtagsChart); } diff --git a/src/main/java/org/gcube/portlets/user/topics/server/cache/CacheInterface.java b/src/main/java/org/gcube/portlets/user/topics/server/cache/CacheInterface.java new file mode 100644 index 0000000..7e7fa82 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/topics/server/cache/CacheInterface.java @@ -0,0 +1,26 @@ +package org.gcube.portlets.user.topics.server.cache; + +/** + * Cache interface + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + * @param the key type + * @param the value type + */ +public interface CacheInterface { + + /** + * Retrieve a value V from the cache + * @param key + * @return + */ + public V get(K key); + + /** + * Insert an object V with key K into the cache + * @param key + * @param value + * @return + */ + public boolean insert(K key, V value); + +} diff --git a/src/main/java/org/gcube/portlets/user/topics/server/cache/CacheUtilities.java b/src/main/java/org/gcube/portlets/user/topics/server/cache/CacheUtilities.java new file mode 100644 index 0000000..13364b4 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/topics/server/cache/CacheUtilities.java @@ -0,0 +1,26 @@ +package org.gcube.portlets.user.topics.server.cache; + +/** + * Utility functions for caches + * @author Costantino Perciante at ISTI-CNR + * (costantino.perciante@isti.cnr.it) + * + */ +public class CacheUtilities { + /** + * Check if the bean expired + * @param beanTimestamp + * @param ttl + * @return if expired, otherwise + */ + public static boolean expired(long beanTimestamp, long ttl){ + + long currentTime = System.currentTimeMillis(); + + if((beanTimestamp + ttl) <= currentTime) + return true; + else + return false; + + } +} diff --git a/src/main/java/org/gcube/portlets/user/topics/server/cache/CacheValueBean.java b/src/main/java/org/gcube/portlets/user/topics/server/cache/CacheValueBean.java new file mode 100644 index 0000000..b51e1fb --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/topics/server/cache/CacheValueBean.java @@ -0,0 +1,40 @@ +package org.gcube.portlets.user.topics.server.cache; + +/** + * A bean object to be used as value within caches. It contains a TTL value too. + * @author Costantino Perciante at ISTI-CNR + * (costantino.perciante@isti.cnr.it) + * + * @param the value type + */ +public class CacheValueBean { + + private V value; + private long TTL; + + /** + * @param value + * @param tTL + */ + public CacheValueBean(V value, long ttl) { + super(); + this.value = value; + this.TTL = ttl; + } + public V getValue() { + return value; + } + public void setValue(V value) { + this.value = value; + } + public long getTTL() { + return TTL; + } + public void setTTL(long ttl) { + this.TTL = ttl; + } + @Override + public String toString() { + return "CacheValueBean [value=" + value + ", TTL=" + TTL + "]"; + } +} diff --git a/src/main/java/org/gcube/portlets/user/topics/server/cache/TopicsCacheImpl.java b/src/main/java/org/gcube/portlets/user/topics/server/cache/TopicsCacheImpl.java new file mode 100644 index 0000000..2e1ede3 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/topics/server/cache/TopicsCacheImpl.java @@ -0,0 +1,77 @@ +package org.gcube.portlets.user.topics.server.cache; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.LoggerFactory; +/** + * TopTopics cache: the key of each value is the scope + * @author Costantino Perciante at ISTI-CNR + * (costantino.perciante@isti.cnr.it) + */ +public class TopicsCacheImpl implements CacheInterface> { + + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(TopicsCacheImpl.class); + + private static TopicsCacheImpl instance = new TopicsCacheImpl(); + + /** + * The hashmap + */ + private Map>> cacheMap; + + /** + * Cache entry expires after EXPIRED_AFTER ms + */ + private static final long EXPIRED_AFTER = 1000 * 60 * 2; + + /** + * private constructor + */ + private TopicsCacheImpl(){ + + this.cacheMap = new HashMap>>(); + + } + + /** + * Retrieve the current cache instance object + * @return + */ + public static TopicsCacheImpl getCacheInstance(){ + + return instance; + + } + + @Override + public ArrayList get(String key) { + if(cacheMap.containsKey(key)){ + + CacheValueBean> bean = cacheMap.get(key); + + if(CacheUtilities.expired(bean.getTTL(), EXPIRED_AFTER)){ + cacheMap.remove(key); + logger.debug("TopTopics for scope " + key + "expired, return null"); + } + else + return bean.getValue(); + } + + return null; + } + + @Override + public boolean insert(String key, ArrayList value) { + CacheValueBean> newBean = new CacheValueBean>(value, System.currentTimeMillis()); + + if(cacheMap.containsKey(key)) + cacheMap.remove(key); + + cacheMap.put(key, newBean); + + return true; + } + +}