INT-1604 encapsulation

This commit is contained in:
Mark Fisher
2010-11-11 17:44:48 -05:00
parent 79b8b6b8b5
commit 4f3b887ddd
9 changed files with 103 additions and 95 deletions

View File

@@ -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());
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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")

View File

@@ -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();
}
}