INT-4086: (S)FTP: Add Local File Tree Support
JIRA: https://jira.spring.io/browse/INT-4086 The remote files may be carried with sub-path, not just file name. The same sub-dirs logic can be achieve with the `localFilenameGeneratorExpression` * Add the logic into `AbstractInboundFileSynchronizer` to build sub-dirs for local files * Switch on `WatchService` for the internal `FileReadingMessageSource` in the `AbstractInboundFileSynchronizingMessageSource` to react for all the changes in the `localDirectory` Introduce `RecursiveDirectoryScanner` and use it for `AbstractInboundFileSynchronizingMessageSource` by default * Add `maxDepth` and `fileVisitOptions` options to the `RecursiveDirectoryScanner` * Polishing JavaDocs, tests and docs
This commit is contained in:
committed by
Gary Russell
parent
6dbc721d73
commit
96ca9647a4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -34,6 +34,7 @@ import org.springframework.messaging.MessagingException;
|
||||
* @author Iwein Fuld
|
||||
* @author Gunnar Hillert
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class DefaultDirectoryScanner implements DirectoryScanner {
|
||||
@@ -80,12 +81,12 @@ public class DefaultDirectoryScanner implements DirectoryScanner {
|
||||
* @param file the file to try to claim.
|
||||
*/
|
||||
@Override
|
||||
public final boolean tryClaim(File file) {
|
||||
public boolean tryClaim(File file) {
|
||||
return (this.locker == null) || this.locker.lock(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<File> listFiles(File directory) throws IllegalArgumentException {
|
||||
public List<File> listFiles(File directory) throws IllegalArgumentException {
|
||||
File[] files = listEligibleFiles(directory);
|
||||
if (files == null) {
|
||||
throw new MessagingException("The path [" + directory
|
||||
|
||||
@@ -273,6 +273,10 @@ public class FileReadingMessageSource extends IntegrationObjectSupport implement
|
||||
this.useWatchService = useWatchService;
|
||||
}
|
||||
|
||||
public boolean isUseWatchService() {
|
||||
return this.useWatchService;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link WatchService} event types.
|
||||
* If {@link #setUseWatchService} isn't {@code true}, this option is ignored.
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2017 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.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.integration.file.filters.AbstractFileListFilter;
|
||||
import org.springframework.integration.file.filters.FileListFilter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The {@link DefaultDirectoryScanner} extension which walks through the directory tree
|
||||
* using {@link Files#walk(Path, int, FileVisitOption...)}.
|
||||
* <p>
|
||||
* By default this class visits all levels of the file tree without any {@link FileVisitOption}s.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*
|
||||
* @see Files#walk
|
||||
*/
|
||||
public class RecursiveDirectoryScanner extends DefaultDirectoryScanner {
|
||||
|
||||
private int maxDepth = Integer.MAX_VALUE;
|
||||
|
||||
private FileVisitOption[] fileVisitOptions = new FileVisitOption[0];
|
||||
|
||||
/**
|
||||
* The maximum number of directory levels to visit.
|
||||
* @param maxDepth the maximum number of directory levels to visit
|
||||
*/
|
||||
public void setMaxDepth(int maxDepth) {
|
||||
this.maxDepth = maxDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* The options to configure the traversal.
|
||||
* @param fileVisitOptions options to configure the traversal
|
||||
*/
|
||||
public void setFileVisitOptions(FileVisitOption... fileVisitOptions) {
|
||||
Assert.notNull(fileVisitOptions, "'fileVisitOptions' must not be null");
|
||||
this.fileVisitOptions = fileVisitOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<File> listFiles(File directory) throws IllegalArgumentException {
|
||||
FileListFilter<File> filter = getFilter();
|
||||
boolean supportAcceptFilter = filter instanceof AbstractFileListFilter;
|
||||
try {
|
||||
Stream<File> fileStream = Files.walk(directory.toPath(), this.maxDepth, this.fileVisitOptions)
|
||||
.skip(1)
|
||||
.map(Path::toFile)
|
||||
.filter(file -> !supportAcceptFilter
|
||||
|| ((AbstractFileListFilter<File>) filter).accept(file));
|
||||
|
||||
if (supportAcceptFilter) {
|
||||
return fileStream.collect(Collectors.toList());
|
||||
}
|
||||
else {
|
||||
return filter.filterFiles(fileStream.toArray(File[]::new));
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -47,6 +47,6 @@ public abstract class AbstractFileListFilter<F> implements FileListFilter<F> {
|
||||
* @param file The file.
|
||||
* @return true if the file passes the filter.
|
||||
*/
|
||||
protected abstract boolean accept(F file);
|
||||
public abstract boolean accept(F file);
|
||||
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public abstract class AbstractPersistentAcceptOnceFileListFilter<F> extends Abst
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean accept(F file) {
|
||||
public boolean accept(F file) {
|
||||
String key = buildKey(file);
|
||||
synchronized (this.monitor) {
|
||||
String newValue = value(file);
|
||||
|
||||
@@ -60,7 +60,7 @@ public class ExpressionFileListFilter<F> extends AbstractFileListFilter<F>
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean accept(F file) {
|
||||
public boolean accept(F file) {
|
||||
return this.expression.getValue(getEvaluationContext(), file, Boolean.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2016 the original author or authors.
|
||||
* Copyright 2015-2017 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.
|
||||
@@ -31,7 +31,7 @@ public class IgnoreHiddenFileListFilter extends AbstractFileListFilter<File> {
|
||||
* @return Returns {@code true} for any non-hidden files.
|
||||
*/
|
||||
@Override
|
||||
protected boolean accept(File file) {
|
||||
public boolean accept(File file) {
|
||||
return !file.isHidden();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -100,7 +101,7 @@ public abstract class AbstractInboundFileSynchronizer<F>
|
||||
* Should we <em>transfer</em> the remote file <b>timestamp</b>
|
||||
* to the local file? By default this is false.
|
||||
*/
|
||||
private volatile boolean preserveTimestamp;
|
||||
private volatile boolean preserveTimestamp;
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
@@ -313,7 +314,12 @@ public abstract class AbstractInboundFileSynchronizer<F>
|
||||
long modified = getModified(remoteFile);
|
||||
|
||||
File localFile = new File(localDirectory, localFileName);
|
||||
if (!localFile.exists() || (this.preserveTimestamp && modified != localFile.lastModified())) {
|
||||
boolean exists = localFile.exists();
|
||||
if (!exists || (this.preserveTimestamp && modified != localFile.lastModified())) {
|
||||
if (!exists &&
|
||||
localFileName.replaceAll("/", Matcher.quoteReplacement(File.separator)).contains(File.separator)) {
|
||||
localFile.getParentFile().mkdirs(); //NOSONAR - will fail on the writing below
|
||||
}
|
||||
String tempFileName = localFile.getAbsolutePath() + this.temporaryFileSuffix;
|
||||
File tempFile = new File(tempFileName);
|
||||
OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(tempFile));
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.beans.factory.BeanInitializationException;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.integration.endpoint.AbstractFetchLimitingMessageSource;
|
||||
import org.springframework.integration.file.FileReadingMessageSource;
|
||||
import org.springframework.integration.file.RecursiveDirectoryScanner;
|
||||
import org.springframework.integration.file.filters.AcceptOnceFileListFilter;
|
||||
import org.springframework.integration.file.filters.CompositeFileListFilter;
|
||||
import org.springframework.integration.file.filters.FileListFilter;
|
||||
@@ -128,6 +129,22 @@ public abstract class AbstractInboundFileSynchronizingMessageSource<F>
|
||||
this.localFileListFilter = localFileListFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the local {@link FileReadingMessageSource} to use its internal
|
||||
* {@code FileReadingMessageSource.WatchServiceDirectoryScanner}.
|
||||
* @param useWatchService the {@code boolean} flag to switch to
|
||||
* {@code FileReadingMessageSource.WatchServiceDirectoryScanner} on {@code true}.
|
||||
* @since 5.0
|
||||
*/
|
||||
public void setUseWatchService(boolean useWatchService) {
|
||||
this.fileSource.setUseWatchService(useWatchService);
|
||||
if (useWatchService) {
|
||||
this.fileSource.setWatchEvents(FileReadingMessageSource.WatchEventType.CREATE,
|
||||
FileReadingMessageSource.WatchEventType.MODIFY,
|
||||
FileReadingMessageSource.WatchEventType.DELETE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
super.afterPropertiesSet();
|
||||
@@ -149,7 +166,15 @@ public abstract class AbstractInboundFileSynchronizingMessageSource<F>
|
||||
this.localFileListFilter = new FileSystemPersistentAcceptOnceFileListFilter(
|
||||
new SimpleMetadataStore(), getComponentName());
|
||||
}
|
||||
this.fileSource.setFilter(this.buildFilter());
|
||||
FileListFilter<File> filter = buildFilter();
|
||||
if (!this.fileSource.isUseWatchService()) {
|
||||
RecursiveDirectoryScanner directoryScanner = new RecursiveDirectoryScanner();
|
||||
directoryScanner.setFilter(filter);
|
||||
this.fileSource.setScanner(directoryScanner);
|
||||
}
|
||||
else {
|
||||
this.fileSource.setFilter(filter);
|
||||
}
|
||||
if (this.getBeanFactory() != null) {
|
||||
this.fileSource.setBeanFactory(this.getBeanFactory());
|
||||
}
|
||||
@@ -168,12 +193,14 @@ public abstract class AbstractInboundFileSynchronizingMessageSource<F>
|
||||
@Override
|
||||
public void start() {
|
||||
this.running = true;
|
||||
this.fileSource.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
this.running = false;
|
||||
try {
|
||||
this.fileSource.stop();
|
||||
this.synchronizer.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2017 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.file;
|
||||
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import org.springframework.integration.file.filters.AcceptOnceFileListFilter;
|
||||
|
||||
/**
|
||||
* @author Iwein Fuld
|
||||
* @author Gunnar Hillert
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public class RecursiveDirectoryScannerTests {
|
||||
|
||||
private File subFolder;
|
||||
|
||||
private File subSubFolder;
|
||||
|
||||
private File topLevelFile;
|
||||
|
||||
private File subLevelFile;
|
||||
|
||||
private File subSubLevelFile;
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder recursivePath = new TemporaryFolder();
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
this.subFolder = this.recursivePath.newFolder("subFolder");
|
||||
this.subSubFolder = new File(this.subFolder, "subSubFolder");
|
||||
this.subSubFolder.mkdir();
|
||||
this.topLevelFile = this.recursivePath.newFile("file1");
|
||||
this.subLevelFile = new File(this.subFolder, "file2");
|
||||
this.subLevelFile.createNewFile();
|
||||
this.subSubLevelFile = new File(this.subSubFolder, "file3");
|
||||
this.subSubLevelFile.createNewFile();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnAllFilesIncludingDirs() throws IOException {
|
||||
RecursiveDirectoryScanner scanner = new RecursiveDirectoryScanner();
|
||||
scanner.setFilter(new AcceptOnceFileListFilter<>());
|
||||
List<File> files = scanner.listFiles(this.recursivePath.getRoot());
|
||||
assertEquals(5, files.size());
|
||||
assertThat(files, hasItem(this.topLevelFile));
|
||||
assertThat(files, hasItem(this.subLevelFile));
|
||||
assertThat(files, hasItem(this.subSubLevelFile));
|
||||
assertThat(files, hasItem(this.subFolder));
|
||||
assertThat(files, hasItem(this.subSubFolder));
|
||||
File file = new File(this.subSubFolder, "file4");
|
||||
file.createNewFile();
|
||||
files = scanner.listFiles(this.recursivePath.getRoot());
|
||||
assertEquals(1, files.size());
|
||||
assertThat(files, hasItem(file));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -131,6 +131,7 @@ public class AbstractRemoteFileSynchronizerTests {
|
||||
source.setMaxFetchSize(1);
|
||||
source.setBeanName("maxFetchSizeSource");
|
||||
source.afterPropertiesSet();
|
||||
source.start();
|
||||
|
||||
source.receive();
|
||||
assertEquals(1, count.get());
|
||||
@@ -138,7 +139,7 @@ public class AbstractRemoteFileSynchronizerTests {
|
||||
source.receive();
|
||||
sync.synchronizeToLocalDirectory(mock(File.class), 1);
|
||||
source.receive();
|
||||
sync.close();
|
||||
source.stop();
|
||||
}
|
||||
|
||||
private AbstractInboundFileSynchronizer<String> createLimitingSynchronizer(final AtomicInteger count) {
|
||||
|
||||
@@ -74,13 +74,7 @@ public class FtpInboundRemoteFileSystemSynchronizerTests {
|
||||
@Before
|
||||
@After
|
||||
public void cleanup() {
|
||||
File file = new File("test");
|
||||
if (file.exists()) {
|
||||
for (File f : file.listFiles()) {
|
||||
f.delete();
|
||||
}
|
||||
file.delete();
|
||||
}
|
||||
recursiveDelete(new File("test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -109,7 +103,7 @@ public class FtpInboundRemoteFileSystemSynchronizerTests {
|
||||
synchronizer.setFilter(filter);
|
||||
|
||||
ExpressionParser expressionParser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||
Expression expression = expressionParser.parseExpression("#this.toUpperCase() + '.a'");
|
||||
Expression expression = expressionParser.parseExpression("'subdir/' + #this.toUpperCase() + '.a'");
|
||||
synchronizer.setLocalFilenameGeneratorExpression(expression);
|
||||
synchronizer.setBeanFactory(mock(BeanFactory.class));
|
||||
synchronizer.afterPropertiesSet();
|
||||
@@ -126,6 +120,8 @@ public class FtpInboundRemoteFileSystemSynchronizerTests {
|
||||
localFileListFilter.addFilter(localAcceptOnceFilter);
|
||||
ms.setLocalFilter(localFileListFilter);
|
||||
ms.afterPropertiesSet();
|
||||
ms.start();
|
||||
|
||||
Message<File> atestFile = ms.receive();
|
||||
assertNotNull(atestFile);
|
||||
assertEquals("A.TEST.a", atestFile.getPayload().getName());
|
||||
@@ -146,13 +142,13 @@ public class FtpInboundRemoteFileSystemSynchronizerTests {
|
||||
// two times because on the third receive (above) the internal queue will be empty, so it will attempt
|
||||
verify(synchronizer, times(2)).synchronizeToLocalDirectory(localDirectory, Integer.MIN_VALUE);
|
||||
|
||||
assertTrue(new File("test/A.TEST.a").exists());
|
||||
assertTrue(new File("test/B.TEST.a").exists());
|
||||
assertTrue(new File("test/subdir/A.TEST.a").exists());
|
||||
assertTrue(new File("test/subdir/B.TEST.a").exists());
|
||||
|
||||
TestUtils.getPropertyValue(localAcceptOnceFilter, "seenSet", Collection.class).clear();
|
||||
|
||||
new File("test/A.TEST.a").delete();
|
||||
new File("test/B.TEST.a").delete();
|
||||
new File("test/subdir/A.TEST.a").delete();
|
||||
new File("test/subdir/B.TEST.a").delete();
|
||||
// the remote filter should prevent a re-fetch
|
||||
nothing = ms.receive();
|
||||
assertNull(nothing);
|
||||
@@ -192,6 +188,24 @@ public class FtpInboundRemoteFileSystemSynchronizerTests {
|
||||
assertEquals(0, localDirectory.list().length);
|
||||
}
|
||||
|
||||
private static void recursiveDelete(File file) {
|
||||
if (file != null && file.exists()) {
|
||||
File[] files = file.listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
if (f.isDirectory()) {
|
||||
recursiveDelete(f);
|
||||
}
|
||||
else {
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class TestFtpSessionFactory extends AbstractFtpSessionFactory<FTPClient> {
|
||||
|
||||
private final Collection<FTPFile> ftpFiles = new ArrayList<FTPFile>();
|
||||
|
||||
@@ -121,6 +121,8 @@ public class SftpInboundRemoteFileSystemSynchronizerTests {
|
||||
localFileListFilter.addFilter(localAcceptOnceFilter);
|
||||
ms.setLocalFilter(localFileListFilter);
|
||||
ms.afterPropertiesSet();
|
||||
ms.start();
|
||||
|
||||
Message<File> atestFile = ms.receive();
|
||||
assertNotNull(atestFile);
|
||||
assertEquals("a.test", atestFile.getPayload().getName());
|
||||
|
||||
@@ -162,6 +162,13 @@ It is used by the internal (`PriorityBlockingQueue`) to reorder its content acco
|
||||
Therefore, to process files in a specific order, you should provide a comparator to the `FileReadingMessageSource`,
|
||||
rather than ordering the list produced by a custom `DirectoryScanner`.
|
||||
|
||||
Starting with _version 5.0_, a new `RecursiveDirectoryScanner` is presented to perform file tree visiting.
|
||||
The implementation is based on the `Files.walk(Path start, int maxDepth, FileVisitOption... options)` functionality.
|
||||
The root directory (`DirectoryScanner.listFiles(File)` argument) is excluded from the result.
|
||||
All other sub-directories includes/excludes are based on the target `FileListFilter` implementation.
|
||||
For example the `SimplePatternFileListFilter` filters directories by default.
|
||||
See `AbstractDirectoryAwareFileListFilter` and its implementations for more information.
|
||||
|
||||
[[file-namespace-support]]
|
||||
==== Namespace Support
|
||||
|
||||
|
||||
@@ -389,8 +389,7 @@ This will work for any `ResettableFileListFilter`.
|
||||
remote-directory-expression="'/sftpSource'"
|
||||
local-directory="file:myLocalDir"
|
||||
auto-create-local-directory="true"
|
||||
filename-pattern="*.txt"
|
||||
local-filter="acceptOnceFilter">
|
||||
filename-pattern="*.txt">
|
||||
<int:poller fixed-rate="1000">
|
||||
<int:transactional synchronization-factory="syncFactory" />
|
||||
</int:poller>
|
||||
@@ -400,13 +399,22 @@ This will work for any `ResettableFileListFilter`.
|
||||
class="org.springframework.integration.file.filters.AcceptOnceFileListFilter" />
|
||||
|
||||
<int:transaction-synchronization-factory id="syncFactory">
|
||||
<int:after-rollback expression="@acceptOnceFilter.remove(payload)" />
|
||||
<int:after-rollback expression="payload.delete()" />
|
||||
</int:transaction-synchronization-factory>
|
||||
|
||||
<bean id="transactionManager"
|
||||
class="org.springframework.integration.transaction.PseudoTransactionManager" />
|
||||
----
|
||||
|
||||
Starting with _version 5.0_, the Inbound Channel Adapter can build sub-directories locally according the generated local file name.
|
||||
That can be a remote sub-path as well.
|
||||
To be able to read local directory recursively for modification according the hierarchy support, an internal `FileReadingMessageSource` now have been switched to use a new `RecursiveDirectoryScanner` based on the `Files.walk()` algorithm.
|
||||
Also the `AbstractInboundFileSynchronizingMessageSource` can now be switched to the `WatchService` -based `DirectoryScanner` via `setUseWatchService()` option.
|
||||
It is also configured for all the `WatchEventType` s to react for any modifications in local directory.
|
||||
The reprocessing sample above is based on the build-in functionality of the `FileReadingMessageSource.WatchServiceDirectoryScanner` to perform `ResettableFileListFilter.remove()` when the file is deleted (`StandardWatchEventKinds.ENTRY_DELETE`) from the local directory.
|
||||
See <<watch-service-directory-scanner>> for more information.
|
||||
|
||||
|
||||
==== Configuring with Java Configuration
|
||||
|
||||
The following Spring Boot application provides an example of configuring the inbound adapter using Java configuration:
|
||||
|
||||
@@ -427,8 +427,7 @@ This will work for any `ResettableFileListFilter`.
|
||||
remote-directory-expression="'/sftpSource'"
|
||||
local-directory="file:myLocalDir"
|
||||
auto-create-local-directory="true"
|
||||
filename-pattern="*.txt"
|
||||
local-filter="acceptOnceFilter">
|
||||
filename-pattern="*.txt">
|
||||
<int:poller fixed-rate="1000">
|
||||
<int:transactional synchronization-factory="syncFactory" />
|
||||
</int:poller>
|
||||
@@ -438,13 +437,21 @@ This will work for any `ResettableFileListFilter`.
|
||||
class="org.springframework.integration.file.filters.AcceptOnceFileListFilter" />
|
||||
|
||||
<int:transaction-synchronization-factory id="syncFactory">
|
||||
<int:after-rollback expression="@acceptOnceFilter.remove(payload)" />
|
||||
<int:after-rollback expression="payload.delete()" />
|
||||
</int:transaction-synchronization-factory>
|
||||
|
||||
<bean id="transactionManager"
|
||||
class="org.springframework.integration.transaction.PseudoTransactionManager" />
|
||||
----
|
||||
|
||||
Starting with _version 5.0_, the Inbound Channel Adapter can build sub-directories locally according the generated local file name.
|
||||
That can be a remote sub-path as well.
|
||||
To be able to read local directory recursively for modification according the hierarchy support, an internal `FileReadingMessageSource` now have been switched to use a new `RecursiveDirectoryScanner` based on the `Files.walk()` algorithm.
|
||||
Also the `AbstractInboundFileSynchronizingMessageSource` can now be switched to the `WatchService` -based `DirectoryScanner` via `setUseWatchService()` option.
|
||||
It is also configured for all the `WatchEventType` s to react for any modifications in local directory.
|
||||
The reprocessing sample above is based on the build-in functionality of the `FileReadingMessageSource.WatchServiceDirectoryScanner` to perform `ResettableFileListFilter.remove()` when the file is deleted (`StandardWatchEventKinds.ENTRY_DELETE`) from the local directory.
|
||||
See <<watch-service-directory-scanner>> for more information.
|
||||
|
||||
==== Configuring with Java Configuration
|
||||
|
||||
The following Spring Boot application provides an example of configuring the inbound adapter using Java configuration:
|
||||
|
||||
@@ -125,8 +125,12 @@ The (S)FTP streaming inbound channel adapters now add remote file information in
|
||||
|
||||
The FTP and SFTP outbound channel adapters, as well as `PUT` command of the outbound gateways, now support `InputStream` as `payload`, too.
|
||||
|
||||
The inbound channel adapters now can build file tree locally and use a new `RecursiveDirectoryScanner` by default for local directory.
|
||||
Also these adapters can now be switched to the `WatchService` instead.
|
||||
|
||||
See <<ftp>> and <<sftp>> for more information.
|
||||
|
||||
|
||||
==== Integration Properties
|
||||
|
||||
Since _version 4.3.2_ a new `spring.integration.readOnly.headers` global property has been added to customize the list of headers which should not be copied to a newly created `Message` by the `MessageBuilder`.
|
||||
|
||||
Reference in New Issue
Block a user