This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 38042d59704 optimize camel-file when filtering files by name (#16663)
38042d59704 is described below

commit 38042d5970471752e116eb82602bd9eae8d152c1
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Dec 30 08:42:32 2024 +0100

    optimize camel-file when filtering files by name (#16663)
    
    * CAMEL-17648: camel-file - Optimize file consumer when filtering file 
names.
---
 .../camel/component/file/azure/FilesConsumer.java  | 27 +++++--
 ...atcherGenericFileFilter.java => AntFilter.java} | 19 ++---
 .../file/AntPathMatcherGenericFileFilter.java      |  4 +-
 .../apache/camel/component/file/FileConsumer.java  | 84 +++++++++++++-------
 .../camel/component/file/GenericFileConsumer.java  | 90 ++++++++++++++--------
 .../camel/component/file/GenericFileEndpoint.java  |  8 +-
 .../camel/component/file/GenericFileFilter.java    |  2 +
 ...ricFileFilter.java => OptimizedFileFilter.java} | 20 +++--
 .../MarkerFileExclusiveReadLockStrategy.java       |  7 +-
 .../camel/component/file/remote/FtpConsumer.java   | 71 ++++++++++-------
 .../camel/component/file/remote/FtpUtils.java      | 35 ++++++++-
 .../camel/component/file/remote/RemoteFile.java    |  3 +-
 .../camel/component/file/remote/SftpConsumer.java  | 67 +++++++++-------
 .../remote/RemoteFileIgnoreDoPollErrorTest.java    |  8 +-
 .../integration/SftpMoveWithOutMessageTest.java    |  2 +
 .../file/FileConsumerFileFilterOptimizedTest.java  | 85 ++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_10.adoc    | 11 +++
 17 files changed, 389 insertions(+), 154 deletions(-)

diff --git 
a/components/camel-azure/camel-azure-files/src/main/java/org/apache/camel/component/file/azure/FilesConsumer.java
 
b/components/camel-azure/camel-azure-files/src/main/java/org/apache/camel/component/file/azure/FilesConsumer.java
index d329a0452dd..e11f4635906 100644
--- 
a/components/camel-azure/camel-azure-files/src/main/java/org/apache/camel/component/file/azure/FilesConsumer.java
+++ 
b/components/camel-azure/camel-azure-files/src/main/java/org/apache/camel/component/file/azure/FilesConsumer.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.file.azure;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
+import java.util.function.Supplier;
 
 import com.azure.storage.file.share.models.ShareFileItem;
 import org.apache.camel.Message;
@@ -33,6 +34,7 @@ import 
org.apache.camel.component.file.remote.RemoteFileConsumer;
 import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.URISupport;
