Skip to content

Commit a2d1edf

Browse files
garyrussellartembilan
authored andcommitted
GH-3488: Fix Persistent Filters with Recursion
Resolves #3488 Resolves two problems: - When changes are made deep in the directory tree, they were not detected because the directory is in the metadata store and only passes the filter if a file immediately under it is changed, changing the directory's timestamp. This is solved by subclassing `AbstractDirectoryAwareFileListFilter`, allowing its `alwaysAcceptDirectories` property to be set. - Only the filename was used as a metadata key; causing problems if a file with the same name appears multiple times in the tree. This is solved with a new property on `AbstractDirectoryAwareFileListFilter` used by the gateways to determine whether to filter the raw file names returned by the session (previous behavior) or the full path relative to the root directory. **cherry-pick to 5.4.x, 5.3.x** * Some code style clean up # Conflicts: # spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java # src/reference/asciidoc/whats-new.adoc
1 parent 8466f09 commit a2d1edf

File tree

17 files changed

+261
-80
lines changed

17 files changed

+261
-80
lines changed

spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractDirectoryAwareFileListFilter.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2019 the original author or authors.
2+
* Copyright 2016-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,14 +21,19 @@
2121
* This permits, for example, pattern matching on just files when using recursion
2222
* to examine a directory tree.
2323
*
24+
* @param <F> the file type.
25+
*
2426
* @author Gary Russell
27+
*
2528
* @since 5.0
2629
*
2730
*/
2831
public abstract class AbstractDirectoryAwareFileListFilter<F> extends AbstractFileListFilter<F> {
2932

3033
private boolean alwaysAcceptDirectories;
3134

35+
private boolean forRecursion;
36+
3237
/**
3338
* Set to true so that filters that support this feature can unconditionally pass
3439
* directories; default false.
@@ -38,6 +43,22 @@ public void setAlwaysAcceptDirectories(boolean alwaysAcceptDirectories) {
3843
this.alwaysAcceptDirectories = alwaysAcceptDirectories;
3944
}
4045

46+
@Override
47+
public boolean isForRecursion() {
48+
return this.forRecursion;
49+
}
50+
51+
/**
52+
* Set to true to inform a recursive gateway operation to use the full file path as
53+
* the metadata key. Also sets {@link #alwaysAcceptDirectories}.
54+
* @param forRecursion true to use the full path.
55+
* @since 5.3.6
56+
*/
57+
public void setForRecursion(boolean forRecursion) {
58+
this.forRecursion = forRecursion;
59+
this.alwaysAcceptDirectories = forRecursion;
60+
}
61+
4162
protected boolean alwaysAccept(F file) {
4263
return file != null && this.alwaysAcceptDirectories && isDirectory(file);
4364
}

spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractPersistentAcceptOnceFileListFilter.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2019 the original author or authors.
2+
* Copyright 2013-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,13 +31,15 @@
3131
* Files are deemed as already 'seen' if they exist in the store and have the
3232
* same modified time as the current file.
3333
*
34+
* @param <F> the file type.
35+
*
3436
* @author Gary Russell
3537
* @author Artem Bilan
3638
*
3739
* @since 3.0
3840
*
3941
*/
40-
public abstract class AbstractPersistentAcceptOnceFileListFilter<F> extends AbstractFileListFilter<F>
42+
public abstract class AbstractPersistentAcceptOnceFileListFilter<F> extends AbstractDirectoryAwareFileListFilter<F>
4143
implements ReversibleFileListFilter<F>, ResettableFileListFilter<F>, Closeable {
4244

4345
protected final ConcurrentMetadataStore store; // NOSONAR
@@ -73,6 +75,9 @@ public void setFlushOnUpdate(boolean flushOnUpdate) {
7375

7476
@Override
7577
public boolean accept(F file) {
78+
if (alwaysAccept(file)) {
79+
return true;
80+
}
7681
String key = buildKey(file);
7782
String newValue = value(file);
7883
String oldValue = this.store.putIfAbsent(key, newValue);

spring-integration-file/src/main/java/org/springframework/integration/file/filters/CompositeFileListFilter.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -56,16 +56,23 @@ public class CompositeFileListFilter<F>
5656

5757
private boolean allSupportAccept = true;
5858

59+
private boolean oneIsForRecursion;
60+
5961

6062
public CompositeFileListFilter() {
6163
this.fileFilters = new LinkedHashSet<>();
6264
}
6365

6466
public CompositeFileListFilter(Collection<? extends FileListFilter<F>> fileFilters) {
6567
this.fileFilters = new LinkedHashSet<>(fileFilters);
66-
this.allSupportAccept = fileFilters.stream().allMatch(FileListFilter<F>::supportsSingleFileFiltering);
68+
this.allSupportAccept = fileFilters.stream().allMatch(FileListFilter::supportsSingleFileFiltering);
69+
this.oneIsForRecursion = fileFilters.stream().anyMatch(FileListFilter::isForRecursion);
6770
}
6871

72+
@Override
73+
public boolean isForRecursion() {
74+
return this.oneIsForRecursion;
75+
}
6976

7077
@Override
7178
public void close() throws IOException {
@@ -77,7 +84,6 @@ public void close() throws IOException {
7784
}
7885

7986
public CompositeFileListFilter<F> addFilter(FileListFilter<F> filter) {
80-
this.allSupportAccept &= filter.supportsSingleFileFiltering();
8187
return addFilters(Collections.singletonList(filter));
8288
}
8389

@@ -114,11 +120,10 @@ public CompositeFileListFilter<F> addFilters(Collection<? extends FileListFilter
114120
throw new IllegalStateException(e);
115121
}
116122
}
123+
this.allSupportAccept &= elf.supportsSingleFileFiltering();
124+
this.oneIsForRecursion |= elf.isForRecursion();
117125
}
118126
this.fileFilters.addAll(filtersToAdd);
119-
if (this.allSupportAccept) {
120-
this.allSupportAccept = filtersToAdd.stream().allMatch(FileListFilter<F>::supportsSingleFileFiltering);
121-
}
122127
return this;
123128
}
124129

spring-integration-file/src/main/java/org/springframework/integration/file/filters/FileListFilter.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -64,4 +64,13 @@ default boolean supportsSingleFileFiltering() {
6464
return false;
6565
}
6666

67+
/**
68+
* Return true if this filter is being used for recursion.
69+
* @return whether or not to filter based on the full path.
70+
* @since 5.3.6
71+
*/
72+
default boolean isForRecursion() {
73+
return false;
74+
}
75+
6776
}

spring-integration-file/src/main/java/org/springframework/integration/file/filters/FileSystemPersistentAcceptOnceFileListFilter.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2019 the original author or authors.
2+
* Copyright 2013-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323

2424
/**
2525
* @author Gary Russell
26+
*
2627
* @since 3.0
2728
*
2829
*/
@@ -52,5 +53,9 @@ protected boolean fileStillExists(File file) {
5253
return file.exists();
5354
}
5455

56+
@Override
57+
protected boolean isDirectory(File file) {
58+
return file.isDirectory();
59+
}
5560

5661
}

spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
9797
*/
9898
private FileListFilter<F> filter;
9999

100+
private boolean filterAfterEnhancement;
101+
100102
/**
101103
* A {@link FileListFilter} that runs against the <em>local</em> file system view when
102104
* using MPUT.
@@ -402,6 +404,15 @@ public void setCharset(String charset) {
402404
*/
403405
public void setFilter(FileListFilter<F> filter) {
404406
this.filter = filter;
407+
this.filterAfterEnhancement = filter != null
408+
&& filter.isForRecursion()
409+
&& filter.supportsSingleFileFiltering()
410+
&& this.options.contains(Option.RECURSIVE);
411+
if (filter != null && !filter.isForRecursion()) {
412+
this.logger.warn("When using recursion, you will normally want to set the filter's "
413+
+ "'forRecursion' property; otherwise files added deep into the "
414+
+ "directory tree may not be detected");
415+
}
405416
}
406417

407418
/**
@@ -947,12 +958,19 @@ private List<F> listFilesInRemoteDir(Session<F> session, String directory, Strin
947958
List<F> lsFiles = new ArrayList<>();
948959
String remoteDirectory = buildRemotePath(directory, subDirectory);
949960

950-
F[] files = session.list(remoteDirectory);
951-
boolean recursion = this.options.contains(Option.RECURSIVE);
961+
F[] list = session.list(remoteDirectory);
962+
List<F> files;
963+
if (!this.filterAfterEnhancement) {
964+
files = filterFiles(list);
965+
}
966+
else {
967+
files = Arrays.asList(list);
968+
}
952969
if (!ObjectUtils.isEmpty(files)) {
953-
for (F file : filterFiles(files)) {
970+
for (F file : files) {
954971
if (file != null) {
955-
processFile(session, directory, subDirectory, lsFiles, recursion, file);
972+
processFile(session, directory, subDirectory, lsFiles, this.options.contains(Option.RECURSIVE),
973+
file);
956974
}
957975
}
958976
}
@@ -974,29 +992,51 @@ protected final List<F> filterFiles(F[] files) {
974992
return (this.filter != null) ? this.filter.filterFiles(files) : Arrays.asList(files);
975993
}
976994

995+
protected final F filterFile(F file) {
996+
if (this.filter.accept(file)) {
997+
return file;
998+
}
999+
else {
1000+
return null;
1001+
}
1002+
}
1003+
9771004
private void processFile(Session<F> session, String directory, String subDirectory, List<F> lsFiles,
9781005
boolean recursion, F file) throws IOException {
9791006

980-
String fileName = getFilename(file);
981-
String fileSep = this.remoteFileTemplate.getRemoteFileSeparator();
982-
boolean isDots = ".".equals(fileName)
983-
|| "..".equals(fileName)
984-
|| fileName.endsWith(fileSep + ".")
985-
|| fileName.endsWith(fileSep + "..");
986-
if (this.options.contains(Option.SUBDIRS) || !isDirectory(file)) {
987-
if (recursion && StringUtils.hasText(subDirectory) && (!isDots || this.options.contains(Option.ALL))) {
988-
lsFiles.add(enhanceNameWithSubDirectory(file, subDirectory));
1007+
F fileToAdd = file;
1008+
if (recursion && StringUtils.hasText(subDirectory)) {
1009+
fileToAdd = enhanceNameWithSubDirectory(file, subDirectory);
1010+
}
1011+
if (this.filterAfterEnhancement) {
1012+
if (!this.filter.accept(fileToAdd)) {
1013+
return;
9891014
}
1015+
}
1016+
String fileName = getFilename(fileToAdd);
1017+
final boolean isDirectory = isDirectory(file);
1018+
boolean isDots = hasDots(fileName);
1019+
if ((this.options.contains(Option.SUBDIRS) || !isDirectory)
1020+
&& (!isDots || this.options.contains(Option.ALL))) {
1021+
9901022
else if (this.options.contains(Option.ALL) || !isDots) {
9911023
lsFiles.add(file);
9921024
}
9931025
}
994-
if (recursion && isDirectory(file) && !isDots) {
995-
lsFiles.addAll(listFilesInRemoteDir(session, directory,
996-
subDirectory + fileName + fileSep));
1026+
1027+
if (recursion && isDirectory && !isDots) {
1028+
lsFiles.addAll(listFilesInRemoteDir(session, directory, fileName +
1029+
this.remoteFileTemplate.getRemoteFileSeparator()));
9971030
}
9981031
}
9991032

1033+
private boolean hasDots(String fileName) {
1034+
String fileSeparator = this.remoteFileTemplate.getRemoteFileSeparator();
1035+
return ".".equals(fileName)
1036+
|| "..".equals(fileName)
1037+
|| fileName.endsWith(fileSeparator + ".")
1038+
|| fileName.endsWith(fileSeparator + "..");
1039+
}
10001040
protected final List<File> filterMputFiles(File[] files) {
10011041
if (files == null) {
10021042
return Collections.emptyList();

0 commit comments

Comments
 (0)