INT-786, added persistence to the feed adapter, added more tests, polished more code

This commit is contained in:
Oleg Zhurakousky
2010-10-17 19:02:39 -04:00
parent 05de3b44c4
commit 31b3a8c0e6
13 changed files with 269 additions and 167 deletions

View File

@@ -19,7 +19,10 @@
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.commons</groupId>
<artifactId>spring-commons-serializer</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.5</version>
</dependency>
@@ -28,7 +31,6 @@
<artifactId>rome-fetcher</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>net.java.dev.rome</groupId>
<artifactId>rome</artifactId>

View File

@@ -15,12 +15,15 @@
*/
package org.springframework.integration.feed;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.springframework.integration.Message;
@@ -28,6 +31,8 @@ import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;
import org.springframework.util.DefaultPropertiesPersister;
import org.springframework.util.StringUtils;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
@@ -41,14 +46,17 @@ import com.sun.syndication.feed.synd.SyndFeed;
* @author Oleg Zhurakousky
*/
public class FeedEntryReaderMessageSource extends IntegrationObjectSupport implements MessageSource<SyndEntry>{
private volatile Map<String, String> persisterMap = new ConcurrentHashMap<String, String>();
private final DefaultPropertiesPersister persister = new DefaultPropertiesPersister();
private volatile Properties lastPersistentEntry = new Properties();
private volatile Queue<SyndEntry> entries = new ConcurrentLinkedQueue<SyndEntry>();
private volatile FeedReaderMessageSource feedReaderMessageSource;
private final Object monitor = new Object();
private volatile String feedMetadataIdKey;
private volatile boolean initialized;
private volatile String persistentIdentifier;
private volatile boolean initialized;
private volatile long lastTime = -1;
private volatile File persisterFile;
private Comparator<SyndEntry> syndEntryComparator = new Comparator<SyndEntry>() {
public int compare(SyndEntry syndEntry, SyndEntry syndEntry1) {
@@ -68,15 +76,9 @@ public class FeedEntryReaderMessageSource extends IntegrationObjectSupport imple
Assert.notNull(feedReaderMessageSource, "'feedReaderMessageSource' must not be null");
this.feedReaderMessageSource = feedReaderMessageSource;
}
/**
* Allows you to provide your own implementation of 'persisterMap' instead of relying on
* your own which is in-memory.
*
* @param persisterMap
*/
public void setPersisterMap(Map<String, String> persisterMap) {
Assert.notNull(persisterMap, "'persisterMap' can not be null");
this.persisterMap = persisterMap;
public void setPersistentIdentifier(String persistentIdentifier) {
this.persistentIdentifier = persistentIdentifier;
}
public String getComponentType(){
@@ -94,8 +96,9 @@ public class FeedEntryReaderMessageSource extends IntegrationObjectSupport imple
@SuppressWarnings("unchecked")
private SyndEntry doReceieve() {
SyndEntry nextUp = null;
synchronized (this.monitor) {
SyndEntry nextUp = pollAndCache();
nextUp = pollAndCache();
if (nextUp != null) {
return nextUp;
@@ -114,17 +117,33 @@ public class FeedEntryReaderMessageSource extends IntegrationObjectSupport imple
}
}
}
return pollAndCache();
nextUp = pollAndCache();
}
return nextUp;
}
@Override
protected void onInit() throws Exception {
// setup persistence of metadata
this.feedMetadataIdKey = FeedEntryReaderMessageSource.class.getName() + "#" + feedReaderMessageSource.getFeedUrl();
String lastTime = (String) this.persisterMap.get(this.feedMetadataIdKey);
if (lastTime != null && !lastTime.trim().equalsIgnoreCase("")) {
this.lastTime = Long.parseLong(lastTime);
if (StringUtils.hasText(this.persistentIdentifier)){
File dir = new File(System.getProperty("user.home") + "/temp/spring-integration");
dir.mkdirs();
persisterFile = new File(dir, this.persistentIdentifier + ".last.entry");
if (!persisterFile.exists()){
persisterFile.createNewFile();
}
FileInputStream inStream = new FileInputStream(persisterFile);
persister.load(lastPersistentEntry, inStream);
}
else {
logger.info("Your '" + this.getComponentType() + "' is anonymous (no ID attribute), therefore no feed entries will be persisted " +
"which may result in a duplicate feed entries once this adapter is restarted");
}
this.feedMetadataIdKey = this.getComponentType() + "@" + this.getComponentName() +
"#" + feedReaderMessageSource.getFeedUrl();
String keyTime = (String) this.lastPersistentEntry.get(this.feedMetadataIdKey);
if (StringUtils.hasText(keyTime)){
this.lastTime = Long.parseLong(keyTime);
}
this.initialized = true;
}
@@ -135,9 +154,32 @@ public class FeedEntryReaderMessageSource extends IntegrationObjectSupport imple
if (next == null) {
return null;
}
this.lastTime = next.getPublishedDate().getTime();
this.persisterMap.put(this.feedMetadataIdKey, this.lastTime + "");
this.lastPersistentEntry.put(this.feedMetadataIdKey, this.lastTime + "");
if (persisterFile != null){
FileOutputStream fo = null;
try {
fo = new FileOutputStream(persisterFile);
persister.store(this.lastPersistentEntry, fo, "Last feed entry");
}
catch (IOException e) {
// not fatal for the functionality of the component
logger.warn("Failed to persist feed entry. This may result in a duplicate " +
"feed entry after this component is restarted", e);
}
finally {
try {
fo.close();
}
catch (IOException e) {
// not fatal for the functionality of he component
logger.warn("Failed to close output stream to " + persisterFile.getAbsolutePath(), e);
}
}
}
return next;
}
}

View File

@@ -87,7 +87,6 @@ public class FeedReaderMessageSource extends IntegrationObjectSupport
}
}
} catch (Exception e) {
e.printStackTrace();
throw new MessagingException("Exception thrown when trying to retrive feed at url '" + this.feedUrl + "'", e);
}

