package org.karmaexchange.task; import static org.karmaexchange.util.OfyService.ofy; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Map; import lombok.Data; import org.apache.commons.lang3.time.DateUtils; import org.karmaexchange.dao.BaseDao; import org.karmaexchange.dao.Leaderboard; import org.karmaexchange.dao.Organization; import org.karmaexchange.dao.User; import org.karmaexchange.dao.Leaderboard.LeaderboardType; import org.karmaexchange.task.LeaderboardMapper.UserKarmaRecord; import org.karmaexchange.util.AdminUtil; import org.karmaexchange.util.UserService; import org.karmaexchange.util.AdminUtil.AdminTaskType; import com.google.appengine.tools.mapreduce.Reducer; import com.google.appengine.tools.mapreduce.ReducerInput; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.googlecode.objectify.Key; public class LeaderboardReducer extends Reducer<Key<Organization>, UserKarmaRecord, Void> { private static final long serialVersionUID = 1L; private static final int MAX_LEADERBOARD_SIZE = 10; @Override public void reduce(Key<Organization> orgKey, ReducerInput<UserKarmaRecord> userKarmaRecords) { AdminUtil.setCurrentUser(AdminTaskType.MAP_REDUCE); try { reduceAsAdmin(orgKey, userKarmaRecords); } finally { UserService.clearCurrentUser(); } } private void reduceAsAdmin(Key<Organization> orgKey, ReducerInput<UserKarmaRecord> userKarmaRecords) { Date now = new Date(); Date thirtyDayCutOff = DateUtils.addDays(now, -30); Map<Key<User>, LeaderboardScore> allTimeLeaderboardMap = Maps.newHashMap(); Map<Key<User>, LeaderboardScore> thirtyDayLeaderboardMap = Maps.newHashMap(); while (userKarmaRecords.hasNext()) { UserKarmaRecord record = userKarmaRecords.next(); addToLeaderboardMap(allTimeLeaderboardMap, record); if (record.getEventEndTime().after(thirtyDayCutOff)) { addToLeaderboardMap(thirtyDayLeaderboardMap, record); } } List<LeaderboardScore> sortedAllTimeLeaderboardScores = sortAndTrimLeaderboard(allTimeLeaderboardMap); List<LeaderboardScore> sortedThirtyDayLeaderboardScores = sortAndTrimLeaderboard(thirtyDayLeaderboardMap); // Load the user objects asynchronously. Map<Key<User>, User> allTimeLeaderboardUsers = fetchLeaderboardUsers(sortedAllTimeLeaderboardScores); Map<Key<User>, User> thirtyDayLeaderboardUsers = fetchLeaderboardUsers(sortedThirtyDayLeaderboardScores); persistLeaderboard(orgKey, sortedAllTimeLeaderboardScores, allTimeLeaderboardUsers, LeaderboardType.ALL_TIME); persistLeaderboard(orgKey, sortedThirtyDayLeaderboardScores, thirtyDayLeaderboardUsers, LeaderboardType.THIRTY_DAY); } private void addToLeaderboardMap(Map<Key<User>, LeaderboardScore> leaderboardMap, UserKarmaRecord record) { LeaderboardScore score = leaderboardMap.get(record.getUserKey()); if (score == null) { leaderboardMap.put(record.getUserKey(), new LeaderboardScore(record)); } else { score.add(record); } } private List<LeaderboardScore> sortAndTrimLeaderboard( Map<Key<User>, LeaderboardScore> leaderboadMap) { List<LeaderboardScore> sortedScores = Lists.newArrayList(leaderboadMap.values()); Collections.sort(sortedScores, LeaderboardScore.KarmaPointsComparator.INSTANCE); if (sortedScores.size() > MAX_LEADERBOARD_SIZE) { sortedScores.subList(MAX_LEADERBOARD_SIZE, sortedScores.size()).clear(); } return sortedScores; } private Map<Key<User>, User> fetchLeaderboardUsers(List<LeaderboardScore> scores) { List<Key<User>> userKeys = Lists.newArrayList(); for (LeaderboardScore score : scores) { userKeys.add(score.getUserKey()); } return ofy().load().keys(userKeys); } private void persistLeaderboard(Key<Organization> orgKey, List<LeaderboardScore> sortedScores, Map<Key<User>, User> usersMap, LeaderboardType type) { BaseDao.upsert(new Leaderboard(orgKey, type, sortedScores, usersMap)); } @Data public static class LeaderboardScore { private final Key<User> userKey; private long leaderboardKarmaPoints; public LeaderboardScore(UserKarmaRecord record) { userKey = record.getUserKey(); leaderboardKarmaPoints = record.getEventKarmaPoints(); } public void add(UserKarmaRecord record) { leaderboardKarmaPoints += record.getEventKarmaPoints(); } public static class KarmaPointsComparator implements Comparator<LeaderboardScore> { public static final KarmaPointsComparator INSTANCE = new KarmaPointsComparator(); @Override public int compare(LeaderboardScore score1, LeaderboardScore score2) { // Higher scored items come first. return Long.compare(score2.leaderboardKarmaPoints, score1.leaderboardKarmaPoints); } } } }