INT-2633 Add File Disposition to (S)FTP Inbound

FileReadingMessageSource supports disposition of the payload
after the message is sent (or via transaction synchronization).

The (S)FTP adapters delegate to an FRMS; add support for the
file disposition expression, result channel, and send timeout
to the (S)FTP adapters.

Also tested with ftp sample - INTSAMPLES-83 - patch will be
committed once this is in a milestone.
This commit is contained in:
Gary Russell
2012-07-06 13:02:55 -04:00
committed by Oleg Zhurakousky
parent d0f26cd618
commit e6e43100b5
15 changed files with 271 additions and 72 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2012 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.
@@ -17,36 +17,38 @@
package org.springframework.integration.sftp.config;
import org.springframework.integration.file.config.AbstractRemoteFileInboundChannelAdapterParser;
import org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilter;
import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFilter;
import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizer;
import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizingMessageSource;
/**
* Parser for 'sftp:inbound-channel-adapter'
*
*
* @author Mark Fisher
* @author Gary Russell
* @since 2.0
*/
public class SftpInboundChannelAdapterParser extends AbstractRemoteFileInboundChannelAdapterParser {
private static final String BASE_PACKAGE = "org.springframework.integration.sftp";
@Override
protected String getMessageSourceClassname() {
return BASE_PACKAGE + ".inbound.SftpInboundFileSynchronizingMessageSource";
return SftpInboundFileSynchronizingMessageSource.class.getName();
}
@Override
protected String getInboundFileSynchronizerClassname() {
return BASE_PACKAGE + ".inbound.SftpInboundFileSynchronizer";
return SftpInboundFileSynchronizer.class.getName();
}
@Override
protected String getSimplePatternFileListFilterClassname() {
return BASE_PACKAGE + ".filters.SftpSimplePatternFileListFilter";
return SftpSimplePatternFileListFilter.class.getName();
}
@Override
protected String getRegexPatternFileListFilterClassname() {
return BASE_PACKAGE + ".filters.SftpRegexPatternFileListFilter";
return SftpRegexPatternFileListFilter.class.getName();
}
}

View File

@@ -221,6 +221,42 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="disposition-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
SpEL expression to be executed after the message has been sent. If running in a transactional
poller, it will be executed after the transaction commits. If running in a non-transactional
poller it will execute after the message is sent. Note that the actual point of execution
depends on any asynchronous handoffs on the downstream flow. It will be executed when the
current thread returns from the channel send. The root object of the expression is the
original message (with a File payload). Examples: "payload.delete()",
"payload.renameTo('/foo/bar/' + payload.name)", "@someBean.doSomething(payload)".
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="disposition-result-channel" type="xsd:string" default="nullChannel">
<xsd:annotation>
<xsd:documentation>
If a 'disposition-expression' is provided, and that expression returns a result, the result
is sent to this channel, with the original message payload and a 'file_dispositionResult'
header containing the result of the expression execution.
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.MessageChannel"/>
</tool:annotation>
</xsd:appinfo>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="disposition-send-timeout" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
If a 'disposition-expression' is provided, and that expression returns a result, the result
is sent to this disposition-result-channel. This timeout specifies how long to wait if
that channel might block (such as a bounded queue channel that is full). Default infinity.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

View File

@@ -48,9 +48,14 @@
local-filename-generator-expression="#this.toUpperCase() + '.a'"
temporary-file-suffix=".bar"
comparator="comparator"
delete-remote-files="${delete.remote.files}">
delete-remote-files="${delete.remote.files}"
disposition-expression="'foo'"
disposition-result-channel="dispoChannel"
disposition-send-timeout="123">
<poller fixed-rate="1000"/>
</sftp:inbound-channel-adapter>
<channel id="dispoChannel" />
<beans:bean id="comparator" class="org.mockito.Mockito" factory-method="mock">
<beans:constructor-arg value="java.util.Comparator"/>

View File

@@ -29,14 +29,15 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.Expression;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizer;
import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizingMessageSource;
import org.springframework.integration.test.util.TestUtils;
@@ -46,17 +47,17 @@ import org.springframework.integration.test.util.TestUtils;
* @author Gary Russell
*/
public class InboundChannelAdapterParserTests {
@Before
public void prepare(){
new File("foo").delete();
}
@Test
public void testAutoStartup() throws Exception{
ApplicationContext context =
new ClassPathXmlApplicationContext("SftpInboundAutostartup-context.xml", this.getClass());
SourcePollingChannelAdapter adapter = context.getBean("sftpAutoStartup", SourcePollingChannelAdapter.class);
assertFalse(adapter.isRunning());
}
@@ -67,10 +68,10 @@ public class InboundChannelAdapterParserTests {
ApplicationContext context =
new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context.xml", this.getClass());
assertTrue(new File("src/main/resources").exists());
Object adapter = context.getBean("sftpAdapterAutoCreate");
assertTrue(adapter instanceof SourcePollingChannelAdapter);
SftpInboundFileSynchronizingMessageSource source =
SftpInboundFileSynchronizingMessageSource source =
(SftpInboundFileSynchronizingMessageSource) TestUtils.getPropertyValue(adapter, "source");
assertNotNull(source);
Comparator<File> comparator = TestUtils.getPropertyValue(adapter, "source.fileSource.toBeReceived.q.comparator", Comparator.class);
@@ -83,6 +84,12 @@ public class InboundChannelAdapterParserTests {
assertEquals(".", remoteFileSeparator);
PollableChannel requestChannel = context.getBean("requestChannel", PollableChannel.class);
assertNotNull(requestChannel.receive(2000));
FileReadingMessageSource fileSource = TestUtils.getPropertyValue(source, "fileSource", FileReadingMessageSource.class);
assertEquals("foo", TestUtils.getPropertyValue(fileSource, "dispositionExpression", Expression.class).getValue());
assertSame(context.getBean("dispoChannel"), TestUtils.getPropertyValue(
TestUtils.getPropertyValue(fileSource, "dispositionMessagingTemplate"), "defaultChannel"));
assertEquals(123L, TestUtils.getPropertyValue(
TestUtils.getPropertyValue(fileSource, "dispositionMessagingTemplate"), "sendTimeout"));
}
@Test
@@ -108,19 +115,19 @@ public class InboundChannelAdapterParserTests {
new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context.xml", this.getClass());
assertTrue(new File("target").exists());
}
@Test
public void testLocalDirAutoCreated() throws Exception{
assertFalse(new File("foo").exists());
new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context.xml", this.getClass());
assertTrue(new File("foo").exists());
}
@Test(expected=BeanCreationException.class)
public void testLocalDirAutoCreateFailed() throws Exception{
new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context-fail-autocreate.xml", this.getClass());
}
@After
public void cleanUp() throws Exception{
new File("foo").delete();