INT-3763: (S)FTP - Inbound Remote Dir. Expression

JIRA: https://jira.spring.io/browse/INT-3763

Fix `AbstractRemoteFileSynchronizerTests` according to this change.
This commit is contained in:
Gary Russell
2015-07-08 14:21:53 -04:00
committed by Artem Bilan
parent 5a9b898635
commit c68c7b3412
14 changed files with 113 additions and 35 deletions

View File

@@ -19,6 +19,7 @@ package org.springframework.integration.file.config;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.ExpressionFactoryBean;
@@ -44,7 +45,9 @@ public abstract class AbstractRemoteFileInboundChannelAdapterParser extends Abst
synchronizerBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
// configure the InboundFileSynchronizer properties
IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "remote-directory");
BeanDefinition expressionDef = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression(
"remote-directory", "remote-directory-expression", parserContext, element, true);
synchronizerBuilder.addPropertyValue("remoteDirectoryExpression", expressionDef);
IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "delete-remote-files");
IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "preserve-timestamp");

View File

@@ -25,12 +25,16 @@ import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.file.filters.ReversibleFileListFilter;
@@ -42,9 +46,6 @@ import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Base class charged with knowing how to connect to a remote file system,
* scan it for new files and then download the files.
@@ -81,7 +82,7 @@ public abstract class AbstractInboundFileSynchronizer<F>
/**
* the path on the remote mount as a String.
*/
private volatile String remoteDirectory;
private volatile Expression remoteDirectoryExpression;
/**
* An {@link FileListFilter} that runs against the <em>remote</em> file system view.
@@ -145,7 +146,18 @@ public abstract class AbstractInboundFileSynchronizer<F>
* @param remoteDirectory The remote directory.
*/
public void setRemoteDirectory(String remoteDirectory) {
this.remoteDirectory = remoteDirectory;
this.remoteDirectoryExpression = new LiteralExpression(remoteDirectory);
}
/**
* Specify an expression that evaluates to the full path to the remote directory.
*
* @param remoteDirectoryExpression The remote directory expression.
* @since 4.2
*/
public void setRemoteDirectoryExpression(Expression remoteDirectoryExpression) {
Assert.notNull(remoteDirectoryExpression, "'remoteDirectoryExpression' must not be null");
this.remoteDirectoryExpression = remoteDirectoryExpression;
}
/**
@@ -184,7 +196,7 @@ public abstract class AbstractInboundFileSynchronizer<F>
@Override
public final void afterPropertiesSet() {
Assert.notNull(this.remoteDirectory, "remoteDirectory must not be null");
Assert.state(this.remoteDirectoryExpression != null, "'remoteDirectoryExpression' must not be null");
if (this.evaluationContext == null) {
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.beanFactory);
}
@@ -212,14 +224,15 @@ public abstract class AbstractInboundFileSynchronizer<F>
@Override
public Integer doInSession(Session<F> session) throws IOException {
F[] files = session.list(AbstractInboundFileSynchronizer.this.remoteDirectory);
String remoteDirectory = remoteDirectoryExpression.getValue(evaluationContext, String.class);
F[] files = session.list(remoteDirectory);
if (!ObjectUtils.isEmpty(files)) {
List<F> filteredFiles = AbstractInboundFileSynchronizer.this.filterFiles(files);
List<F> filteredFiles = filterFiles(files);
for (F file : filteredFiles) {
try {
if (file != null) {
AbstractInboundFileSynchronizer.this.copyFileToLocalDirectory(
AbstractInboundFileSynchronizer.this.remoteDirectory, file, localDirectory,
copyFileToLocalDirectory(
remoteDirectory, file, localDirectory,
session);
}
}

View File

@@ -731,9 +731,9 @@ Only files matching this regular expression will be picked up by this adapter.
type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide a SpEL expression which
will compute the directory
path where the files will be transferred to
Specify a SpEL expression which
will be used to evaluate the directory
path to where the files will be transferred
(e.g., "headers.['remote_dir'] +
'/myTransfers'");
</xsd:documentation>
@@ -743,9 +743,9 @@ Only files matching this regular expression will be picked up by this adapter.
type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide a SpEL expression which
will compute the temporary directory
path where files will be transferred to before they are moved to the remote-directory
Specify a SpEL expression which
will be used to evaluate the temporary directory
path to where files will be transferred before they are moved to the remote-directory
(e.g., "headers.['remote_dir'] +
'/temp/myTransfers'");
</xsd:documentation>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2014-2015 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.
@@ -38,6 +38,7 @@ import org.springframework.messaging.MessagingException;
/**
* @author Gary Russell
* @author Artem Bilan
* @since 4.0.4
*
*/
@@ -76,6 +77,7 @@ public class AbstractRemoteFileSynchronizerTests {
};
sync.setFilter(new AcceptOnceFileListFilter<String>());
sync.setRemoteDirectory("foo");
try {
sync.synchronizeToLocalDirectory(mock(File.class));

View File

@@ -215,6 +215,18 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="remote-directory-expression"
type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specify a SpEL expression which
will be used to evaluate the directory
path from where the files will be transferred
(e.g., "@someBean.fetchDirectory");
Mutually exclusive with 'remote-directory'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
@@ -500,11 +512,11 @@
<xsd:complexType name="base-ftp-adapter-type">
<xsd:complexContent>
<xsd:extension base="base-adapter-type">
<xsd:attribute name="remote-directory" type="xsd:string"
use="optional">
<xsd:attribute name="remote-directory" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
Identifies the remote directory path (e.g., "/remote/mytransfers")
Mutually exclusive with 'remote-directory-expression'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -29,7 +29,7 @@
comparator="comparator"
temporary-file-suffix=".foo"
local-filter="acceptAllFilter"
remote-directory="foo/bar">
remote-directory-expression="'foo/bar'">
<int:poller fixed-rate="1000">
<int:transactional synchronization-factory="syncFactory"/>
</int:poller>

View File

@@ -37,6 +37,7 @@ import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.expression.Expression;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.file.remote.session.CachingSessionFactory;
@@ -96,6 +97,8 @@ public class FtpInboundChannelAdapterParserTests {
FtpInboundFileSynchronizer fisync =
(FtpInboundFileSynchronizer) TestUtils.getPropertyValue(inbound, "synchronizer");
assertEquals("'foo/bar'", TestUtils.getPropertyValue(fisync, "remoteDirectoryExpression", Expression.class)
.getExpressionString());
assertNotNull(TestUtils.getPropertyValue(fisync, "localFilenameGeneratorExpression"));
assertTrue(TestUtils.getPropertyValue(fisync, "preserveTimestamp", Boolean.class));
assertEquals(".foo", TestUtils.getPropertyValue(fisync, "temporaryFileSuffix", String.class));
@@ -131,6 +134,8 @@ public class FtpInboundChannelAdapterParserTests {
String remoteFileSeparator = (String) TestUtils.getPropertyValue(fisync, "remoteFileSeparator");
assertNotNull(remoteFileSeparator);
assertEquals("/", remoteFileSeparator);
assertEquals("foo/bar", TestUtils.getPropertyValue(fisync, "remoteDirectoryExpression", Expression.class)
.getExpressionString());
}
@Test

View File

@@ -17,6 +17,7 @@
entity-manager-factory="entityManagerFactory"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
expect-single-result="true"
parameter-source=""
channel="out">
<int:poller fixed-rate="5000"/>
</int-jpa:inbound-channel-adapter>

View File

@@ -219,6 +219,18 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="remote-directory-expression"
type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Specify a SpEL expression which
will be used to evaluate the directory
path from where the files will be transferred
(e.g., "@someBean.fetchDirectory").
Mutually exclusive with 'remote-directory'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
@@ -501,12 +513,12 @@
<xsd:complexType name="base-sftp-adapter-type">
<xsd:complexContent>
<xsd:extension base="base-adapter-type">
<xsd:attribute name="remote-directory" type="xsd:string"
use="optional">
<xsd:attribute name="remote-directory" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
Identifies the directory path (e.g.,
"/temp/mytransfers")
Mutually exclusive with 'remote-directory-expression'.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@@ -41,7 +41,7 @@
session-factory="sftpSessionFactory"
channel="requestChannel"
filename-regex="f[o]+\.txt"
remote-directory="/foo"
remote-directory-expression="'/foo'"
local-directory="file:local-test-dir"
auto-create-local-directory="false"
remote-file-separator="."

View File

@@ -31,17 +31,19 @@ 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.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.expression.Expression;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizer;
import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizingMessageSource;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
/**
* @author Oleg Zhurakousky
@@ -57,16 +59,17 @@ public class InboundChannelAdapterParserTests {
@Test
public void testAutoStartup() throws Exception{
ApplicationContext context =
ConfigurableApplicationContext context =
new ClassPathXmlApplicationContext("SftpInboundAutostartup-context.xml", this.getClass());
SourcePollingChannelAdapter adapter = context.getBean("sftpAutoStartup", SourcePollingChannelAdapter.class);
assertFalse(adapter.isRunning());
context.close();
}
@Test
public void testWithLocalFiles() throws Exception{
ApplicationContext context =
ConfigurableApplicationContext context =
new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context.xml", this.getClass());
assertTrue(new File("src/main/resources").exists());
@@ -81,6 +84,8 @@ public class InboundChannelAdapterParserTests {
assertNotNull(comparator);
SftpInboundFileSynchronizer synchronizer = (SftpInboundFileSynchronizer) TestUtils.getPropertyValue(source, "synchronizer");
assertEquals("'/foo'", TestUtils.getPropertyValue(synchronizer, "remoteDirectoryExpression", Expression.class)
.getExpressionString());
assertNotNull(TestUtils.getPropertyValue(synchronizer, "localFilenameGeneratorExpression"));
assertTrue(TestUtils.getPropertyValue(synchronizer, "preserveTimestamp", Boolean.class));
String remoteFileSeparator = (String) TestUtils.getPropertyValue(synchronizer, "remoteFileSeparator");
@@ -91,42 +96,53 @@ public class InboundChannelAdapterParserTests {
assertNotNull(requestChannel.receive(2000));
FileListFilter<?> acceptAllFilter = context.getBean("acceptAllFilter", FileListFilter.class);
assertTrue(TestUtils.getPropertyValue(source, "fileSource.scanner.filter.fileFilters", Collection.class).contains(acceptAllFilter));
context.close();
}
@Test
public void testAutoChannel() {
ApplicationContext context =
ConfigurableApplicationContext context =
new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context.xml", this.getClass());
// Auto-created channel
MessageChannel autoChannel = context.getBean("autoChannel", MessageChannel.class);
SourcePollingChannelAdapter autoChannelAdapter = context.getBean("autoChannel.adapter", SourcePollingChannelAdapter.class);
SourcePollingChannelAdapter autoChannelAdapter = context.getBean("autoChannel.adapter",
SourcePollingChannelAdapter.class);
assertEquals("/foo", TestUtils
.getPropertyValue(autoChannelAdapter, "source.synchronizer.remoteDirectoryExpression", Expression.class)
.getExpressionString());
assertSame(autoChannel, TestUtils.getPropertyValue(autoChannelAdapter, "outputChannel"));
context.close();
}
@Test(expected=BeanDefinitionStoreException.class)
//exactly one of 'filename-pattern' or 'filter' is allowed on SFTP inbound adapter
public void testFailWithFilePatternAndFilter() throws Exception{
assertTrue(!new File("target/bar").exists());
new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context-fail.xml", this.getClass());
new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context-fail.xml", this.getClass()).close();
}
@Test @Ignore
public void testLocalFilesAreFound() throws Exception{
assertTrue(new File("target").exists());
new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context.xml", this.getClass());
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
"InboundChannelAdapterParserTests-context.xml", this.getClass());
assertTrue(new File("target").exists());
context.close();
}
@Test
public void testLocalDirAutoCreated() throws Exception{
assertFalse(new File("foo").exists());
new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context.xml", this.getClass());
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
"InboundChannelAdapterParserTests-context.xml", this.getClass());
assertTrue(new File("foo").exists());
context.close();
}
@Test(expected=BeanCreationException.class)
public void testLocalDirAutoCreateFailed() throws Exception{
new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context-fail-autocreate.xml", this.getClass());
new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context-fail-autocreate.xml",
this.getClass()).close();
}
@After

View File

@@ -154,6 +154,10 @@ So, the root object of the SpEL Evaluation Context is the original name of the r
Starting with _Spring Integration 3.0_, you can specify the `preserve-timestamp` attribute (default `false`); when `true`, the local file's modified timestamp will be set to the value retrieved from the server; otherwise it will be set to the current time.
Starting with _version 4.2_, you can specify `remote-directory-expression` instead of `remote-directory`, allowing
you to dynamically determine the directory on each poll.
e.g `remote-directory-expression="@myBean.determineRemoteDir()"`.
Sometimes file filtering based on the simple pattern specified via `filename-pattern` attribute might not be sufficient.
If this is the case, you can use the `filename-regex` attribute to specify a Regular Expression (e.g.
`filename-regex=".*\.test$"`).

View File

@@ -240,6 +240,10 @@ So, the root object of the SpEL Evaluation Context is the original name of the r
Starting with _Spring Integration 3.0_, you can specify the `preserve-timestamp` attribute (default `false`); when `true`, the local file's modified timestamp will be set to the value retrieved from the server; otherwise it will be set to the current time.
Starting with _version 4.2_, you can specify `remote-directory-expression` instead of `remote-directory`, allowing
you to dynamically determine the directory on each poll.
e.g `remote-directory-expression="@myBean.determineRemoteDir()"`.
Sometimes file filtering based on the simple pattern specified via `filename-pattern` attribute might not be sufficient.
If this is the case, you can use the `filename-regex` attribute to specify a Regular Expression (e.g.
`filename-regex=".*\.test$"`).

View File

@@ -207,3 +207,9 @@ In addition, a `SimpleMessageGroupProcessor` is provided that simply returns the
When an output processor produces a collection of `Message<?>`, the aggregator releases those messages individually.
Configuring the `SimpleMessageGroupProcessor` makes the aggregator a message barrier, were messages are held up
until they all arrive, and are then released individually. See <<aggregator>> for more information.
==== (S)FTP Changes
You can now specify a `remote-directory-expression` on the inbound channel adapters, to determine the directory
at runtime.
See <<ftp>> and <<sftp>> for more information.