Java tutorial
/* * Copyright 2010 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.integration.twitter; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang.exception.ExceptionUtils; import org.springframework.context.Lifecycle; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.endpoint.AbstractEndpoint; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.twitter.oauth.OAuthConfiguration; import org.springframework.util.Assert; import twitter4j.RateLimitStatus; import twitter4j.ResponseList; import twitter4j.Twitter; /** * There are a lot of operations that are common to receiving the various types of messages when using the Twitter API, and this * class abstracts most of them for you. Implementers must take note of {@link org.springframework.integration.twitter.AbstractInboundTwitterEndpointSupport#runAsAPIRateLimitsPermit(org.springframework.integration.twitter.AbstractInboundTwitterEndpointSupport.ApiCallback)} * which will invoke the instance of {@link org.springframework.integration.twitter.AbstractInboundTwitterEndpointSupport.ApiCallback} when the rate-limit API * deems that its OK to do so. This class handles keeping tabs on that and on spacing out requests as required. * <p/> * Simialarly, this class handles keeping track on the latest inbound message its received and avoiding, where possible, redelivery of * common messages. This functionality is enabled using the {@link org.springframework.integration.context.metadata.MetadataPersister} implementation * * @author Josh Long * @since 2.0 */ public abstract class AbstractInboundTwitterEndpointSupport<T> extends AbstractEndpoint implements Lifecycle { protected volatile OAuthConfiguration configuration; protected final MessagingTemplate messagingTemplate = new MessagingTemplate(); private volatile MessageChannel requestChannel; protected volatile long markerId = -1; protected Twitter twitter; private final Object markerGuard = new Object(); private final Object apiPermitGuard = new Object(); @SuppressWarnings("unused") public void setConfiguration(OAuthConfiguration configuration) { this.configuration = configuration; } abstract protected void markLastStatusId(T statusId); abstract protected List<T> sort(List<T> rl); protected void forwardAll(ResponseList<T> tResponses) { List<T> stats = new ArrayList<T>(); for (T t : tResponses) stats.add(t); for (T twitterResponse : sort(stats)) forward(twitterResponse); } public long getMarkerId() { return markerId; } @Override protected void doStart() { try { refresh(); } catch (Exception e) { throw new RuntimeException(e); } } protected void forward(T status) { synchronized (this.markerGuard) { Message<T> twtMsg = MessageBuilder.withPayload(status).build(); messagingTemplate.send(requestChannel, twtMsg); markLastStatusId(status); } } @SuppressWarnings("unchecked") protected void runAsAPIRateLimitsPermit(ApiCallback cb) throws Exception { synchronized (this.apiPermitGuard) { while (waitUntilPullAvailable()) { if (logger.isDebugEnabled()) { logger.debug("have room to make an API request now"); } cb.run(this, twitter); } } } protected boolean handleReceivingRateLimitStatus(RateLimitStatus rateLimitStatus) { try { int secondsUntilReset = rateLimitStatus.getSecondsUntilReset(); int remainingHits = rateLimitStatus.getRemainingHits(); if (remainingHits == 0) { logger.debug("rate status limit service returned 0 for the remaining hits value"); return false; } if (secondsUntilReset == 0) { logger.debug("rate status limit service returned 0 for the seconds until reset period value"); return false; } int secondsUntilWeCanPullAgain = secondsUntilReset / remainingHits; long msUntilWeCanPullAgain = secondsUntilWeCanPullAgain * 1000; logger.debug("need to Thread.sleep() " + secondsUntilWeCanPullAgain + " seconds until the next timeline pull. Have " + remainingHits + " remaining pull this rate period. The period ends in " + secondsUntilReset); Thread.sleep(msUntilWeCanPullAgain); } catch (Throwable throwable) { logger.debug("encountered an error when" + " trying to refresh the timeline: " + ExceptionUtils.getFullStackTrace(throwable)); } return true; } protected boolean waitUntilPullAvailable() throws Exception { return this.handleReceivingRateLimitStatus(this.twitter.getRateLimitStatus()); } protected boolean hasMarkedStatus() { return markerId > -1; } abstract protected void refresh() throws Exception; @Override protected void onInit() throws Exception { messagingTemplate.afterPropertiesSet(); Assert.notNull(this.configuration, "'configuration' can't be null"); this.twitter = this.configuration.getTwitter(); Assert.notNull(this.twitter, "'twitter' instance can't be null"); } @Override protected void doStop() { } @SuppressWarnings("unused") public void setRequestChannel(MessageChannel requestChannel) { this.messagingTemplate.setDefaultChannel(requestChannel); this.requestChannel = requestChannel; } /** * Hook for clients to run logic when the API rate limiting lets us * <p/> * Simply register your callback using #runAsAPIRateLimitsPermit * * @param <C> */ public static interface ApiCallback<C> { void run(C t, Twitter twitter) throws Exception; } }