View File

@@ -19,6 +19,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.w3c.dom.Element;
/**
@@ -34,7 +35,7 @@ public class FeedMessageSourceBeanDefinitionParser extends AbstractPollingInboun
BeanDefinitionBuilder feedEntryBuilder =
BeanDefinitionBuilder.genericBeanDefinition("org.springframework.integration.feed.FeedEntryReaderMessageSource");
IntegrationNamespaceUtils.setValueIfAttributeDefined(feedEntryBuilder, element, "id", "persistentIdentifier");
BeanDefinitionBuilder feedBuilder =
BeanDefinitionBuilder.genericBeanDefinition("org.springframework.integration.feed.FeedReaderMessageSource");
feedBuilder.addConstructorArgValue(element.getAttribute("feedUrl"));

View File

@@ -27,6 +27,7 @@
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string"/>
<xsd:attribute name="auto-startup" type="xsd:string" default="true" />
<xsd:attribute name="channel" use="required" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>

View File

@@ -1,32 +0,0 @@
package org.springframework.integration.feed;
import java.util.Properties;
import org.springframework.integration.Message;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.history.MessageHistory;
import org.springframework.stereotype.Component;
import com.sun.syndication.feed.synd.SyndEntry;
@Component
public class FeedDeliveryEventServiceActivator {
@ServiceActivator
public void activate(Message<SyndEntry> message) throws Exception {
MessageHistory history = MessageHistory.read(message);
for (Properties properties : history) {
System.out.println(properties);
}
SyndEntry syndEntry = message.getPayload();
System.out.println( "Publishing new SyndEntry " + syndEntry.getUri() +":"+
syndEntry.getPublishedDate().toString()+ ":"+ syndEntry.getPublishedDate().getTime());
// System.out.println( syndEntry.toString());
// System.out.println("Delivery! " + ToStringBuilder.reflectionToString(evtMsg));
}
}

View File

@@ -21,10 +21,13 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.integration.Message;
@@ -36,6 +39,13 @@ import com.sun.syndication.feed.synd.SyndFeed;
*
*/
public class FeedEntryReaderMessageSourceTests {
@Before
public void prepare(){
File persisterFile = new File(System.getProperty("user.home") + "/temp/spring-integration", "feedReader.last.entry");
if (persisterFile.exists()){
persisterFile.delete();
}
}
@Test(expected=IllegalArgumentException.class)
public void testFailureWhenNotInitialized(){
@@ -49,6 +59,7 @@ public class FeedEntryReaderMessageSourceTests {
SyndFeed feed = mock(SyndFeed.class);
when(feedReaderSource.receiveSyndFeed()).thenReturn(feed);
FeedEntryReaderMessageSource feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
feedEntrySource.setPersistentIdentifier("feedReader");
feedEntrySource.afterPropertiesSet();
assertNull(feedEntrySource.receive());
}
@@ -68,6 +79,7 @@ public class FeedEntryReaderMessageSourceTests {
when(feedReaderSource.receiveSyndFeed()).thenReturn(feed);
FeedEntryReaderMessageSource feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
feedEntrySource.setPersistentIdentifier("feedReader");
feedEntrySource.afterPropertiesSet();
Message<SyndEntry> entryMessage = feedEntrySource.receive();
assertEquals(entry2, entryMessage.getPayload());
@@ -77,4 +89,79 @@ public class FeedEntryReaderMessageSourceTests {
entryMessage = feedEntrySource.receive();
assertNull(entryMessage);
}
// will test, that last feed entry is remembered between the sessions
// and no duplicate entries are retrieved
@Test
public void testReceieveFeedWithRealEntriesAndRepeatWithPersistentIdentifier() throws Exception{
FeedReaderMessageSource feedReaderSource =
new FeedReaderMessageSource(new URL("file:src/test/java/org/springframework/integration/feed/sample.rss"));
FeedEntryReaderMessageSource feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
feedEntrySource.setPersistentIdentifier("feedReader");
feedEntrySource.afterPropertiesSet();
SyndEntry entry1 = feedEntrySource.receive().getPayload();
SyndEntry entry2 = feedEntrySource.receive().getPayload();
SyndEntry entry3 = feedEntrySource.receive().getPayload();
assertNull(feedEntrySource.receive()); // only 3 entries in the test feed
assertEquals("Spring Integration download", entry1.getTitle().trim());
assertEquals(1266088337000L, entry1.getPublishedDate().getTime());
assertEquals("Check out Spring Integration forums", entry2.getTitle().trim());
assertEquals(1268469501000L, entry2.getPublishedDate().getTime());
assertEquals("Spring Integration adapters", entry3.getTitle().trim());
assertEquals(1272044098000L, entry3.getPublishedDate().getTime());
// now test that what's been read is no longer retrieved
feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
feedEntrySource.setPersistentIdentifier("feedReader");
feedEntrySource.afterPropertiesSet();
assertNull(feedEntrySource.receive());
assertNull(feedEntrySource.receive());
assertNull(feedEntrySource.receive());
}
// will test, that last feed entry is NOT remembered between the sessions, since
// persister is not used due to the lack of persistentIdentifier (id attribute in xml)
// and the same entries are retrieved again
@Test
public void testReceieveFeedWithRealEntriesAndRepeatNoPersistentIdentifier() throws Exception{
FeedReaderMessageSource feedReaderSource =
new FeedReaderMessageSource(new URL("file:src/test/java/org/springframework/integration/feed/sample.rss"));
FeedEntryReaderMessageSource feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
feedEntrySource.afterPropertiesSet();
SyndEntry entry1 = feedEntrySource.receive().getPayload();
SyndEntry entry2 = feedEntrySource.receive().getPayload();
SyndEntry entry3 = feedEntrySource.receive().getPayload();
assertNull(feedEntrySource.receive()); // only 3 entries in the test feed
assertEquals("Spring Integration download", entry1.getTitle().trim());
assertEquals(1266088337000L, entry1.getPublishedDate().getTime());
assertEquals("Check out Spring Integration forums", entry2.getTitle().trim());
assertEquals(1268469501000L, entry2.getPublishedDate().getTime());
assertEquals("Spring Integration adapters", entry3.getTitle().trim());
assertEquals(1272044098000L, entry3.getPublishedDate().getTime());
// UNLIKE the previous test
// now test that what's been read is read AGAIN
feedEntrySource = new FeedEntryReaderMessageSource(feedReaderSource);
feedEntrySource.afterPropertiesSet();
entry1 = feedEntrySource.receive().getPayload();
entry2 = feedEntrySource.receive().getPayload();
entry3 = feedEntrySource.receive().getPayload();
assertNull(feedEntrySource.receive()); // only 3 entries in the test feed
assertEquals("Spring Integration download", entry1.getTitle().trim());
assertEquals(1266088337000L, entry1.getPublishedDate().getTime());
assertEquals("Check out Spring Integration forums", entry2.getTitle().trim());
assertEquals(1268469501000L, entry2.getPublishedDate().getTime());
assertEquals("Spring Integration adapters", entry3.getTitle().trim());
assertEquals(1272044098000L, entry3.getPublishedDate().getTime());
}
}

View File

@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:feed="http://www.springframework.org/schema/integration/feed"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.0.xsd
http://www.springframework.org/schema/integration/feed http://www.springframework.org/schema/integration/feed/spring-integration-feed.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<int:message-history/>
<bean id="activator" class="org.springframework.integration.feed.FeedDeliveryEventServiceActivator"/>
<!--
this will keep state in /tmp/feedDemo.properties and not deliver anything until the feed has an updated pub date
to see the feed again, rm /tmp/feedDemo.properties
-->
<!-- <bean id="metadataPersister" class="org.springframework.integration.context.metadata.PropertiesBasedMetadataPersister">-->
<!-- <property name="uniqueName" value="feedDemo"/>-->
<!-- </bean>-->
<!--http://twitter.com/statuses/public_timeline.atom
-->
<feed:inbound-channel-adapter id="feedAdapter" channel="feedChanges" feedUrl="http://feeds.bbci.co.uk/news/rss.xml" >
<int:poller fixed-rate="10000" max-messages-per-poll="-1"/>
</feed:inbound-channel-adapter>
<int:channel id="feedChanges"/>
<int:service-activator input-channel="feedChanges" ref="activator" />
</beans>

View File

@@ -1,33 +0,0 @@
/*
* 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.feed;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class TestFeedEventDelivery {
@Test
@Ignore
public void testDeliveryOfFeed() throws Exception {
Thread.sleep(1000 * 60);
}
}

View File

@@ -6,10 +6,14 @@
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.0.xsd
http://www.springframework.org/schema/integration/feed http://www.springframework.org/schema/integration/feed/spring-integration-feed-2.0.xsd">
<int-feed:inbound-channel-adapter id="feedAdapter" channel="feedChannel"
<int-feed:inbound-channel-adapter id="feedAdapter" channel="feedChannel" auto-startup="false"
feedUrl="file:src/test/java/org/springframework/integration/feed/config/sample.rss">
<int:poller fixed-rate="10000" max-messages-per-poll="100" />
</int-feed:inbound-channel-adapter>
<int:channel id="feedChannel" />
<int:channel id="feedChannel">
<int:queue/>
</int:channel>
</beans>

View File

@@ -6,7 +6,7 @@
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.0.xsd
http://www.springframework.org/schema/integration/feed http://www.springframework.org/schema/integration/feed/spring-integration-feed-2.0.xsd">
<int-feed:inbound-channel-adapter id="feedAdapter" channel="feedChannel"
<int-feed:inbound-channel-adapter id="feedAdapter" channel="feedChannel" auto-startup="false"
feedUrl="http://feeds.bbci.co.uk/news/rss.xml">
<int:poller fixed-rate="10000" max-messages-per-poll="100" />
</int-feed:inbound-channel-adapter>

View File

@@ -15,15 +15,20 @@
*/
package org.springframework.integration.feed.config;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.io.File;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.context.ApplicationContext;
@@ -36,21 +41,30 @@ import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.feed.FeedEntryReaderMessageSource;
import org.springframework.integration.feed.FeedReaderMessageSource;
import org.springframework.integration.feed.FileUrlFeedFetcher;
import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.test.util.TestUtils;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.fetcher.impl.AbstractFeedFetcher;
import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher;
/**
* @author Oleg Zhurakousky
*
*/
public class FeedMessageSourceBeanDefinitionParserTests {
private static CountDownLatch latch;
@Before
public void prepare(){
File persisterFile = new File(System.getProperty("user.home") + "/temp/spring-integration", "feedAdapter.last.entry");
if (persisterFile.exists()){
persisterFile.delete();
}
}
@Test
public void validateSuccessfullConfiguration(){
ApplicationContext context =
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("FeedMessageSourceBeanDefinitionParserTests-file-context.xml", this.getClass());
SourcePollingChannelAdapter adapter = context.getBean("feedAdapter", SourcePollingChannelAdapter.class);
FeedEntryReaderMessageSource source = (FeedEntryReaderMessageSource) TestUtils.getPropertyValue(adapter, "source");
@@ -65,26 +79,55 @@ public class FeedMessageSourceBeanDefinitionParserTests {
feedReaderMessageSource = (FeedReaderMessageSource) TestUtils.getPropertyValue(source, "feedReaderMessageSource");
fetcher = (AbstractFeedFetcher) TestUtils.getPropertyValue(feedReaderMessageSource, "fetcher");
assertTrue(fetcher instanceof HttpURLFeedFetcher);
context.destroy();
}
@Test
public void validateSuccessfullNewsRetrievalFile() throws Exception{
public void validateSuccessfullNewsRetrievalWithFileUrlAndMessageHistory() throws Exception{
File persisterFile = new File(System.getProperty("user.home") + "/temp/spring-integration", "feedAdapterUsage.last.entry");
if (persisterFile.exists()){
persisterFile.delete();
}
//Test file samples.rss has 3 news items
final CountDownLatch latch = new CountDownLatch(3);
MessageHandler handler = spy(new MessageHandler() {
public void handleMessage(Message<?> message) throws MessagingException {
latch.countDown();
}
});
ApplicationContext context =
new ClassPathXmlApplicationContext("FeedMessageSourceBeanDefinitionParserTests-file-context.xml", this.getClass());
DirectChannel feedChannel = context.getBean("feedChannel", DirectChannel.class);
feedChannel.subscribe(handler);
latch = spy(new CountDownLatch(3));
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("FeedMessageSourceBeanDefinitionParserTests-file-usage-context.xml", this.getClass());
latch.await(5, TimeUnit.SECONDS);
verify(handler, times(3)).handleMessage(Mockito.any(Message.class));
verify(latch, times(3)).countDown();
context.destroy();
// since we are not deleting the persister file
// in this iteration no new feeds will be received and the latch will timeout
latch = spy(new CountDownLatch(3));
context =
new ClassPathXmlApplicationContext("FeedMessageSourceBeanDefinitionParserTests-file-usage-context.xml", this.getClass());
latch.await(5, TimeUnit.SECONDS);
verify(latch, times(0)).countDown();
context.destroy();
}
@Test
public void validateSuccessfullNewsRetrievalHttp() throws Exception{
public void validateSuccessfullNewsRetrievalWithFileUrlNoPersistentIdentifier() throws Exception{
//Test file samples.rss has 3 news items
latch = spy(new CountDownLatch(3));
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("FeedMessageSourceBeanDefinitionParserTests-file-usage-noid-context.xml", this.getClass());
latch.await(5, TimeUnit.SECONDS);
verify(latch, times(3)).countDown();
context.destroy();
// since we are not deleting the persister file
// in this iteration no new feeds will be received and the latch will timeout
latch = spy(new CountDownLatch(3));
context =
new ClassPathXmlApplicationContext("FeedMessageSourceBeanDefinitionParserTests-file-usage-noid-context.xml", this.getClass());
latch.await(5, TimeUnit.SECONDS);
verify(latch, times(3)).countDown();
context.destroy();
}
@Test
@Ignore // goes against the real feed
public void validateSuccessfullNewsRetrievalWithHttpUrl() throws Exception{
final CountDownLatch latch = new CountDownLatch(3);
MessageHandler handler = spy(new MessageHandler() {
public void handleMessage(Message<?> message) throws MessagingException {
@@ -98,4 +141,29 @@ public class FeedMessageSourceBeanDefinitionParserTests {
latch.await(5, TimeUnit.SECONDS);
verify(handler, atLeast(3)).handleMessage(Mockito.any(Message.class));
}
public static class SampleService{
public void receiveFeedEntry(Message<?> message){
MessageHistory history = MessageHistory.read(message);
assertTrue(history.size() == 3);
Properties historyItem = history.get(0);
assertEquals("feedAdapterUsage", historyItem.get("name"));
assertEquals("feed:inbound-channel-adapter", historyItem.get("type"));
historyItem = history.get(1);
assertEquals("feedChannelUsage", historyItem.get("name"));
assertEquals("channel", historyItem.get("type"));
historyItem = history.get(2);
assertEquals("sampleActivator", historyItem.get("name"));
assertEquals("service-activator", historyItem.get("type"));
latch.countDown();
}
}
public static class SampleServiceNoHistory{
public void receiveFeedEntry(SyndEntry entry){
latch.countDown();
}
}
}

View File

@@ -1,52 +1,52 @@
<rss version="2.0">
<channel>
<title>ASP @ BellaOnline</title>
<link>http://www.bellaonline.com/Site/asp</link>
<title>Spring Integration</title>
<link>http://www.springsource.org/spring-integration</link>
<description>
Learn to program in ASP, and enhance your ASP skills to add great new functionality to your website!
Spring Integration is a really cool framework
</description>
<language>en-us</language>
<copyright>Copyright 2001-2005 BellaOnline.com
<copyright>Copyright 2004-2010 SpringSource/VMWare
All Rights Reserved.</copyright>
<lastBuildDate>Tue, 12 Apr 2005 14:21:32 EST</lastBuildDate>
<lastBuildDate>Tue, 12 Apr 2010 18:21:32 EST</lastBuildDate>
<ttl>240</ttl>
<image>
<url>http://www.bellaonline.com/images/bella.gif</url>
<title>ASP @ BellaOnline</title>
<link>http://asp.bellaonline.com</link>
<url>http://www.springsource.org/sites/all/themes/dotorg09/images/dotorg09_logo.png</url>
<title>Spring Integration</title>
<link>http://www.springsource.org/spring-integration</link>
</image>
<item>
<title>
Using ASP to Code an RSS Feed
Spring Integration adapters
</title>
<link>http://www.bellaonline.com/articles/art30646.asp</link>
<link>http://www.springsource.org/extensions/se-sia</link>
<description>
RSS feeds let you easily syndicate your content to an end user or another website. ASP can help you easily create your own RSS feed for your website.
Spring Integration adapters are realy cool
</description>
<pubDate>Tue, 12 Apr 2005 13:59:56 EST</pubDate>
<pubDate>Tue, 23 Apr 2010 12:34:58 EST</pubDate>
</item>
<item>
<title>
RecordCount and Count
Spring Integration download
</title>
<link>http://www.bellaonline.com/articles/art30403.asp</link>
<link>http://www.springsource.com/products/spring-community-download</link>
<description>
If you're trying to figure out how many records are in a given SQL result set, you can use either the RecordCount or Count command. Both work in different ways.
Download Spring Integration
</description>
<pubDate>Sun, 3 Apr 2005 17:12:17 EST</pubDate>
<pubDate>Sun, 13 Feb 2010 14:12:17 EST</pubDate>
</item>
<item>
<title>
Bubble Sort Code Technique
Check out Spring Integration forums
</title>
<link>http://www.bellaonline.com/articles/art29843.asp</link>
<link>http://forum.springsource.org/forumdisplay.php?f=42</link>
<description>
If you are sorting content into an order, one of the most simple techniques that exists is the bubble sort technique.
Spring Integration forums are awesome
</description>
<pubDate>Wed, 16 Mar 2005 00:38:21 EST</pubDate>
<pubDate>Wed, 13 Mar 2010 03:38:21 EST</pubDate>
</item>
</channel>