+import org.apache.camel.util.function.Suppliers;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -152,8 +154,10 @@ public class FilesConsumer extends 
RemoteFileConsumer<ShareFileItem> {
             int depth, ShareFileItem[] listedFileItems, ShareFileItem dir) {
 
         if (endpoint.isRecursive() && depth < endpoint.getMaxDepth()) {
-            var remote = asRemoteFile(path, dir);
-            if (isValidFile(remote, true, listedFileItems)) {
+            Supplier<GenericFile<ShareFileItem>> remote = 
Suppliers.memorize(() -> asRemoteFile(path, dir));
+            String absoluteFilePath = FilesPath.concat(path, dir.getName());
+            Supplier<String> relative = getRelativeFilePath(endpointPath, 
path, absoluteFilePath, dir);
+            if (isValidFile(remote, dir.getName(), absoluteFilePath, relative, 
true, listedFileItems)) {
                 String dirName = dir.getName();
                 String dirPath = FilesPath.concat(path, dirName);
                 boolean canPollMore = doSafePollSubDirectory(dirPath, dirName, 
polledFiles, depth);
@@ -169,9 +173,12 @@ public class FilesConsumer extends 
RemoteFileConsumer<ShareFileItem> {
             String path, List<GenericFile<ShareFileItem>> polledFiles, int 
depth,
             ShareFileItem[] listedFileItems, ShareFileItem file) {
         if (depth >= endpoint.getMinDepth()) {
-            var remote = asRemoteFile(path, file);
-            if (isValidFile(remote, false, listedFileItems)) {
-                polledFiles.add(remote);
+            Supplier<GenericFile<ShareFileItem>> remote = 
Suppliers.memorize(() -> asRemoteFile(path, file));
+            String absoluteFilePath = FilesPath.concat(path, file.getName());
+            Supplier<String> relative = getRelativeFilePath(endpointPath, 
path, absoluteFilePath, file);
+            if (isValidFile(remote, file.getName(), absoluteFilePath, 
relative, false,
+                    listedFileItems)) {
+                polledFiles.add(remote.get());
             }
         }
     }
@@ -193,7 +200,7 @@ public class FilesConsumer extends 
RemoteFileConsumer<ShareFileItem> {
 
     @Override
     protected boolean isMatched(
-            GenericFile<ShareFileItem> file, String doneFileName,
+            Supplier<GenericFile<ShareFileItem>> file, String doneFileName,
             ShareFileItem[] files) {
         String onlyName = FileUtil.stripPath(doneFileName);
 
@@ -232,6 +239,14 @@ public class FilesConsumer extends 
RemoteFileConsumer<ShareFileItem> {
         return answer;
     }
 
+    @Override
+    protected Supplier<String> getRelativeFilePath(String endpointPath, String 
path, String absolutePath, ShareFileItem file) {
+        return () -> {
+            String relativePath = StringHelper.after(absolutePath, 
endpointPath);
+            return FilesPath.ensureRelative(relativePath);
+        };
+    }
+
     @Override
     protected void updateFileHeaders(GenericFile<ShareFileItem> file, Message 
message) {
         message.setHeader(FilesHeaders.FILE_LENGTH, file.getFileLength());
diff --git 
a/components/camel-file/src/main/java/org/apache/camel/component/file/AntPathMatcherGenericFileFilter.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/AntFilter.java
similarity index 82%
copy from 
components/camel-file/src/main/java/org/apache/camel/component/file/AntPathMatcherGenericFileFilter.java
copy to 
components/camel-file/src/main/java/org/apache/camel/component/file/AntFilter.java
index 02f72c8c117..7e2b7711729 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/AntPathMatcherGenericFileFilter.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/AntFilter.java
@@ -20,31 +20,26 @@ package org.apache.camel.component.file;
  * File filter using AntPathMatcher.
  * <p/>
  * Exclude take precedence over includes. If a file match both exclude and 
include it will be regarded as excluded.
- *
- * @param <T>
  */
-public class AntPathMatcherGenericFileFilter<T> implements 
GenericFileFilter<T> {
+public class AntFilter {
 
     private final AntPathMatcherFileFilter filter;
 
-    public AntPathMatcherGenericFileFilter() {
+    public AntFilter() {
         filter = new AntPathMatcherFileFilter();
     }
 
-    public AntPathMatcherGenericFileFilter(String... includes) {
+    public AntFilter(String... includes) {
         filter = new AntPathMatcherFileFilter();
         filter.setIncludes(includes);
     }
 
-    @Override
-    public boolean accept(GenericFile<T> file) {
+    public boolean accept(boolean directory, String relativeFilePath) {
         // directories should always be accepted by ANT path matcher
-        if (file.isDirectory()) {
+        if (directory) {
             return true;
         }
-
-        String path = file.getRelativeFilePath();
-        return filter.acceptPathName(path);
+        return filter.acceptPathName(relativeFilePath);
     }
 
     public String[] getExcludes() {
@@ -78,7 +73,7 @@ public class AntPathMatcherGenericFileFilter<T> implements 
GenericFileFilter<T>
     }
 
     /**
-     * Sets case sensitive flag on {@link 
org.apache.camel.component.file.AntPathMatcherFileFilter}
+     * Sets case sensitive flag on {@link AntPathMatcherFileFilter}
      * <p/>
      * Is by default turned on <tt>true</tt>.
      */
diff --git 
a/components/camel-file/src/main/java/org/apache/camel/component/file/AntPathMatcherGenericFileFilter.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/AntPathMatcherGenericFileFilter.java
index 02f72c8c117..ae7902a03a9 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/AntPathMatcherGenericFileFilter.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/AntPathMatcherGenericFileFilter.java
@@ -21,8 +21,10 @@ package org.apache.camel.component.file;
  * <p/>
  * Exclude take precedence over includes. If a file match both exclude and 
include it will be regarded as excluded.
  *
- * @param <T>
+ * @param      <T>
+ * @deprecated     use {@link AntFilter}
  */
+@Deprecated
 public class AntPathMatcherGenericFileFilter<T> implements 
GenericFileFilter<T> {
 
     private final AntPathMatcherFileFilter filter;
diff --git 
a/components/camel-file/src/main/java/org/apache/camel/component/file/FileConsumer.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/FileConsumer.java
index 1cc3327ef1c..d2b6dfbd57b 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/FileConsumer.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/FileConsumer.java
@@ -27,6 +27,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Supplier;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
@@ -39,6 +40,7 @@ import org.apache.camel.resume.ResumeStrategy;
 import org.apache.camel.support.resume.Resumables;
 import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.function.Suppliers;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -109,12 +111,11 @@ public class FileConsumer extends 
GenericFileConsumer<File> implements ResumeAwa
             }
 
             // creates a generic file
-            GenericFile<File> gf
-                    = asGenericFile(endpointPath, file, 
getEndpoint().getCharset(), getEndpoint().isProbeContentType());
+            Supplier<GenericFile<File>> gf = Suppliers.memorize(
+                    () -> asGenericFile(endpointPath, file, 
getEndpoint().getCharset(), getEndpoint().isProbeContentType()));
 
             if (resumeStrategy != null) {
-                final ResumeAdapter adapter = setupResumeStrategy(gf);
-
+                final ResumeAdapter adapter = setupResumeStrategy(gf.get());
                 if (adapter instanceof DirectoryEntriesResumeAdapter 
directoryEntriesResumeAdapter) {
                     LOG.trace("Running the resume process for file {}", file);
                     if (directoryEntriesResumeAdapter.resume(file)) {
@@ -131,7 +132,8 @@ public class FileConsumer extends GenericFileConsumer<File> 
implements ResumeAwa
         return false;
     }
 
-    private boolean processEntry(List<GenericFile<File>> fileList, int depth, 
File file, GenericFile<File> gf, File[] files) {
+    private boolean processEntry(
+            List<GenericFile<File>> fileList, int depth, File file, 
Supplier<GenericFile<File>> gf, File[] files) {
         if (file.isDirectory()) {
             return processDirectoryEntry(fileList, depth, file, gf, files);
         } else {
@@ -141,32 +143,40 @@ public class FileConsumer extends 
GenericFileConsumer<File> implements ResumeAwa
         return false;
     }
 
-    private void processFileEntry(List<GenericFile<File>> fileList, int depth, 
File file, GenericFile<File> gf, File[] files) {
+    private void processFileEntry(
+            List<GenericFile<File>> fileList, int depth, File file, 
Supplier<GenericFile<File>> gf, File[] files) {
         // Windows can report false to a file on a share so regard it
         // always as a file (if it is not a directory)
-        if (depth >= endpoint.minDepth && isValidFile(gf, false, files)) {
-            LOG.trace("Adding valid file: {}", file);
-            // matched file so add
-            if (extendedAttributes != null) {
-                Path path = file.toPath();
-                Map<String, Object> allAttributes = new HashMap<>();
-                for (String attribute : extendedAttributes) {
-                    readAttributes(file, path, allAttributes, attribute);
+        if (depth >= endpoint.minDepth) {
+            boolean valid
+                    = isValidFile(gf, file.getName(), file.getAbsolutePath(),
+                            getRelativeFilePath(endpointPath, null, null, 
file),
+                            false, files);
+            if (valid) {
+                LOG.trace("Adding valid file: {}", file);
+                if (extendedAttributes != null) {
+                    Path path = file.toPath();
+                    Map<String, Object> allAttributes = new HashMap<>();
+                    for (String attribute : extendedAttributes) {
+                        readAttributes(file, path, allAttributes, attribute);
+                    }
+                    gf.get().setExtendedAttributes(allAttributes);
                 }
-
-                gf.setExtendedAttributes(allAttributes);
+                fileList.add(gf.get());
             }
-
-            fileList.add(gf);
         }
     }
 
     private boolean processDirectoryEntry(
-            List<GenericFile<File>> fileList, int depth, File file, 
GenericFile<File> gf, File[] files) {
-        if (endpoint.isRecursive() && depth < endpoint.getMaxDepth() && 
isValidFile(gf, true, files)) {
-            boolean canPollMore = pollDirectory(file, fileList, depth);
-            if (!canPollMore) {
-                return true;
+            List<GenericFile<File>> fileList, int depth, File file, 
Supplier<GenericFile<File>> gf, File[] files) {
+        if (endpoint.isRecursive() && depth < endpoint.getMaxDepth()) {
+            boolean valid
+                    = isValidFile(gf, file.getName(), file.getAbsolutePath(),
+                            getRelativeFilePath(endpointPath, null, null, 
file),
+                            true, files);
+            if (valid) {
+                boolean canPollMore = pollDirectory(file, fileList, depth);
+                return !canPollMore;
             }
         }
         return false;
@@ -250,7 +260,7 @@ public class FileConsumer extends GenericFileConsumer<File> 
implements ResumeAwa
     }
 
     @Override
-    protected boolean isMatched(GenericFile<File> file, String doneFileName, 
File[] files) {
+    protected boolean isMatched(Supplier<GenericFile<File>> file, String 
doneFileName, File[] files) {
         String onlyName = FileUtil.stripPath(doneFileName);
         // the done file name must be among the files
         for (File f : files) {
@@ -319,6 +329,27 @@ public class FileConsumer extends 
GenericFileConsumer<File> implements ResumeAwa
         return answer;
     }
 
+    @Override
+    protected Supplier<String> getRelativeFilePath(String endpointPath, String 
path, String absolutePath, File file) {
+        return () -> {
+            File f;
+            String endpointNormalizedSep = 
FileUtil.normalizePath(endpointPath) + File.separator;
+            String p = file.getPath();
+            if (p.startsWith(endpointNormalizedSep)) {
+                p = p.substring(endpointNormalizedSep.length());
+            }
+            f = new File(p);
+
+            String answer;
+            if (f.getParent() != null) {
+                answer = f.getParent() + File.separator + file.getName();
+            } else {
+                answer = f.getName();
+            }
+            return answer;
+        };
+    }
+
     @Override
     protected void updateFileHeaders(GenericFile<File> file, Message message) {
         File upToDateFile = file.getFile();
@@ -345,9 +376,8 @@ public class FileConsumer extends GenericFileConsumer<File> 
implements ResumeAwa
     }
 
     @Override
-    protected boolean isMatchedHiddenFile(GenericFile<File> file, boolean 
isDirectory) {
+    protected boolean isMatchedHiddenFile(Supplier<GenericFile<File>> file, 
String name, boolean isDirectory) {
         if (isDirectory) {
-            String name = file.getFileNameOnly();
             if (!name.startsWith(".")) {
                 return true;
             }
@@ -357,7 +387,7 @@ public class FileConsumer extends GenericFileConsumer<File> 
implements ResumeAwa
         if (getEndpoint().isIncludeHiddenFiles()) {
             return true;
         } else {
-            return super.isMatchedHiddenFile(file, isDirectory);
+            return super.isMatchedHiddenFile(file, name, isDirectory);
         }
     }
 
diff --git 
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileConsumer.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileConsumer.java
index 87d51223983..7f87cb2af1d 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileConsumer.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileConsumer.java
@@ -22,6 +22,7 @@ import java.util.Deque;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
+import java.util.function.Supplier;
 import java.util.regex.Pattern;
 
 import org.apache.camel.CamelContextAware;
@@ -580,15 +581,16 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
      * Strategy for validating if the given remote file should be included or 
not
      *
      * @param  file        the file
+     * @param  name        the file name
      * @param  isDirectory whether the file is a directory or a file
      * @param  files       files in the directory
      * @return             <tt>true</tt> to include the file, <tt>false</tt> 
to skip it
      */
-    protected boolean isValidFile(GenericFile<T> file, boolean isDirectory, 
T[] files) {
-        String absoluteFilePath = file.getAbsoluteFilePath();
-
-        if (!isMatched(file, isDirectory, files)) {
-            LOG.trace("File did not match. Will skip this file: {}", file);
+    protected boolean isValidFile(
+            Supplier<GenericFile<T>> file, String name, String 
absoluteFilePath,
+            Supplier<String> relativeFilePath, boolean isDirectory, T[] files) 
{
+        if (!isMatched(file, name, absoluteFilePath, relativeFilePath, 
isDirectory, files)) {
+            LOG.trace("File did not match. Will skip this file: {}", name);
             return false;
         }
 
@@ -600,7 +602,7 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
         // check if file is already in progress
         if (endpoint.getInProgressRepository().contains(absoluteFilePath)) {
             if (LOG.isTraceEnabled()) {
-                LOG.trace("Skipping as file is already in progress: {}", 
file.getFileName());
+                LOG.trace("Skipping as file is already in progress: {}", name);
             }
             return false;
         }
@@ -608,7 +610,7 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
         // if it is a file then check we have the file in the idempotent 
registry
         // already
         if (Boolean.TRUE.equals(endpoint.isIdempotent())) {
-            if (notUnique(file)) {
+            if (notUnique(file, absoluteFilePath)) {
                 return false;
             }
         }
@@ -619,13 +621,13 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
         return endpoint.getInProgressRepository().add(absoluteFilePath);
     }
 
-    private boolean notUnique(GenericFile<T> file) {
+    private boolean notUnique(Supplier<GenericFile<T>> file, String 
absoluteFilePath) {
         boolean answer = false;
         // use absolute file path as default key, but evaluate if an
         // expression key was configured
-        String key = file.getAbsoluteFilePath();
+        String key = absoluteFilePath;
         if (endpoint.getIdempotentKey() != null) {
-            Exchange dummy = endpoint.createExchange(file);
+            Exchange dummy = endpoint.createExchange(file.get());
             key = endpoint.getIdempotentKey().evaluate(dummy, String.class);
             LOG.trace("Evaluated idempotentKey: {} for file: {}", key, file);
         }
@@ -649,9 +651,7 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
      * <li>Starting with a dot (hidden)</li>
      * </ul>
      */
-    protected boolean isMatchedHiddenFile(GenericFile<T> file, boolean 
isDirectory) {
-        String name = file.getFileNameOnly();
-
+    protected boolean isMatchedHiddenFile(Supplier<GenericFile<T>> file, 
String name, boolean isDirectory) {
         // folders/names starting with dot is always skipped (eg. ".", 
".camel",
         // ".camelLock")
         if (name.startsWith(".")) {
@@ -661,6 +661,12 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
         return true;
     }
 
+    /**
+     * Geta the relative path from the given file, calculated from the 
starting path, current path, and current absolute
+     * path
+     */
+    protected abstract Supplier<String> getRelativeFilePath(String 
endpointPath, String path, String absolutePath, T file);
+
     /**
      * Strategy to perform file matching based on endpoint configuration.
      * <p/>
@@ -671,15 +677,19 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
      * </ul>
      * And then <tt>true</tt> for directories.
      *
-     * @param  file        the file
-     * @param  isDirectory whether the file is a directory or a file
-     * @param  files       files in the directory
-     * @return             <tt>true</tt> if the file is matched, 
<tt>false</tt> if not
+     * @param  file             the file
+     * @param  name             the file name
+     * @param  absoluteFilePath the absolute file name
+     * @param  relativeFilePath the relative file name
+     * @param  isDirectory      whether the file is a directory or a file
+     * @param  files            files in the directory
+     * @return                  <tt>true</tt> if the file is matched, 
<tt>false</tt> if not
      */
-    protected boolean isMatched(GenericFile<T> file, boolean isDirectory, T[] 
files) {
-        String name = file.getFileNameOnly();
+    protected boolean isMatched(
+            Supplier<GenericFile<T>> file, String name, String 
absoluteFilePath,
+            Supplier<String> relativeFilePath, boolean isDirectory, T[] files) 
{
 
-        if (!isMatchedHiddenFile(file, isDirectory)) {
+        if (!isMatchedHiddenFile(file, name, isDirectory)) {
             // folders/names starting with dot is always skipped (eg. ".", 
".camel",
             // ".camelLock")
             return false;
@@ -691,13 +701,22 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
         }
 
         if (endpoint.getFilter() != null) {
-            if (!endpoint.getFilter().accept(file)) {
+            Boolean accepted = null;
+            if (endpoint.getFilter() instanceof OptimizedFileFilter off) {
+                // use optimized test using file name only
+                accepted = off.accept(name);
+            }
+            if (accepted == null) {
+                // use default test using generic file
+                accepted = endpoint.getFilter().accept(file.get());
+            }
+            if (!accepted) {
                 return false;
             }
         }
 
         if (endpoint.getAntFilter() != null) {
-            if (!endpoint.getAntFilter().accept(file)) {
+            if (!endpoint.getAntFilter().accept(isDirectory, 
relativeFilePath.get())) {
                 return false;
             }
         }
@@ -705,7 +724,7 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
         if (isDirectory && endpoint.getFilterDirectory() != null) {
             // create a dummy exchange as Exchange is needed for expression
             // evaluation
-            Exchange dummy = endpoint.createExchange(file);
+            Exchange dummy = endpoint.createExchange(file.get());
             boolean matches = endpoint.getFilterDirectory().matches(dummy);
             if (!matches) {
                 return false;
@@ -717,13 +736,13 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
             return true;
         }
 
-        if (hasInclusionsOrExclusions(file, name)) {
+        if (hasInclusionsOrExclusions(name)) {
             return false;
         }
 
         if (endpoint.getFileName() != null) {
             // create a dummy exchange as Exchange is needed for expression 
evaluation
-            Exchange dummy = endpoint.createExchange(file);
+            Exchange dummy = endpoint.createExchange(file.get());
             String result = evaluateFileExpression(dummy);
             if (result != null) {
                 if (!name.equals(result)) {
@@ -734,7 +753,7 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
 
         if (endpoint.getFilterFile() != null) {
             // create a dummy exchange as Exchange is needed for expression 
evaluation
-            Exchange dummy = endpoint.createExchange(file);
+            Exchange dummy = endpoint.createExchange(file.get());
             boolean matches = endpoint.getFilterFile().matches(dummy);
             if (!matches) {
                 return false;
@@ -745,11 +764,11 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
         // file exists
         if (endpoint.getDoneFileName() != null) {
             // done file must be in same path as the file
-            String doneFileName = 
endpoint.createDoneFileName(file.getAbsoluteFilePath());
+            String doneFileName = 
endpoint.createDoneFileName(absoluteFilePath);
             StringHelper.notEmpty(doneFileName, "doneFileName", endpoint);
 
             // is it a done file name?
-            if (endpoint.isDoneFile(file.getFileNameOnly())) {
+            if (endpoint.isDoneFile(name)) {
                 LOG.trace("Skipping done file: {}", file);
                 return false;
             }
@@ -762,15 +781,16 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
         return true;
     }
 
-    private boolean hasInclusionsOrExclusions(GenericFile<T> file, String 
name) {
+    private boolean hasInclusionsOrExclusions(String name) {
         // exclude take precedence over include
         if (excludePattern != null) {
             if (excludePattern.matcher(name).matches()) {
                 return true;
             }
         }
+        String fname = null;
         if (excludeExt != null) {
-            String fname = file.getFileName().toLowerCase();
+            fname = name.toLowerCase();
             if (hasExtExlusions(fname)) {
                 return true;
             }
@@ -781,7 +801,9 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
             }
         }
         if (includeExt != null) {
-            String fname = file.getFileName().toLowerCase();
+            if (fname == null) {
+                fname = name.toLowerCase();
+            }
             if (hasExtInclusions(fname)) {
                 return true;
             }
@@ -789,7 +811,7 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
         return false;
     }
 
-    private boolean hasExtInclusions(String fname) {
+    protected boolean hasExtInclusions(String fname) {
         boolean any = false;
         for (String include : includeExt) {
             any |= fname.endsWith("." + include);
@@ -800,7 +822,7 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
         return false;
     }
 
-    private boolean hasExtExlusions(String fname) {
+    protected boolean hasExtExlusions(String fname) {
         for (String exclude : excludeExt) {
             if (fname.endsWith("." + exclude)) {
                 return true;
@@ -817,7 +839,7 @@ public abstract class GenericFileConsumer<T> extends 
ScheduledBatchPollingConsum
      * @param  files        files in the directory
      * @return              <tt>true</tt> if the file is matched, 
<tt>false</tt> if not
      */
-    protected abstract boolean isMatched(GenericFile<T> file, String 
doneFileName, T[] files);
+    protected abstract boolean isMatched(Supplier<GenericFile<T>> file, String 
doneFileName, T[] files);
 
     protected String evaluateFileExpression(Exchange exchange) {
         String result = endpoint.getFileName().evaluate(exchange, 
String.class);
diff --git 
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileEndpoint.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileEndpoint.java
index 392f44d9a82..b32bfad5f1f 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileEndpoint.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileEndpoint.java
@@ -308,7 +308,7 @@ public abstract class GenericFileEndpoint<T> extends 
ScheduledPollEndpoint imple
     protected Predicate filterFile;
     @UriParam(label = "consumer,filter", defaultValue = "true", description = 
"Sets case sensitive flag on ant filter.")
     protected boolean antFilterCaseSensitive = true;
-    protected volatile AntPathMatcherGenericFileFilter<T> antFilter;
+    protected volatile AntFilter antFilter;
     @UriParam(label = "consumer,filter",
               description = "Ant style filter inclusion. Multiple inclusions 
may be " + "specified in comma-delimited format.")
     protected String antInclude;
@@ -685,7 +685,7 @@ public abstract class GenericFileEndpoint<T> extends 
ScheduledPollEndpoint imple
         this.antFilterCaseSensitive = antFilterCaseSensitive;
     }
 
-    public GenericFileFilter<T> getAntFilter() {
+    public AntFilter getAntFilter() {
         return antFilter;
     }
 
@@ -1878,13 +1878,13 @@ public abstract class GenericFileEndpoint<T> extends 
ScheduledPollEndpoint imple
 
         if (antInclude != null) {
             if (antFilter == null) {
-                antFilter = new AntPathMatcherGenericFileFilter<>();
+                antFilter = new AntFilter();
             }
             antFilter.setIncludes(antInclude);
         }
         if (antExclude != null) {
             if (antFilter == null) {
-                antFilter = new AntPathMatcherGenericFileFilter<>();
+                antFilter = new AntFilter();
             }
             antFilter.setExcludes(antExclude);
         }
diff --git 
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileFilter.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileFilter.java
index 5178dc96d62..6e1f5acf9fb 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileFilter.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileFilter.java
@@ -18,6 +18,8 @@ package org.apache.camel.component.file;
 
 /**
  * A filter for {@link GenericFile}.
+ *
+ * @see OptimizedFileFilter
  */
 public interface GenericFileFilter<T> {
 
diff --git 
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileFilter.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/OptimizedFileFilter.java
similarity index 64%
copy from 
components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileFilter.java
copy to 
components/camel-file/src/main/java/org/apache/camel/component/file/OptimizedFileFilter.java
index 5178dc96d62..3f57c4f3b75 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileFilter.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/OptimizedFileFilter.java
@@ -17,16 +17,24 @@
 package org.apache.camel.component.file;
 
 /**
- * A filter for {@link GenericFile}.
+ * A filter for filtering file that is optimized for matching by name only.
+ *
+ * @see GenericFileFilter
  */
-public interface GenericFileFilter<T> {
+public interface OptimizedFileFilter extends GenericFileFilter {
+
+    @Override
+    default boolean accept(GenericFile file) {
+        return false;
+    }
 
     /**
-     * Tests whether the specified generic file should be included
+     * Tests whether the specified file should be included (quick test using 
only file name)
      *
-     * @param  file the generic file to be tested
-     * @return      <code>true</code> if and only if <code>file</code> should 
be included
+     * @param  name the file name
+     * @return      <code>true</code> if and only if <code>file</code> should 
be included, <tt>null</tt> to use the
+     *              {@link #accept(GenericFile)} method.
      */
-    boolean accept(GenericFile<T> file);
+    Boolean accept(String name);
 
 }
diff --git 
a/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/MarkerFileExclusiveReadLockStrategy.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/MarkerFileExclusiveReadLockStrategy.java
index da924dc71ce..123871090d0 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/MarkerFileExclusiveReadLockStrategy.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/strategy/MarkerFileExclusiveReadLockStrategy.java
@@ -21,6 +21,7 @@ import java.util.regex.Pattern;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.LoggingLevel;
+import org.apache.camel.component.file.AntFilter;
 import org.apache.camel.component.file.FileComponent;
 import org.apache.camel.component.file.GenericFile;
 import org.apache.camel.component.file.GenericFileEndpoint;
@@ -174,7 +175,7 @@ public class MarkerFileExclusiveReadLockStrategy implements 
GenericFileExclusive
     private static <T> void deleteLockFiles(
             File dir, boolean recursive, int minDepth, int maxDepth, int 
depth, boolean hiddenFilesEnabled, String endpointPath,
             GenericFileFilter<T> filter,
-            GenericFileFilter<T> antFilter,
+            AntFilter antFilter,
             Pattern excludePattern,
             Pattern includePattern) {
 
@@ -232,7 +233,7 @@ public class MarkerFileExclusiveReadLockStrategy implements 
GenericFileExclusive
 
     @SuppressWarnings("unchecked")
     private static <T> boolean acceptFile(
-            File file, String endpointPath, GenericFileFilter<T> filter, 
GenericFileFilter<T> antFilter, Pattern excludePattern,
+            File file, String endpointPath, GenericFileFilter<T> filter, 
AntFilter antFilter, Pattern excludePattern,
             Pattern includePattern) {
         GenericFile gf = new GenericFile<>();
         gf.setEndpointPath(endpointPath);
@@ -286,7 +287,7 @@ public class MarkerFileExclusiveReadLockStrategy implements 
GenericFileExclusive
         }
 
         if (antFilter != null) {
-            if (!antFilter.accept(gf)) {
+            if (!antFilter.accept(gf.isDirectory(), gf.getRelativeFilePath())) 
{
                 return false;
             }
         }
diff --git 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpConsumer.java
 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpConsumer.java
index 127aa045759..d7f931c019d 100644
--- 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpConsumer.java
+++ 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpConsumer.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.file.remote;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
+import java.util.function.Supplier;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
@@ -34,6 +35,7 @@ import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.TimeUtils;
 import org.apache.camel.util.URISupport;
+import org.apache.camel.util.function.Suppliers;
 import org.apache.commons.net.ftp.FTPClient;
 import org.apache.commons.net.ftp.FTPFile;
 import org.slf4j.Logger;
@@ -181,14 +183,21 @@ public class FtpConsumer extends 
RemoteFileConsumer<FTPFile> {
 
     private boolean handleDirectory(
             String absolutePath, List<GenericFile<FTPFile>> fileList, int 
depth, FTPFile[] files, FTPFile file) {
-        RemoteFile<FTPFile> remote = asRemoteFile(absolutePath, file, 
getEndpoint().getCharset());
-        if (endpoint.isRecursive() && depth < endpoint.getMaxDepth() && 
isValidFile(remote, true, files)) {
-            // recursive scan and add the sub files and folders
-            String subDirectory = file.getName();
-            String path = ObjectHelper.isNotEmpty(absolutePath) ? absolutePath 
+ "/" + subDirectory : subDirectory;
-            boolean canPollMore = pollSubDirectory(path, subDirectory, 
fileList, depth);
-            if (!canPollMore) {
-                return true;
+        if (endpoint.isRecursive() && depth < endpoint.getMaxDepth()) {
+            // calculate the absolute file path using util class
+            String absoluteFilePath
+                    = FtpUtils.absoluteFilePath((FtpConfiguration) 
endpoint.getConfiguration(), absolutePath, file.getName());
+            Supplier<GenericFile<FTPFile>> remote
+                    = Suppliers.memorize(() -> asRemoteFile(absolutePath, 
absoluteFilePath, file, getEndpoint().getCharset()));
+            Supplier<String> relativePath = getRelativeFilePath(endpointPath, 
null, absolutePath, file);
+            if (isValidFile(remote, file.getName(), absoluteFilePath, 
relativePath, true, files)) {
+                // recursive scan and add the sub files and folders
+                String subDirectory = file.getName();
+                String path = ObjectHelper.isNotEmpty(absolutePath) ? 
absolutePath + "/" + subDirectory : subDirectory;
+                boolean canPollMore = pollSubDirectory(path, subDirectory, 
fileList, depth);
+                if (!canPollMore) {
+                    return true;
+                }
             }
         }
         return false;
@@ -196,10 +205,17 @@ public class FtpConsumer extends 
RemoteFileConsumer<FTPFile> {
 
     private void handleFile(
             String absolutePath, List<GenericFile<FTPFile>> fileList, int 
depth, FTPFile[] files, FTPFile file) {
-        RemoteFile<FTPFile> remote = asRemoteFile(absolutePath, file, 
getEndpoint().getCharset());
-        if (depth >= endpoint.getMinDepth() && isValidFile(remote, false, 
files)) {
-            // matched file so add
-            fileList.add(remote);
+        if (depth >= endpoint.getMinDepth()) {
+            // calculate the absolute file path using util class
+            String absoluteFilePath
+                    = FtpUtils.absoluteFilePath((FtpConfiguration) 
endpoint.getConfiguration(), absolutePath, file.getName());
+            Supplier<GenericFile<FTPFile>> remote
+                    = Suppliers.memorize(() -> asRemoteFile(absolutePath, 
absoluteFilePath, file, getEndpoint().getCharset()));
+            Supplier<String> relativePath = getRelativeFilePath(endpointPath, 
null, absolutePath, file);
+            if (isValidFile(remote, file.getName(), absoluteFilePath, 
relativePath, false, files)) {
+                // matched file so add
+                fileList.add(remote.get());
+            }
         }
     }
 
@@ -258,7 +274,7 @@ public class FtpConsumer extends 
RemoteFileConsumer<FTPFile> {
     }
 
     @Override
-    protected boolean isMatched(GenericFile<FTPFile> file, String 
doneFileName, FTPFile[] files) {
+    protected boolean isMatched(Supplier<GenericFile<FTPFile>> file, String 
doneFileName, FTPFile[] files) {
         String onlyName = FileUtil.stripPath(doneFileName);
 
         for (FTPFile f : files) {
@@ -294,7 +310,17 @@ public class FtpConsumer extends 
RemoteFileConsumer<FTPFile> {
         return super.ignoreCannotRetrieveFile(name, exchange, cause);
     }
 
-    private RemoteFile<FTPFile> asRemoteFile(String absolutePath, FTPFile 
file, String charset) {
+    @Override
+    protected Supplier<String> getRelativeFilePath(String endpointPath, String 
path, String absolutePath, FTPFile file) {
+        return () -> {
+            // the relative filename, skip the leading endpoint configured path
+            String relativePath = StringHelper.after(absolutePath, 
endpointPath);
+            // skip leading /
+            return FileUtil.stripLeadingSeparator(relativePath);
+        };
+    }
+
+    private RemoteFile<FTPFile> asRemoteFile(String absolutePath, String 
absoluteFilePath, FTPFile file, String charset) {
         RemoteFile<FTPFile> answer = new RemoteFile<>();
 
         answer.setCharset(charset);
@@ -311,23 +337,10 @@ public class FtpConsumer extends 
RemoteFileConsumer<FTPFile> {
         // absolute or relative path
         boolean absolute = FileUtil.hasLeadingSeparator(absolutePath);
         answer.setAbsolute(absolute);
-
-        // create a pseudo absolute name
-        String dir = FileUtil.stripTrailingSeparator(absolutePath);
-        String fileName = file.getName();
-        if (((FtpConfiguration) 
endpoint.getConfiguration()).isHandleDirectoryParserAbsoluteResult()) {
-            fileName = FtpUtils.extractDirNameFromAbsolutePath(file.getName());
-        }
-        String absoluteFileName = FileUtil.stripLeadingSeparator(dir + "/" + 
fileName);
-        // if absolute start with a leading separator otherwise let it be
-        // relative
-        if (absolute) {
-            absoluteFileName = "/" + absoluteFileName;
-        }
-        answer.setAbsoluteFilePath(absoluteFileName);
+        answer.setAbsoluteFilePath(absoluteFilePath);
 
         // the relative filename, skip the leading endpoint configured path
-        String relativePath = StringHelper.after(absoluteFileName, 
endpointPath);
+        String relativePath = StringHelper.after(absoluteFilePath, 
endpointPath);
         // skip leading /
         relativePath = FileUtil.stripLeadingSeparator(relativePath);
         answer.setRelativeFilePath(relativePath);
diff --git 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpUtils.java
 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpUtils.java
index ec84ed12952..f39f1d36b16 100644
--- 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpUtils.java
+++ 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpUtils.java
@@ -126,9 +126,7 @@ public final class FtpUtils {
      * Checks whether directory used in ftp/ftps/sftp endpoint URI is 
relative. Absolute path will be converted to
      * relative path and a WARN will be printed.
      *
-     * @see                 <a 
href="http://camel.apache.org/ftp2.html";>FTP/SFTP/FTPS Component</a>
-     * @param ftpComponent
-     * @param configuration
+     * @see <a href="http://camel.apache.org/ftp2.html";>FTP/SFTP/FTPS 
Component</a>
      */
     public static void ensureRelativeFtpDirectory(Component ftpComponent, 
RemoteFileConfiguration configuration) {
         if (FileUtil.hasLeadingSeparator(configuration.getDirectoryName())) {
@@ -141,4 +139,35 @@ public final class FtpUtils {
         }
     }
 
+    public static String absoluteFilePath(FtpConfiguration configuration, 
String absolutePath, String name) {
+        boolean absolute = FileUtil.hasLeadingSeparator(absolutePath);
+        // create a pseudo absolute name
+        String dir = FileUtil.stripTrailingSeparator(absolutePath);
+        String fileName = name;
+        if (configuration.isHandleDirectoryParserAbsoluteResult()) {
+            fileName = FtpUtils.extractDirNameFromAbsolutePath(name);
+        }
+        String absoluteFileName = FileUtil.stripLeadingSeparator(dir + "/" + 
fileName);
+        // if absolute start with a leading separator otherwise let it be
+        // relative
+        if (absolute) {
+            absoluteFileName = "/" + absoluteFileName;
+        }
+        return absoluteFileName;
+    }
+
+    public static String absoluteFilePath(String absolutePath, String name) {
+        boolean absolute = FileUtil.hasLeadingSeparator(absolutePath);
+
+        // create a pseudo absolute name
+        String dir = FileUtil.stripTrailingSeparator(absolutePath);
+        String absoluteFileName = FileUtil.stripLeadingSeparator(dir + "/" + 
name);
+        // if absolute start with a leading separator otherwise let it be
+        // relative
+        if (absolute) {
+            absoluteFileName = "/" + absoluteFileName;
+        }
+        return absoluteFileName;
+    }
+
 }
diff --git 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/RemoteFile.java
 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/RemoteFile.java
index e2c539128ba..c7787bb4f4f 100644
--- 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/RemoteFile.java
+++ 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/RemoteFile.java
@@ -64,7 +64,7 @@ public class RemoteFile<T> extends GenericFile<T> implements 
Cloneable {
 
     @Override
     protected boolean isAbsolute(String name) {
-        if (name.length() > 0) {
+        if (!name.isEmpty()) {
             return name.charAt(0) == '/' || name.charAt(0) == '\\';
         }
         return false;
@@ -79,7 +79,6 @@ public class RemoteFile<T> extends GenericFile<T> implements 
Cloneable {
     public void copyFromPopulateAdditional(GenericFile<T> source, 
GenericFile<T> result) {
         RemoteFile<?> remoteSource = (RemoteFile<?>) source;
         RemoteFile<?> remoteResult = (RemoteFile<?>) result;
-
         remoteResult.setHostname(remoteSource.getHostname());
     }
 
diff --git 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpConsumer.java
 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpConsumer.java
index 56c10fec3c8..d231d6a3fd8 100644
--- 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpConsumer.java
+++ 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpConsumer.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.file.remote;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
+import java.util.function.Supplier;
 
 import com.jcraft.jsch.ChannelSftp;
 import com.jcraft.jsch.SftpException;
@@ -33,6 +34,7 @@ import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.URISupport;
+import org.apache.camel.util.function.Suppliers;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -124,7 +126,7 @@ public class SftpConsumer extends 
RemoteFileConsumer<SftpRemoteFile> {
         dirName = FileUtil.stripTrailingSeparator(dirName);
 
         // compute dir depending on stepwise is enabled or not
-        String dir = null;
+        String dir;
         if (isStepwise()) {
             dir = ObjectHelper.isNotEmpty(dirName) ? dirName : absolutePath;
             operations.changeCurrentDirectory(dir);
@@ -160,24 +162,36 @@ public class SftpConsumer extends 
RemoteFileConsumer<SftpRemoteFile> {
             }
 
             if (file.isDirectory()) {
-                RemoteFile<SftpRemoteFile> remote = asRemoteFile(absolutePath, 
file, getEndpoint().getCharset());
-                if (endpoint.isRecursive() && depth < endpoint.getMaxDepth() 
&& isValidFile(remote, true, files)) {
-                    // recursive scan and add the sub files and folders
-                    String subDirectory = file.getFilename();
-                    String path = ObjectHelper.isNotEmpty(absolutePath) ? 
absolutePath + "/" + subDirectory : subDirectory;
-                    boolean canPollMore = pollSubDirectory(path, subDirectory, 
fileList, depth);
-                    if (!canPollMore) {
-                        return false;
+                if (endpoint.isRecursive() && depth < endpoint.getMaxDepth()) {
+                    String absoluteFilePath = 
FtpUtils.absoluteFilePath(absolutePath, file.getFilename());
+                    Supplier<GenericFile<SftpRemoteFile>> remote
+                            = Suppliers.memorize(
+                                    () -> asRemoteFile(absolutePath, 
absoluteFilePath, file, getEndpoint().getCharset()));
+                    Supplier<String> relativePath = 
getRelativeFilePath(endpointPath, null, absolutePath, file);
+                    if (isValidFile(remote, file.getFilename(), 
absoluteFilePath, relativePath, true, files)) {
+                        // recursive scan and add the sub files and folders
+                        String subDirectory = file.getFilename();
+                        String path = ObjectHelper.isNotEmpty(absolutePath) ? 
absolutePath + "/" + subDirectory : subDirectory;
+                        boolean canPollMore = pollSubDirectory(path, 
subDirectory, fileList, depth);
+                        if (!canPollMore) {
+                            return false;
+                        }
                     }
                 }
                 // we cannot use file.getAttrs().isLink on Windows, so we dont
                 // invoke the method
                 // just assuming its a file we should poll
             } else {
-                RemoteFile<SftpRemoteFile> remote = asRemoteFile(absolutePath, 
file, getEndpoint().getCharset());
-                if (depth >= endpoint.getMinDepth() && isValidFile(remote, 
false, files)) {
-                    // matched file so add
-                    fileList.add(remote);
+                if (depth >= endpoint.getMinDepth()) {
+                    String absoluteFilePath = 
FtpUtils.absoluteFilePath(absolutePath, file.getFilename());
+                    Supplier<GenericFile<SftpRemoteFile>> remote
+                            = Suppliers.memorize(
+                                    () -> asRemoteFile(absolutePath, 
absoluteFilePath, file, getEndpoint().getCharset()));
+                    Supplier<String> relativePath = 
getRelativeFilePath(endpointPath, null, absolutePath, file);
+                    if (isValidFile(remote, file.getFilename(), 
absoluteFilePath, relativePath, false, files)) {
+                        // matched file so add
+                        fileList.add(remote.get());
+                    }
                 }
             }
         }
@@ -229,7 +243,7 @@ public class SftpConsumer extends 
RemoteFileConsumer<SftpRemoteFile> {
     }
 
     @Override
-    protected boolean isMatched(GenericFile<SftpRemoteFile> file, String 
doneFileName, SftpRemoteFile[] files) {
+    protected boolean isMatched(Supplier<GenericFile<SftpRemoteFile>> file, 
String doneFileName, SftpRemoteFile[] files) {
         String onlyName = FileUtil.stripPath(doneFileName);
 
         for (SftpRemoteFile f : files) {
@@ -253,7 +267,17 @@ public class SftpConsumer extends 
RemoteFileConsumer<SftpRemoteFile> {
         return super.ignoreCannotRetrieveFile(name, exchange, cause);
     }
 
-    private RemoteFile<SftpRemoteFile> asRemoteFile(String absolutePath, 
SftpRemoteFile file, String charset) {
+    @Override
+    protected Supplier<String> getRelativeFilePath(String endpointPath, String 
path, String absolutePath, SftpRemoteFile file) {
+        return () -> {
+            String relativePath = StringHelper.after(absolutePath, 
endpointPath);
+            // skip trailing /
+            return FileUtil.stripLeadingSeparator(relativePath);
+        };
+    }
+
+    private RemoteFile<SftpRemoteFile> asRemoteFile(
+            String absolutePath, String absoluteFilePath, SftpRemoteFile file, 
String charset) {
         RemoteFile<SftpRemoteFile> answer = new RemoteFile<>();
 
         answer.setCharset(charset);
@@ -268,19 +292,10 @@ public class SftpConsumer extends 
RemoteFileConsumer<SftpRemoteFile> {
         // absolute or relative path
         boolean absolute = FileUtil.hasLeadingSeparator(absolutePath);
         answer.setAbsolute(absolute);
-
-        // create a pseudo absolute name
-        String dir = FileUtil.stripTrailingSeparator(absolutePath);
-        String absoluteFileName = FileUtil.stripLeadingSeparator(dir + "/" + 
file.getFilename());
-        // if absolute start with a leading separator otherwise let it be
-        // relative
-        if (absolute) {
-            absoluteFileName = "/" + absoluteFileName;
-        }
-        answer.setAbsoluteFilePath(absoluteFileName);
+        answer.setAbsoluteFilePath(absoluteFilePath);
 
         // the relative filename, skip the leading endpoint configured path
-        String relativePath = StringHelper.after(absoluteFileName, 
endpointPath);
+        String relativePath = StringHelper.after(absoluteFilePath, 
endpointPath);
         // skip trailing /
         relativePath = FileUtil.stripLeadingSeparator(relativePath);
         answer.setRelativeFilePath(relativePath);
diff --git 
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/RemoteFileIgnoreDoPollErrorTest.java
 
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/RemoteFileIgnoreDoPollErrorTest.java
index 13afb717835..780d3d5f1a6 100644
--- 
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/RemoteFileIgnoreDoPollErrorTest.java
+++ 
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/RemoteFileIgnoreDoPollErrorTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.file.remote;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Supplier;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.Exchange;
@@ -128,7 +129,7 @@ public class RemoteFileIgnoreDoPollErrorTest {
             }
 
             @Override
-            protected boolean isMatched(GenericFile<Object> file, String 
doneFileName, Object[] files) {
+            protected boolean isMatched(Supplier<GenericFile<Object>> file, 
String doneFileName, Object[] files) {
                 return false;
             }
 
@@ -141,6 +142,11 @@ public class RemoteFileIgnoreDoPollErrorTest {
             protected void updateFileHeaders(GenericFile<Object> genericFile, 
Message message) {
                 // noop
             }
+
+            @Override
+            protected Supplier<String> getRelativeFilePath(String 
endpointPath, String path, String absolutePath, Object file) {
+                return null;
+            }
         };
     }
 }
diff --git 
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/sftp/integration/SftpMoveWithOutMessageTest.java
 
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/sftp/integration/SftpMoveWithOutMessageTest.java
index 1f01c1d386a..a529cc97cd8 100644
--- 
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/sftp/integration/SftpMoveWithOutMessageTest.java
+++ 
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/sftp/integration/SftpMoveWithOutMessageTest.java
@@ -24,6 +24,7 @@ import org.apache.camel.Processor;
 import org.apache.camel.ProducerTemplate;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.support.DefaultMessage;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
 import org.junit.jupiter.api.condition.EnabledIf;
@@ -36,6 +37,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
  * Test that the existence of a outMessage in an exchange will not break the 
move-file post-processing
  */
 @EnabledIf(value = 
"org.apache.camel.test.infra.ftp.services.embedded.SftpUtil#hasRequiredAlgorithms('src/test/resources/hostkey.pem')")
+@Disabled
 public class SftpMoveWithOutMessageTest extends SftpServerTestSupport {
 
     @Timeout(value = 30)
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/file/FileConsumerFileFilterOptimizedTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/file/FileConsumerFileFilterOptimizedTest.java
new file mode 100644
index 00000000000..5a879799e05
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/file/FileConsumerFileFilterOptimizedTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.camel.component.file;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.spi.Registry;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit test for the file filter option
+ */
+public class FileConsumerFileFilterOptimizedTest extends ContextTestSupport {
+
+    @Override
+    protected Registry createCamelRegistry() throws Exception {
+        Registry jndi = super.createCamelRegistry();
+        jndi.bind("myFilter", new MyFileFilter());
+        return jndi;
+    }
+
+    @Test
+    public void testFilterFiles() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedMessageCount(0);
+
+        template.sendBodyAndHeader(fileUri(), "This is a file to be filtered",
+                Exchange.FILE_NAME,
+                "skipme.txt");
+
+        mock.setResultWaitTime(100);
+        mock.assertIsSatisfied();
+    }
+
+    @Test
+    public void testFilterFilesWithARegularFile() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedBodiesReceived("Hello World");
+
+        template.sendBodyAndHeader(fileUri(), "This is a file to be filtered",
+                Exchange.FILE_NAME,
+                "skipme.txt");
+
+        template.sendBodyAndHeader(fileUri(), "Hello World", 
Exchange.FILE_NAME,
+                "hello.txt");
+
+        mock.assertIsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+                from(fileUri("?initialDelay=0&delay=10&filter=#myFilter"))
+                        .convertBodyTo(String.class).to("mock:result");
+            }
+        };
+    }
+
+    public static class MyFileFilter implements OptimizedFileFilter {
+
+        @Override
+        public Boolean accept(String name) {
+            // we dont accept any files starting with skip in the name
+            return !name.startsWith("skip");
+        }
+    }
+
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_10.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_10.adoc
index c69e7440a40..58d4454ee7b 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_10.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_10.adoc
@@ -6,6 +6,17 @@ from both 4.0 to 4.1 and 4.1 to 4.2.
 
 == Upgrading Camel 4.9 to 4.10
 
+=== camel-file
+
+The `camel-file` consumer has been optimized when filtering file names using 
name matching only,
+to avoid creating an `GenericFile` object that represent the file. This is 
unnessasary if the file
+is to be excluded due the filtering.
+
+This optimization has changed APIs in the `camel-file` component to let 
methods that accept
+`GenericFile` as parameter, has been changed to use a `Supplier<GenericFile>` 
to lazy create the wrapper.
+
+Camel users who have created 3rd party component extending `camel-file` may 
need to migrate your components.
+
 === camel-jgroups
 
 The cluster lock has been removed as it has been removed in JGroups 5.4 
onwards, and it was

Reply via email to