package nl.topicus.onderwijs.dashboard.modules.twitter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NavigableSet; import java.util.TreeSet; import java.util.concurrent.TimeUnit; import nl.topicus.onderwijs.dashboard.config.ISettings; import nl.topicus.onderwijs.dashboard.datasources.TwitterMentions; import nl.topicus.onderwijs.dashboard.datasources.TwitterTimeline; import nl.topicus.onderwijs.dashboard.datatypes.TwitterStatus; import nl.topicus.onderwijs.dashboard.keys.Key; import nl.topicus.onderwijs.dashboard.modules.AbstractService; import nl.topicus.onderwijs.dashboard.modules.DashboardRepository; import nl.topicus.onderwijs.dashboard.modules.ServiceConfiguration; import nl.topicus.onderwijs.dashboard.modules.twitter.TwitterSettings.OAuthKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import twitter4j.RateLimitStatus; import twitter4j.ResponseList; import twitter4j.Status; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.TwitterFactory; import twitter4j.auth.Authorization; import twitter4j.auth.AuthorizationFactory; import twitter4j.conf.Configuration; import twitter4j.conf.ConfigurationBuilder; @Service @ServiceConfiguration(interval = 1, unit = TimeUnit.MINUTES) public class TwitterService extends AbstractService { private static final Logger logger = LoggerFactory .getLogger(TwitterService.class); private Map<Key, List<Twitter>> twitters = new HashMap<Key, List<Twitter>>(); private Map<Key, NavigableSet<Status>> timeline = new HashMap<Key, NavigableSet<Status>>(); private Map<Key, NavigableSet<Status>> mentions = new HashMap<Key, NavigableSet<Status>>(); @Autowired public TwitterService(ISettings settings) { super(settings); } public void onConfigure(DashboardRepository repository) { for (Entry<Key, Map<String, ?>> settingsEntry : getSettings() .getServiceSettings(TwitterService.class).entrySet()) { Key key = settingsEntry.getKey(); TwitterSettings settings = new TwitterSettings( settingsEntry.getValue()); String oAuthConsumerKey = settings.getApplicationKey().getKey(); String oAuthConsumerSecret = settings.getApplicationKey() .getSecret(); List<Twitter> keyTwitters = new ArrayList<Twitter>(); for (Entry<String, OAuthKey> tokenEntry : settings.getTokens() .entrySet()) { OAuthKey token = tokenEntry.getValue(); ConfigurationBuilder builder = new ConfigurationBuilder(); builder.setOAuthConsumerKey(oAuthConsumerKey); builder.setOAuthConsumerSecret(oAuthConsumerSecret); builder.setOAuthAccessToken(token.getKey()); builder.setOAuthAccessTokenSecret(token.getSecret()); Configuration conf = builder.build(); Authorization authorization = AuthorizationFactory .getInstance(conf); Twitter twitter = new TwitterFactory() .getInstance(authorization); keyTwitters.add(twitter); } twitters.put(key, keyTwitters); mentions.put(key, new TreeSet<Status>()); timeline.put(key, new TreeSet<Status>()); repository.addDataSource(key, TwitterTimeline.class, new TwitterTimelineImpl(key, this)); repository.addDataSource(key, TwitterMentions.class, new TwitterMentionsImpl(key, this)); } } @Override public void refreshData() { for (Entry<Key, List<Twitter>> twitterEntry : twitters.entrySet()) { for (Twitter twitter : twitterEntry.getValue()) { pullTweets(twitterEntry.getKey(), twitter); } } } private void pullTweets(Key key, Twitter twitter) { try { RateLimitStatus rateLimitStatus = twitter.getRateLimitStatus(); if (rateLimitStatus.getRemainingHits() == 0) return; double currentRate = (rateLimitStatus.getHourlyLimit() - rateLimitStatus .getRemainingHits()) / (3601 - rateLimitStatus.getSecondsUntilReset()); logger.info( "Current twitter refresh rate: {}/h, official refresh rate: {}", String.format("%1.1f", currentRate), rateLimitStatus.getHourlyLimit()); if (currentRate > rateLimitStatus.getHourlyLimit()) { logger.info("Skipped refreshing Twitter feeds to limit the refresh rate"); return; } ResponseList<Status> newFriendsTimeline = twitter.getHomeTimeline(); ResponseList<Status> newMentions = twitter.getMentions(); synchronized (this) { mergeStatuses(timeline.get(key), newFriendsTimeline); mergeStatuses(mentions.get(key), newMentions); } } catch (TwitterException e) { logger.warn("Twitter reported an error: " + e.getMessage(), e); } } private void mergeStatuses(NavigableSet<Status> originalStatuses, List<Status> newStatuses) { originalStatuses.addAll(newStatuses); while (originalStatuses.size() >= 40) { originalStatuses.pollFirst(); } } public synchronized List<TwitterStatus> getTimeline(Key key) { List<TwitterStatus> ret = new ArrayList<TwitterStatus>(); for (Status curStatus : timeline.get(key)) { ret.add(new TwitterStatus(key, curStatus)); } Collections.reverse(ret); return ret; } public synchronized List<TwitterStatus> getMentions(Key key) { List<TwitterStatus> ret = new ArrayList<TwitterStatus>(); for (Status curStatus : mentions.get(key)) { ret.add(new TwitterStatus(key, curStatus)); } Collections.reverse(ret); return ret; } }