INT-1604 encapsulation
This commit is contained in:
@@ -42,9 +42,8 @@ import org.springframework.util.StringUtils;
|
||||
* Abstract class that defines common operations for receiving various types of
|
||||
* messages when using the Twitter API. This class also handles keeping track of
|
||||
* the latest inbound message it has received and avoiding, where possible,
|
||||
* redelivery of common messages. This functionality is enabled using the
|
||||
* {@link org.springframework.integration.store.MetadataStore}
|
||||
* strategy.
|
||||
* redelivery of duplicate messages. This functionality is enabled using the
|
||||
* {@link org.springframework.integration.store.MetadataStore} strategy.
|
||||
*
|
||||
* @author Josh Long
|
||||
* @author Oleg Zhurakousky
|
||||
@@ -58,25 +57,29 @@ public abstract class AbstractTwitterMessageSource<T> extends AbstractEndpoint i
|
||||
|
||||
private volatile String metadataKey;
|
||||
|
||||
protected final Queue<Tweet> tweets = new LinkedBlockingQueue<Tweet>();
|
||||
private final Queue<Tweet> tweets = new LinkedBlockingQueue<Tweet>();
|
||||
|
||||
protected volatile int prefetchThreshold = 0;
|
||||
private volatile int prefetchThreshold = 0;
|
||||
|
||||
protected volatile long markerId = -1;
|
||||
private volatile long markerId = -1;
|
||||
|
||||
protected volatile long processedId = -1;
|
||||
//private volatile long processedId = -1;
|
||||
|
||||
protected final TwitterOperations twitter;
|
||||
private final TwitterOperations twitterOperations;
|
||||
|
||||
private volatile ScheduledFuture<?> twitterUpdatePollingTask;
|
||||
private final TweetComparator tweetComparator = new TweetComparator();
|
||||
|
||||
private volatile ScheduledFuture<?> twitterPollingTask;
|
||||
|
||||
private final Object markerGuard = new Object();
|
||||
|
||||
|
||||
public AbstractTwitterMessageSource(TwitterOperations twitter){
|
||||
this.twitter = twitter;
|
||||
public AbstractTwitterMessageSource(TwitterOperations twitterOperations) {
|
||||
Assert.notNull(twitterOperations, "twitterOperations must not be null");
|
||||
this.twitterOperations = twitterOperations;
|
||||
}
|
||||
|
||||
|
||||
public long getMarkerId() {
|
||||
return this.markerId;
|
||||
}
|
||||
@@ -85,20 +88,20 @@ public abstract class AbstractTwitterMessageSource<T> extends AbstractEndpoint i
|
||||
return this.markerId > -1;
|
||||
}
|
||||
|
||||
protected TwitterOperations getTwitterOperations() {
|
||||
return this.twitterOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() throws Exception{
|
||||
Assert.notNull(this.getTaskScheduler(),
|
||||
"Can not locate TaskScheduler. You must inject one explicitly or define a bean by the name 'taskScheduler'");
|
||||
"Unable to locate TaskScheduler. You must inject one explicitly or define a bean by the name 'taskScheduler'.");
|
||||
super.onInit();
|
||||
|
||||
if (this.metadataStore == null) {
|
||||
// first try to look for a 'messageStore' in the context
|
||||
// first try to look for a 'metadataStore' in the context
|
||||
BeanFactory beanFactory = this.getBeanFactory();
|
||||
if (beanFactory != null) {
|
||||
MetadataStore metadataStore = IntegrationContextUtils.getMetadataStore(beanFactory);
|
||||
if (metadataStore != null) {
|
||||
this.metadataStore = metadataStore;
|
||||
}
|
||||
this.metadataStore = IntegrationContextUtils.getMetadataStore(beanFactory);
|
||||
}
|
||||
if (this.metadataStore == null) {
|
||||
this.metadataStore = new SimpleMetadataStore();
|
||||
@@ -114,47 +117,18 @@ public abstract class AbstractTwitterMessageSource<T> extends AbstractEndpoint i
|
||||
else if (logger.isWarnEnabled()) {
|
||||
logger.warn(this.getClass().getSimpleName() + " has no name. MetadataStore key might not be unique.");
|
||||
}
|
||||
String profileId = twitter.getProfileId();
|
||||
metadataKeyBuilder.append(profileId);
|
||||
String profileId = this.twitterOperations.getProfileId();
|
||||
if (profileId != null) {
|
||||
metadataKeyBuilder.append(profileId);
|
||||
}
|
||||
this.metadataKey = metadataKeyBuilder.toString();
|
||||
String lastId = this.metadataStore.get(this.metadataKey);
|
||||
// initialize the last status ID from the metadataStore
|
||||
if (StringUtils.hasText(lastId)){
|
||||
if (StringUtils.hasText(lastId)) {
|
||||
this.markerId = Long.parseLong(lastId);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void forwardAll(List<Tweet> tResponses) {
|
||||
Collections.sort(tResponses, this.getComparator());
|
||||
for (Tweet twitterResponse : tResponses) {
|
||||
forward(twitterResponse);
|
||||
}
|
||||
}
|
||||
|
||||
private Comparator getComparator() {
|
||||
return new Comparator<Tweet>() {
|
||||
public int compare(Tweet tweet1, Tweet tweet2) {
|
||||
return tweet1.getCreatedAt().compareTo(tweet2.getCreatedAt());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart(){
|
||||
Assert.notNull(this.twitter, "'twitter' instance must not be null");
|
||||
// temporarily injecting Twitter into a trigger so it can deal with Rate Limits.
|
||||
// This will likely change once we switch to Spring Social.
|
||||
RateLimitStatusTrigger trigger = new RateLimitStatusTrigger(this.twitter.getUnderlyingTwitter());
|
||||
Runnable twitterPollingTask = new TwitterPollingTask();
|
||||
twitterUpdatePollingTask = this.getTaskScheduler().schedule(twitterPollingTask, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop(){
|
||||
twitterUpdatePollingTask.cancel(true);
|
||||
}
|
||||
|
||||
public Message<?> receive() {
|
||||
Tweet tweet = this.tweets.poll();
|
||||
if (tweet != null) {
|
||||
@@ -164,27 +138,54 @@ public abstract class AbstractTwitterMessageSource<T> extends AbstractEndpoint i
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void forward(Tweet tweet) {
|
||||
@SuppressWarnings("unchecked")
|
||||
private void enqueueAll(List<Tweet> tweets) {
|
||||
Collections.sort(tweets, this.tweetComparator);
|
||||
for (Tweet tweet : tweets) {
|
||||
enqueue(tweet);
|
||||
}
|
||||
}
|
||||
|
||||
private void enqueue(Tweet tweet) {
|
||||
synchronized (this.markerGuard) {
|
||||
long id = tweet.getId();
|
||||
if (id > this.markerId) {
|
||||
this.markerId = id;
|
||||
tweets.add(tweet);
|
||||
this.tweets.add(tweet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void markProcessedId(long statusId) {
|
||||
this.processedId = statusId;
|
||||
private void markProcessedId(long statusId) {
|
||||
//this.processedId = statusId;
|
||||
this.metadataStore.put(this.metadataKey, String.valueOf(statusId));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Subclasses must implement this to return tweets.
|
||||
*/
|
||||
protected abstract List<Tweet> pollForTweets();
|
||||
|
||||
|
||||
// Lifecycle methods
|
||||
|
||||
@Override
|
||||
protected void doStart() {
|
||||
// temporarily injecting Twitter into a trigger so it can deal with Rate Limits.
|
||||
// This will likely change once we switch to Spring Social.
|
||||
RateLimitStatusTrigger trigger = new RateLimitStatusTrigger(this.twitterOperations.getUnderlyingTwitter());
|
||||
this.twitterPollingTask = this.getTaskScheduler().schedule(new TwitterPollingTask(), trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() {
|
||||
if (this.twitterPollingTask != null) {
|
||||
this.twitterPollingTask.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class TwitterPollingTask implements Runnable {
|
||||
|
||||
public void run() {
|
||||
@@ -192,7 +193,7 @@ public abstract class AbstractTwitterMessageSource<T> extends AbstractEndpoint i
|
||||
if (tweets.size() <= prefetchThreshold) {
|
||||
List<Tweet> tweets = pollForTweets();
|
||||
if (!CollectionUtils.isEmpty(tweets)) {
|
||||
forwardAll(tweets);
|
||||
enqueueAll(tweets);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,4 +206,12 @@ public abstract class AbstractTwitterMessageSource<T> extends AbstractEndpoint i
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TweetComparator implements Comparator<Tweet> {
|
||||
|
||||
public int compare(Tweet tweet1, Tweet tweet2) {
|
||||
return tweet1.getCreatedAt().compareTo(tweet2.getCreatedAt());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.twitter.inbound;
|
||||
|
||||
import java.util.List;
|
||||
@@ -43,7 +44,7 @@ public class DirectMessageReceivingMessageSource extends AbstractTwitterMessageS
|
||||
@Override
|
||||
protected List<Tweet> pollForTweets() {
|
||||
long sinceId = getMarkerId();
|
||||
return hasMarkedStatus() ? twitter.getDirectMessages(sinceId) : twitter.getDirectMessages();
|
||||
return hasMarkedStatus() ? this.getTwitterOperations().getDirectMessages(sinceId) : this.getTwitterOperations().getDirectMessages();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class MentionsReceivingMessageSource extends AbstractTwitterMessageSource
|
||||
@Override
|
||||
protected List<Tweet> pollForTweets() {
|
||||
long sinceId = getMarkerId();
|
||||
return hasMarkedStatus() ? twitter.getMentions(sinceId) : twitter.getMentions();
|
||||
return hasMarkedStatus() ? this.getTwitterOperations().getMentions(sinceId) : this.getTwitterOperations().getMentions();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public class SearchReceivingMessageSource extends AbstractTwitterMessageSource<T
|
||||
|
||||
@Override
|
||||
protected List<Tweet> pollForTweets() {
|
||||
SearchResults results = this.twitter.search(query);
|
||||
SearchResults results = this.getTwitterOperations().search(query);
|
||||
return (results != null) ? results.getTweets() : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public class TimelineReceivingMessageSource extends AbstractTwitterMessageSource
|
||||
@Override
|
||||
protected List<Tweet> pollForTweets() {
|
||||
long sinceId = getMarkerId();
|
||||
return hasMarkedStatus() ? twitter.getTimeline(sinceId) : twitter.getTimeline();
|
||||
return hasMarkedStatus() ? this.getTwitterOperations().getTimeline(sinceId) : this.getTwitterOperations().getTimeline();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,32 +13,24 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.twitter.config;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.integration.twitter.core.Twitter4jTemplate;
|
||||
import org.springframework.integration.twitter.core.TwitterOperations;
|
||||
import org.springframework.integration.twitter.inbound.AbstractTwitterMessageSource;
|
||||
import org.springframework.integration.twitter.inbound.SearchReceivingMessageSource;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
*/
|
||||
public class TestSearchReceivingMessageSourceParserTests {
|
||||
|
||||
@@ -48,23 +40,20 @@ public class TestSearchReceivingMessageSourceParserTests {
|
||||
SourcePollingChannelAdapter spca = ac.getBean("searchAdapter", SourcePollingChannelAdapter.class);
|
||||
SearchReceivingMessageSource ms = (SearchReceivingMessageSource) TestUtils.getPropertyValue(spca, "source");
|
||||
assertFalse(ms.isAutoStartup());
|
||||
|
||||
assertFalse(ms.isAutoStartup());
|
||||
Twitter4jTemplate template = (Twitter4jTemplate) TestUtils.getPropertyValue(ms, "twitter");
|
||||
|
||||
assertFalse(template.getUnderlyingTwitter().isOAuthEnabled()); // verify that anonymous Twitte
|
||||
Twitter4jTemplate template = (Twitter4jTemplate) TestUtils.getPropertyValue(ms, "twitterOperations");
|
||||
assertFalse(template.getUnderlyingTwitter().isOAuthEnabled()); // verify anonymous Twitter
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchReceivingCustomTemplate(){
|
||||
ApplicationContext ac = new ClassPathXmlApplicationContext("TestSearchReceivingMessageSourceParser-context.xml", this.getClass());
|
||||
SourcePollingChannelAdapter spca = ac.getBean("searchAdapterWithTemplate", SourcePollingChannelAdapter.class);
|
||||
SearchReceivingMessageSource ms = (SearchReceivingMessageSource) TestUtils.getPropertyValue(spca, "source");
|
||||
assertFalse(ms.isAutoStartup());
|
||||
|
||||
assertFalse(ms.isAutoStartup());
|
||||
TwitterOperations template = (TwitterOperations) TestUtils.getPropertyValue(ms, "twitter");
|
||||
TwitterOperations template = (TwitterOperations) TestUtils.getPropertyValue(ms, "twitterOperations");
|
||||
assertEquals(ac.getBean("twitter"), template);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.twitter.ignored;
|
||||
|
||||
import org.springframework.integration.Message;
|
||||
@@ -20,28 +21,29 @@ import org.springframework.integration.history.MessageHistory;
|
||||
import org.springframework.integration.twitter.core.Tweet;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
@Component
|
||||
public class TwitterAnnouncer {
|
||||
|
||||
public void dm(Tweet directMessage) {
|
||||
System.out.println("A direct message has been received from " +
|
||||
directMessage.getFromUser() + " with text " + directMessage.getText());
|
||||
}
|
||||
|
||||
public void search(Message search) {
|
||||
|
||||
public void search(Message<?> search) {
|
||||
MessageHistory history = MessageHistory.read(search);
|
||||
System.out.println(history);
|
||||
Tweet tweet = (Tweet) search.getPayload();
|
||||
|
||||
System.out.println("A search item was received " +
|
||||
tweet.getCreatedAt() + " with text " + tweet.getText());
|
||||
}
|
||||
|
||||
public void mention(Tweet s) {
|
||||
System.out.println("A tweet mentioning (or replying) to " + "you was received having text " + s.getFromUser() + "-" + s.getText() + " from " + s.getSource());
|
||||
System.out.println("A tweet mentioning (or replying) to you was received having text "
|
||||
+ s.getFromUser() + "-" + s.getText() + " from " + s.getSource());
|
||||
}
|
||||
|
||||
public void updates(Tweet t) {
|
||||
System.out.println("Received timeline update: " + t.getText() + " from " + t.getSource());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -134,12 +134,12 @@ public class DirectMessageReceivingMessageSourceTests {
|
||||
assertEquals(2000, message.getId());
|
||||
Thread.sleep(1000);
|
||||
verify(twitter, times(1)).getDirectMessages();
|
||||
// based on the Mock, the Queue shoud now have 1 more messages third and fourth
|
||||
// based on the Mock, the Queue should now have 2 more messages third and fourth
|
||||
assertTrue(((Queue)TestUtils.getPropertyValue(source, "tweets")).size() == 2);
|
||||
source.stop();
|
||||
}
|
||||
/**
|
||||
* This test will validate that last status is initilaized from the metadatastore
|
||||
* This test will validate that last status is initialized from the metadatastore
|
||||
* @throws Exception
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
/**
|
||||
*
|
||||
/*
|
||||
* Copyright 2002-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.inbound;
|
||||
|
||||
import org.junit.Ignore;
|
||||
@@ -8,7 +21,6 @@ import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageChannel;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.core.MessageHandler;
|
||||
@@ -19,7 +31,6 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
|
||||
/**
|
||||
* @author ozhurakousky
|
||||
*
|
||||
*/
|
||||
public class SearchReceivingMessageSourceTests {
|
||||
|
||||
@@ -30,9 +41,7 @@ public class SearchReceivingMessageSourceTests {
|
||||
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
|
||||
scheduler.afterPropertiesSet();
|
||||
bf.registerSingleton("taskScheduler", scheduler);
|
||||
|
||||
SearchReceivingMessageSource ms = new SearchReceivingMessageSource(new Twitter4jTemplate());
|
||||
|
||||
DirectChannel channel = new DirectChannel();
|
||||
channel.subscribe(new MessageHandler() {
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
@@ -45,14 +54,12 @@ public class SearchReceivingMessageSourceTests {
|
||||
adapter.setOutputChannel(channel);
|
||||
adapter.afterPropertiesSet();
|
||||
adapter.start();
|
||||
|
||||
ms.setBeanFactory(bf);
|
||||
ms.setQuery("#springintegration");
|
||||
|
||||
ms.setTaskScheduler(scheduler);
|
||||
ms.afterPropertiesSet();
|
||||
ms.start();
|
||||
|
||||
System.in.read();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user