[ 
https://issues.apache.org/jira/browse/CAMEL-5558?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16635068#comment-16635068
 ] 

ASF GitHub Bot commented on CAMEL-5558:
---------------------------------------

oscerd closed pull request #2541: CAMEL-5558 - add file existing move strategy 
interface to let custom
URL: https://github.com/apache/camel/pull/2541
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/camel-core/src/main/docs/file-component.adoc 
b/camel-core/src/main/docs/file-component.adoc
index 19eea919656..4efaac5f47d 100644
--- a/camel-core/src/main/docs/file-component.adoc
+++ b/camel-core/src/main/docs/file-component.adoc
@@ -72,7 +72,7 @@ with the following path and query parameters:
 |===
 
 
-==== Query Parameters (85 parameters):
+==== Query Parameters (86 parameters):
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
@@ -111,6 +111,7 @@ with the following path and query parameters:
 | *eagerDeleteTargetFile* (producer) | Whether or not to eagerly delete any 
existing target file. This option only applies when you use fileExists=Override 
and the tempFileName option as well. You can use this to disable (set it to 
false) deleting the target file before the temp file is written. For example 
you may write big files and want the target file to exists during the temp file 
is being written. This ensure the target file is only deleted until the very 
last moment, just before the temp file is being renamed to the target filename. 
This option is also used to control whether to delete any existing files when 
fileExist=Move is enabled, and an existing file exists. If this option 
copyAndDeleteOnRenameFails false, then an exception will be thrown if an 
existing file existed, if its true, then the existing file is deleted before 
the move operation. | true | boolean
 | *forceWrites* (producer) | Whether to force syncing writes to the file 
system. You can turn this off if you do not want this level of guarantee, for 
example if writing to logs / audit logs etc; this would yield better 
performance. | true | boolean
 | *keepLastModified* (producer) | Will keep the last modified timestamp from 
the source file (if any). Will use the Exchange.FILE_LAST_MODIFIED header to 
located the timestamp. This header can contain either a java.util.Date or long 
with the timestamp. If the timestamp exists and the option is enabled it will 
set this timestamp on the written file. Note: This option only applies to the 
file producer. You cannot use this option with any of the ftp producers. | 
false | boolean
+| *moveExistingFileStrategy* (producer) | Strategy (Custom Strategy) used to 
move file with special naming token to use when fileExist=Move is configured. 
By default, there is an implementation used if no custom strategy is provided | 
 | FileMoveExisting Strategy
 | *autoCreate* (advanced) | Automatically create missing directories in the 
file's pathname. For the file consumer, that means creating the starting 
directory. For the file producer, it means the directory the files should be 
written to. | true | boolean
 | *bufferSize* (advanced) | Write buffer sized in bytes. | 131072 | int
 | *copyAndDeleteOnRenameFail* (advanced) | Whether to fallback and do a copy 
and delete file, in case the file could not be renamed directly. This option is 
not available for the FTP component. | true | boolean
diff --git 
a/camel-core/src/main/java/org/apache/camel/component/file/FileEndpoint.java 
b/camel-core/src/main/java/org/apache/camel/component/file/FileEndpoint.java
index 2481b88c905..b866cba8ad3 100644
--- a/camel-core/src/main/java/org/apache/camel/component/file/FileEndpoint.java
+++ b/camel-core/src/main/java/org/apache/camel/component/file/FileEndpoint.java
@@ -29,6 +29,7 @@
 import org.apache.camel.Message;
 import org.apache.camel.PollingConsumer;
 import org.apache.camel.Processor;
+import org.apache.camel.component.file.strategy.FileMoveExistingStrategy;
 import org.apache.camel.processor.idempotent.MemoryIdempotentRepository;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.UriEndpoint;
@@ -158,7 +159,9 @@ public PollingConsumer createPollingConsumer() throws 
Exception {
         } else if (getMoveExisting() != null && getFileExist() != 
GenericFileExist.Move) {
             throw new IllegalArgumentException("You must configure 
fileExist=Move when moveExisting has been set");
         }
-
+        if (this.getMoveExistingFileStrategy() == null) {
+            
this.setMoveExistingFileStrategy(createDefaultMoveExistingFileStrategy());
+        }
         return new GenericFileProducer<>(this, operations);
     }
 
@@ -181,6 +184,14 @@ protected FileConsumer newFileConsumer(Processor 
processor, GenericFileOperation
         return new FileConsumer(this, processor, operations, processStrategy 
!= null ? processStrategy : createGenericFileStrategy());
     }
 
+    /**
+     * Default Existing File Move Strategy
+     * @return the default implementation for file component
+     */
+    private FileMoveExistingStrategy createDefaultMoveExistingFileStrategy() {
+        return new GenericFileDefaultMoveExistingFileStrategy();
+    }
+
     public File getFile() {
         return file;
     }
diff --git 
a/camel-core/src/main/java/org/apache/camel/component/file/FileOperations.java 
b/camel-core/src/main/java/org/apache/camel/component/file/FileOperations.java
index 7a89227042f..9224b872ae6 100644
--- 
a/camel-core/src/main/java/org/apache/camel/component/file/FileOperations.java
+++ 
b/camel-core/src/main/java/org/apache/camel/component/file/FileOperations.java
@@ -227,7 +227,7 @@ public boolean storeFile(String fileName, Exchange 
exchange, long size) throws G
                 throw new GenericFileOperationFailedException("File already 
exist: " + file + ". Cannot write new file.");
             } else if (endpoint.getFileExist() == GenericFileExist.Move) {
                 // move any existing file first
-                doMoveExistingFile(fileName);
+                
this.endpoint.getMoveExistingFileStrategy().moveExistingFile(endpoint, this, 
fileName);
             }
         }
         
@@ -354,57 +354,7 @@ public boolean storeFile(String fileName, Exchange 
exchange, long size) throws G
             throw new GenericFileOperationFailedException("Cannot store file: 
" + file, e);
         }
     }
-
-    /**
-     * Moves any existing file due fileExists=Move is in use.
-     */
-    private void doMoveExistingFile(String fileName) throws 
GenericFileOperationFailedException {
-        // need to evaluate using a dummy and simulate the file first, to have 
access to all the file attributes
-        // create a dummy exchange as Exchange is needed for expression 
evaluation
-        // we support only the following 3 tokens.
-        Exchange dummy = endpoint.createExchange();
-        String parent = FileUtil.onlyPath(fileName);
-        String onlyName = FileUtil.stripPath(fileName);
-        dummy.getIn().setHeader(Exchange.FILE_NAME, fileName);
-        dummy.getIn().setHeader(Exchange.FILE_NAME_ONLY, onlyName);
-        dummy.getIn().setHeader(Exchange.FILE_PARENT, parent);
-
-        String to = endpoint.getMoveExisting().evaluate(dummy, String.class);
-        // we must normalize it (to avoid having both \ and / in the name 
which confuses java.io.File)
-        to = FileUtil.normalizePath(to);
-        if (ObjectHelper.isEmpty(to)) {
-            throw new GenericFileOperationFailedException("moveExisting 
evaluated as empty String, cannot move existing file: " + fileName);
-        }
-
-        // ensure any paths is created before we rename as the renamed file 
may be in a different path (which may be non exiting)
-        // use java.io.File to compute the file path
-        File toFile = new File(to);
-        String directory = toFile.getParent();
-        boolean absolute = FileUtil.isAbsolute(toFile);
-        if (directory != null) {
-            if (!buildDirectory(directory, absolute)) {
-                LOG.debug("Cannot build directory [{}] (could be because of 
denied permissions)", directory);
-            }
-        }
-
-        // deal if there already exists a file
-        if (existsFile(to)) {
-            if (endpoint.isEagerDeleteTargetFile()) {
-                LOG.trace("Deleting existing file: {}", to);
-                if (!deleteFile(to)) {
-                    throw new GenericFileOperationFailedException("Cannot 
delete file: " + to);
-                }
-            } else {
-                throw new GenericFileOperationFailedException("Cannot move 
existing file from: " + fileName + " to: " + to + " as there already exists a 
file: " + to);
-            }
-        }
-
-        LOG.trace("Moving existing file: {} to: {}", fileName, to);
-        if (!renameFile(fileName, to)) {
-            throw new GenericFileOperationFailedException("Cannot rename file 
from: " + fileName + " to: " + to);
-        }
-    }
-
+    
     private void keepLastModified(Exchange exchange, File file) {
         if (endpoint.isKeepLastModified()) {
             Long last;
diff --git 
a/camel-core/src/main/java/org/apache/camel/component/file/GenericFileDefaultMoveExistingFileStrategy.java
 
b/camel-core/src/main/java/org/apache/camel/component/file/GenericFileDefaultMoveExistingFileStrategy.java
new file mode 100644
index 00000000000..a8ddac8d92c
--- /dev/null
+++ 
b/camel-core/src/main/java/org/apache/camel/component/file/GenericFileDefaultMoveExistingFileStrategy.java
@@ -0,0 +1,87 @@
+/**
+ * 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 java.io.File;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.component.file.strategy.FileMoveExistingStrategy;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.ObjectHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GenericFileDefaultMoveExistingFileStrategy implements 
FileMoveExistingStrategy {
+    
+    private static final Logger LOG = 
LoggerFactory.getLogger(GenericFileDefaultMoveExistingFileStrategy.class);
+
+    /**
+     * Moves any existing file due fileExists=Move is in use.
+     */
+    @Override
+    public boolean moveExistingFile(GenericFileEndpoint endpoint, 
GenericFileOperations operations, String fileName)
+            throws GenericFileOperationFailedException {
+
+        // need to evaluate using a dummy and simulate the file first, to have 
access to all the file attributes
+        // create a dummy exchange as Exchange is needed for expression 
evaluation
+        // we support only the following 3 tokens.
+        Exchange dummy = endpoint.createExchange();
+        String parent = FileUtil.onlyPath(fileName);
+        String onlyName = FileUtil.stripPath(fileName);
+        dummy.getIn().setHeader(Exchange.FILE_NAME, fileName);
+        dummy.getIn().setHeader(Exchange.FILE_NAME_ONLY, onlyName);
+        dummy.getIn().setHeader(Exchange.FILE_PARENT, parent);
+
+        String to = endpoint.getMoveExisting().evaluate(dummy, String.class);
+        // we must normalize it (to avoid having both \ and / in the name 
which confuses java.io.File)
+        to = FileUtil.normalizePath(to);
+        if (ObjectHelper.isEmpty(to)) {
+            throw new GenericFileOperationFailedException("moveExisting 
evaluated as empty String, cannot move existing file: " + fileName);
+        }
+
+        // ensure any paths is created before we rename as the renamed file 
may be in a different path (which may be non exiting)
+        // use java.io.File to compute the file path
+        File toFile = new File(to);
+        String directory = toFile.getParent();
+        boolean absolute = FileUtil.isAbsolute(toFile);
+        if (directory != null) {
+            if (!operations.buildDirectory(directory, absolute)) {
+                LOG.debug("Cannot build directory [{}] (could be because of 
denied permissions)", directory);
+            }
+        }
+
+        // deal if there already exists a file
+        if (operations.existsFile(to)) {
+            if (endpoint.isEagerDeleteTargetFile()) {
+                LOG.trace("Deleting existing file: {}", to);
+                if (!operations.deleteFile(to)) {
+                    throw new GenericFileOperationFailedException("Cannot 
delete file: " + to);
+                }
+            } else {
+                throw new GenericFileOperationFailedException("Cannot move 
existing file from: " + fileName + " to: " + to + " as there already exists a 
file: " + to);
+            }
+        }
+
+        LOG.trace("Moving existing file: {} to: {}", fileName, to);
+        if (!operations.renameFile(fileName, to)) {
+            throw new GenericFileOperationFailedException("Cannot rename file 
from: " + fileName + " to: " + to);
+        }
+    
+        return true;
+    }
+
+}
diff --git 
a/camel-core/src/main/java/org/apache/camel/component/file/GenericFileEndpoint.java
 
b/camel-core/src/main/java/org/apache/camel/component/file/GenericFileEndpoint.java
index 954859303ff..fac9e7e8fbe 100644
--- 
a/camel-core/src/main/java/org/apache/camel/component/file/GenericFileEndpoint.java
+++ 
b/camel-core/src/main/java/org/apache/camel/component/file/GenericFileEndpoint.java
@@ -36,6 +36,7 @@
 import org.apache.camel.Message;
 import org.apache.camel.Predicate;
 import org.apache.camel.Processor;
+import org.apache.camel.component.file.strategy.FileMoveExistingStrategy;
 import org.apache.camel.impl.ScheduledPollEndpoint;
 import org.apache.camel.processor.idempotent.MemoryIdempotentRepository;
 import org.apache.camel.spi.BrowsableEndpoint;
@@ -135,6 +136,8 @@
     protected Expression preMove;
     @UriParam(label = "producer", javaType = "java.lang.String")
     protected Expression moveExisting;
+    @UriParam(label = "producer,advanced")
+    protected FileMoveExistingStrategy moveExistingFileStrategy;
     @UriParam(label = "consumer,filter", defaultValue = "false")
     protected Boolean idempotent;
     @UriParam(label = "consumer,filter", javaType = "java.lang.String")
@@ -570,6 +573,18 @@ public Expression getMoveExisting() {
     public void setMoveExisting(Expression moveExisting) {
         this.moveExisting = moveExisting;
     }
+    
+    public FileMoveExistingStrategy getMoveExistingFileStrategy() {
+        return moveExistingFileStrategy;
+    }
+
+    /**
+     * Strategy (Custom Strategy) used to move file with special naming token 
to use when fileExist=Move is configured.
+     * By default, there is an implementation used if no custom strategy is 
provided
+     */
+    public void setMoveExistingFileStrategy(FileMoveExistingStrategy 
moveExistingFileStrategy) {
+        this.moveExistingFileStrategy = moveExistingFileStrategy;
+    }
 
     public void setMoveExisting(String fileLanguageExpression) {
         String expression = 
configureMoveOrPreMoveExpression(fileLanguageExpression);
diff --git 
a/camel-core/src/main/java/org/apache/camel/component/file/strategy/FileMoveExistingStrategy.java
 
b/camel-core/src/main/java/org/apache/camel/component/file/strategy/FileMoveExistingStrategy.java
new file mode 100644
index 00000000000..48123e4cd3f
--- /dev/null
+++ 
b/camel-core/src/main/java/org/apache/camel/component/file/strategy/FileMoveExistingStrategy.java
@@ -0,0 +1,43 @@
+/**
+ * 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.strategy;
+
+import org.apache.camel.component.file.GenericFileEndpoint;
+import org.apache.camel.component.file.GenericFileOperationFailedException;
+import org.apache.camel.component.file.GenericFileOperations;
+
+/**
+ * This is the interface to be implemented when a custom implementation needs 
to be 
+ * provided in case of fileExists=Move is in use while moving any existing 
file in producer
+ * endpoints.
+ */
+public interface FileMoveExistingStrategy {
+
+    /**
+     * Moves any existing file due fileExists=Move is in use.
+     *
+     * @param endpoint  the given endpoint of the component
+     * @param operations file operations API of the relevant component's API 
+     * @return result of the file opeartion can be returned
+     *         note that for now, implemetion classes for file component
+     *         and ftp components, always returned true. However,if such
+     *         a need of direct usage of File API returning true|false,
+     *         you can use that return value for implementation's return value.
+     */
+    boolean moveExistingFile(GenericFileEndpoint endpoint, 
GenericFileOperations operations, String fileName) throws 
GenericFileOperationFailedException;
+
+}
diff --git 
a/camel-core/src/test/java/org/apache/camel/component/file/FileProducerMoveExistingStrategyTest.java
 
b/camel-core/src/test/java/org/apache/camel/component/file/FileProducerMoveExistingStrategyTest.java
new file mode 100644
index 00000000000..f8b2a6f8205
--- /dev/null
+++ 
b/camel-core/src/test/java/org/apache/camel/component/file/FileProducerMoveExistingStrategyTest.java
@@ -0,0 +1,137 @@
+/**
+ * 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 java.io.File;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.component.file.strategy.FileMoveExistingStrategy;
+import org.apache.camel.impl.JndiRegistry;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.ObjectHelper;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ */
+public class FileProducerMoveExistingStrategyTest extends ContextTestSupport {
+    
+    private MyStrategy myStrategy = new MyStrategy();
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        deleteDirectory("target/file");
+        super.setUp();
+    }
+    
+    @Override
+    protected JndiRegistry createRegistry() throws Exception {
+        JndiRegistry jndi = super.createRegistry();
+        jndi.bind("myStrategy", myStrategy);
+        return jndi;
+    }
+
+    @Test
+    public void testExistingFileExists() throws Exception {
+        
template.sendBodyAndHeader("file://target/file?fileExist=Move&moveExisting=${file:parent}/renamed-${file:onlyname}&moveExistingFileStrategy=#myStrategy",
+                "Hello World", Exchange.FILE_NAME, "hello.txt");
+        
template.sendBodyAndHeader("file://target/file?fileExist=Move&moveExisting=${file:parent}/renamed-${file:onlyname}&moveExistingFileStrategy=#myStrategy",
+                "Bye Existing World 1", Exchange.FILE_NAME, "hello.txt");
+        
template.sendBodyAndHeader("file://target/file?fileExist=Move&moveExisting=${file:parent}/renamed-${file:onlyname}&moveExistingFileStrategy=#myStrategy",
+                "Bye Existing World 2", Exchange.FILE_NAME, "hello.txt");
+
+        assertFileExists("target/file/hello.txt");
+        assertEquals("Bye Existing World 2", 
context.getTypeConverter().convertTo(String.class, new 
File("target/file/hello.txt")));
+        
+        assertFileExists("target/file/renamed-hello2.txt");
+        assertEquals("Bye Existing World 1", 
context.getTypeConverter().convertTo(String.class, new 
File("target/file/renamed-hello2.txt")));
+
+        assertFileExists("target/file/renamed-hello1.txt");
+        assertEquals("Hello World", 
context.getTypeConverter().convertTo(String.class, new 
File("target/file/renamed-hello1.txt")));
+    }
+    
+    private static class MyStrategy implements FileMoveExistingStrategy {
+        
+        private static final Logger LOG = 
LoggerFactory.getLogger(FileMoveExistingStrategy.class);
+        private int counter;
+        
+        @Override
+        public boolean moveExistingFile(GenericFileEndpoint endpoint, 
GenericFileOperations operations, String fileName)
+                throws GenericFileOperationFailedException {
+            
+            // need to evaluate using a dummy and simulate the file first, to 
have access to all the file attributes
+            // create a dummy exchange as Exchange is needed for expression 
evaluation
+            // we support only the following 3 tokens.
+            Exchange dummy = endpoint.createExchange();
+            String parent = FileUtil.onlyPath(fileName);
+            String onlyName = FileUtil.stripPath(fileName);
+            
+            dummy.getIn().setHeader(Exchange.FILE_NAME, fileName);
+            dummy.getIn().setHeader(Exchange.FILE_NAME_ONLY, onlyName);
+            dummy.getIn().setHeader(Exchange.FILE_PARENT, parent);
+
+            String to = endpoint.getMoveExisting().evaluate(dummy, 
String.class);
+            counter++;
+            String fileNameWithoutExtension = to.substring(0, 
to.lastIndexOf('.')) + "" + counter;
+            to = fileNameWithoutExtension + to.substring(to.lastIndexOf('.'), 
to.length());
+            // we must normalize it (to avoid having both \ and / in the name 
which confuses java.io.File)
+            to = FileUtil.normalizePath(to);
+            if (ObjectHelper.isEmpty(to)) {
+                throw new GenericFileOperationFailedException("moveExisting 
evaluated as empty String, cannot move existing file: " + fileName);
+            }
+
+            // ensure any paths is created before we rename as the renamed 
file may be in a different path (which may be non exiting)
+            // use java.io.File to compute the file path
+            File toFile = new File(to);
+            String directory = toFile.getParent();
+            boolean absolute = FileUtil.isAbsolute(toFile);
+            if (directory != null) {
+                if (!operations.buildDirectory(directory, absolute)) {
+                    LOG.debug("Cannot build directory [{}] (could be because 
of denied permissions)", directory);
+                }
+            }
+
+            // deal if there already exists a file
+            if (operations.existsFile(to)) {
+                if (endpoint.isEagerDeleteTargetFile()) {
+                    LOG.trace("Deleting existing file: {}", to);
+                    if (!operations.deleteFile(to)) {
+                        throw new GenericFileOperationFailedException("Cannot 
delete file: " + to);
+                    }
+                } else {
+                    throw new GenericFileOperationFailedException("Cannot 
moved existing file from: " + fileName + " to: " + to + " as there already 
exists a file: " + to);
+                }
+            }
+
+            LOG.trace("Moving existing file: {} to: {}", fileName, to);
+            if (!operations.renameFile(fileName, to)) {
+                throw new GenericFileOperationFailedException("Cannot rename 
file from: " + fileName + " to: " + to);
+            }
+            return true;
+        }
+    
+    }
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+}
diff --git a/components/camel-ftp/src/main/docs/ftp-component.adoc 
b/components/camel-ftp/src/main/docs/ftp-component.adoc
index a4046c0d07d..590eb5b6803 100644
--- a/components/camel-ftp/src/main/docs/ftp-component.adoc
+++ b/components/camel-ftp/src/main/docs/ftp-component.adoc
@@ -100,7 +100,7 @@ with the following path and query parameters:
 |===
 
 
-==== Query Parameters (108 parameters):
+==== Query Parameters (109 parameters):
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
@@ -151,6 +151,7 @@ with the following path and query parameters:
 | *disconnectOnBatchComplete* (producer) | Whether or not to disconnect from 
remote FTP server right after a Batch upload is complete. 
disconnectOnBatchComplete will only disconnect the current connection to the 
FTP server. | false | boolean
 | *eagerDeleteTargetFile* (producer) | Whether or not to eagerly delete any 
existing target file. This option only applies when you use fileExists=Override 
and the tempFileName option as well. You can use this to disable (set it to 
false) deleting the target file before the temp file is written. For example 
you may write big files and want the target file to exists during the temp file 
is being written. This ensure the target file is only deleted until the very 
last moment, just before the temp file is being renamed to the target filename. 
This option is also used to control whether to delete any existing files when 
fileExist=Move is enabled, and an existing file exists. If this option 
copyAndDeleteOnRenameFails false, then an exception will be thrown if an 
existing file existed, if its true, then the existing file is deleted before 
the move operation. | true | boolean
 | *keepLastModified* (producer) | Will keep the last modified timestamp from 
the source file (if any). Will use the Exchange.FILE_LAST_MODIFIED header to 
located the timestamp. This header can contain either a java.util.Date or long 
with the timestamp. If the timestamp exists and the option is enabled it will 
set this timestamp on the written file. Note: This option only applies to the 
file producer. You cannot use this option with any of the ftp producers. | 
false | boolean
+| *moveExistingFileStrategy* (producer) | Strategy (Custom Strategy) used to 
move file with special naming token to use when fileExist=Move is configured. 
By default, there is an implementation used if no custom strategy is provided | 
 | FileMoveExisting Strategy
 | *sendNoop* (producer) | Whether to send a noop command as a pre-write check 
before uploading files to the FTP server. This is enabled by default as a 
validation of the connection is still valid, which allows to silently 
re-connect to be able to upload the file. However if this causes problems, you 
can turn this option off. | true | boolean
 | *activePortRange* (advanced) | Set the client side port range in active 
mode. The syntax is: minPort-maxPort Both port numbers are inclusive, eg 
10000-19999 to include all 1xxxx ports. |  | String
 | *autoCreate* (advanced) | Automatically create missing directories in the 
file's pathname. For the file consumer, that means creating the starting 
directory. For the file producer, it means the directory the files should be 
written to. | true | boolean
diff --git a/components/camel-ftp/src/main/docs/ftps-component.adoc 
b/components/camel-ftp/src/main/docs/ftps-component.adoc
index 3ccaf392519..797a1c8bc3d 100644
--- a/components/camel-ftp/src/main/docs/ftps-component.adoc
+++ b/components/camel-ftp/src/main/docs/ftps-component.adoc
@@ -60,7 +60,7 @@ with the following path and query parameters:
 |===
 
 
-==== Query Parameters (120 parameters):
+==== Query Parameters (121 parameters):
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
@@ -111,6 +111,7 @@ with the following path and query parameters:
 | *disconnectOnBatchComplete* (producer) | Whether or not to disconnect from 
remote FTP server right after a Batch upload is complete. 
disconnectOnBatchComplete will only disconnect the current connection to the 
FTP server. | false | boolean
 | *eagerDeleteTargetFile* (producer) | Whether or not to eagerly delete any 
existing target file. This option only applies when you use fileExists=Override 
and the tempFileName option as well. You can use this to disable (set it to 
false) deleting the target file before the temp file is written. For example 
you may write big files and want the target file to exists during the temp file 
is being written. This ensure the target file is only deleted until the very 
last moment, just before the temp file is being renamed to the target filename. 
This option is also used to control whether to delete any existing files when 
fileExist=Move is enabled, and an existing file exists. If this option 
copyAndDeleteOnRenameFails false, then an exception will be thrown if an 
existing file existed, if its true, then the existing file is deleted before 
the move operation. | true | boolean
 | *keepLastModified* (producer) | Will keep the last modified timestamp from 
the source file (if any). Will use the Exchange.FILE_LAST_MODIFIED header to 
located the timestamp. This header can contain either a java.util.Date or long 
with the timestamp. If the timestamp exists and the option is enabled it will 
set this timestamp on the written file. Note: This option only applies to the 
file producer. You cannot use this option with any of the ftp producers. | 
false | boolean
+| *moveExistingFileStrategy* (producer) | Strategy (Custom Strategy) used to 
move file with special naming token to use when fileExist=Move is configured. 
By default, there is an implementation used if no custom strategy is provided | 
 | FileMoveExisting Strategy
 | *sendNoop* (producer) | Whether to send a noop command as a pre-write check 
before uploading files to the FTP server. This is enabled by default as a 
validation of the connection is still valid, which allows to silently 
re-connect to be able to upload the file. However if this causes problems, you 
can turn this option off. | true | boolean
 | *activePortRange* (advanced) | Set the client side port range in active 
mode. The syntax is: minPort-maxPort Both port numbers are inclusive, eg 
10000-19999 to include all 1xxxx ports. |  | String
 | *autoCreate* (advanced) | Automatically create missing directories in the 
file's pathname. For the file consumer, that means creating the starting 
directory. For the file producer, it means the directory the files should be 
written to. | true | boolean
diff --git a/components/camel-ftp/src/main/docs/sftp-component.adoc 
b/components/camel-ftp/src/main/docs/sftp-component.adoc
index b3c8d344ba5..2fc626ae211 100644
--- a/components/camel-ftp/src/main/docs/sftp-component.adoc
+++ b/components/camel-ftp/src/main/docs/sftp-component.adoc
@@ -51,7 +51,7 @@ with the following path and query parameters:
 |===
 
 
-==== Query Parameters (116 parameters):
+==== Query Parameters (117 parameters):
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
@@ -95,6 +95,7 @@ with the following path and query parameters:
 | *disconnectOnBatchComplete* (producer) | Whether or not to disconnect from 
remote FTP server right after a Batch upload is complete. 
disconnectOnBatchComplete will only disconnect the current connection to the 
FTP server. | false | boolean
 | *eagerDeleteTargetFile* (producer) | Whether or not to eagerly delete any 
existing target file. This option only applies when you use fileExists=Override 
and the tempFileName option as well. You can use this to disable (set it to 
false) deleting the target file before the temp file is written. For example 
you may write big files and want the target file to exists during the temp file 
is being written. This ensure the target file is only deleted until the very 
last moment, just before the temp file is being renamed to the target filename. 
This option is also used to control whether to delete any existing files when 
fileExist=Move is enabled, and an existing file exists. If this option 
copyAndDeleteOnRenameFails false, then an exception will be thrown if an 
existing file existed, if its true, then the existing file is deleted before 
the move operation. | true | boolean
 | *keepLastModified* (producer) | Will keep the last modified timestamp from 
the source file (if any). Will use the Exchange.FILE_LAST_MODIFIED header to 
located the timestamp. This header can contain either a java.util.Date or long 
with the timestamp. If the timestamp exists and the option is enabled it will 
set this timestamp on the written file. Note: This option only applies to the 
file producer. You cannot use this option with any of the ftp producers. | 
false | boolean
+| *moveExistingFileStrategy* (producer) | Strategy (Custom Strategy) used to 
move file with special naming token to use when fileExist=Move is configured. 
By default, there is an implementation used if no custom strategy is provided | 
 | FileMoveExisting Strategy
 | *sendNoop* (producer) | Whether to send a noop command as a pre-write check 
before uploading files to the FTP server. This is enabled by default as a 
validation of the connection is still valid, which allows to silently 
re-connect to be able to upload the file. However if this causes problems, you 
can turn this option off. | true | boolean
 | *autoCreate* (advanced) | Automatically create missing directories in the 
file's pathname. For the file consumer, that means creating the starting 
directory. For the file producer, it means the directory the files should be 
written to. | true | boolean
 | *bindAddress* (advanced) | Specifies the address of the local interface 
against which the connection should bind. |  | String
diff --git 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpDefaultMoveExistingFileStrategy.java
 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpDefaultMoveExistingFileStrategy.java
new file mode 100644
index 00000000000..9d3757a5ed5
--- /dev/null
+++ 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpDefaultMoveExistingFileStrategy.java
@@ -0,0 +1,96 @@
+/**
+ * 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.remote;
+
+import java.io.IOException;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.component.file.GenericFileEndpoint;
+import org.apache.camel.component.file.GenericFileOperationFailedException;
+import org.apache.camel.component.file.GenericFileOperations;
+import org.apache.camel.component.file.strategy.FileMoveExistingStrategy;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.commons.net.ftp.FTPFile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FtpDefaultMoveExistingFileStrategy implements 
FileMoveExistingStrategy {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(FtpDefaultMoveExistingFileStrategy.class);
+
+    /**
+     * Moves any existing file due fileExists=Move is in use.
+     */
+    @Override
+    public boolean moveExistingFile(GenericFileEndpoint endpoint, 
GenericFileOperations operations, String fileName)
+            throws GenericFileOperationFailedException {
+        // need to evaluate using a dummy and simulate the file first, to have 
access to all the file attributes
+        // create a dummy exchange as Exchange is needed for expression 
evaluation
+        // we support only the following 3 tokens.
+        Exchange dummy = endpoint.createExchange();
+        // we only support relative paths for the ftp component, so dont 
provide any parent
+        String parent = null;
+        String onlyName = FileUtil.stripPath(fileName);
+        dummy.getIn().setHeader(Exchange.FILE_NAME, fileName);
+        dummy.getIn().setHeader(Exchange.FILE_NAME_ONLY, onlyName);
+        dummy.getIn().setHeader(Exchange.FILE_PARENT, parent);
+
+        String to = endpoint.getMoveExisting().evaluate(dummy, String.class);
+        // we only support relative paths for the ftp component, so strip any 
leading paths
+        to = FileUtil.stripLeadingSeparator(to);
+        // normalize accordingly to configuration
+        to = 
((FtpEndpoint<FTPFile>)endpoint).getConfiguration().normalizePath(to);
+        if (ObjectHelper.isEmpty(to)) {
+            throw new GenericFileOperationFailedException("moveExisting 
evaluated as empty String, cannot move existing file: " + fileName);
+        }
+
+        // do we have a sub directory
+        String dir = FileUtil.onlyPath(to);
+        if (dir != null) {
+            // ensure directory exists
+            operations.buildDirectory(dir, false);
+        }
+
+        // deal if there already exists a file
+        if (operations.existsFile(to)) {
+            if (endpoint.isEagerDeleteTargetFile()) {
+                LOG.trace("Deleting existing file: {}", to);
+                boolean result;
+                try {
+                    result = 
((FtpOperations)operations).getClient().deleteFile(to);
+                    if (!result) {
+                        throw new GenericFileOperationFailedException("Cannot 
delete file: " + to);
+                    }
+                } catch (IOException e) {
+                    throw new 
GenericFileOperationFailedException(((FtpOperations)operations).getClient().getReplyCode(),
 
+                                                                  
((FtpOperations)operations).getClient().getReplyString(), 
+                                                                  "Cannot 
delete file: " + to, e);
+                }
+            } else {
+                throw new GenericFileOperationFailedException("Cannot move 
existing file from: " + fileName + " to: " + to + " as there already exists a 
file: " + to);
+            }
+        }
+
+        LOG.trace("Moving existing file: {} to: {}", fileName, to);
+        if (!operations.renameFile(fileName, to)) {
+            throw new GenericFileOperationFailedException("Cannot rename file 
from: " + fileName + " to: " + to);
+        }
+        return true;
+    }
+
+}
diff --git 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpEndpoint.java
 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpEndpoint.java
index 8334e69861b..22a72ca61dc 100644
--- 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpEndpoint.java
+++ 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpEndpoint.java
@@ -28,6 +28,7 @@
 import org.apache.camel.component.file.GenericFileConfiguration;
 import org.apache.camel.component.file.GenericFileProducer;
 import 
org.apache.camel.component.file.remote.RemoteFileConfiguration.PathSeparator;
+import org.apache.camel.component.file.strategy.FileMoveExistingStrategy;
 import org.apache.camel.spi.ClassResolver;
 import org.apache.camel.spi.UriEndpoint;
 import org.apache.camel.spi.UriParam;
@@ -103,12 +104,23 @@ public String getScheme() {
 
     protected GenericFileProducer<FTPFile> buildProducer() {
         try {
+            if (this.getMoveExistingFileStrategy() == null) {
+                
this.setMoveExistingFileStrategy(createDefaultFtpMoveExistingFileStrategy());
+            }
             return new RemoteFileProducer<>(this, 
createRemoteFileOperations());
         } catch (Exception e) {
             throw new FailedToCreateProducerException(this, e);
         }
     }
 
+    /**
+     * Default Existing File Move Strategy
+     * @return the default implementation for ftp components
+     */
+    private FileMoveExistingStrategy 
createDefaultFtpMoveExistingFileStrategy() {
+        return new FtpDefaultMoveExistingFileStrategy();
+    }
+
     public RemoteFileOperations<FTPFile> createRemoteFileOperations() throws 
Exception {
         // configure ftp client
         FTPClient client = ftpClient;
diff --git 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
index 196dfc258fe..25ea4fc8196 100644
--- 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
+++ 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
@@ -646,7 +646,7 @@ private boolean doStoreFile(String name, String targetName, 
Exchange exchange) t
                 throw new GenericFileOperationFailedException("File already 
exist: " + name + ". Cannot write new file.");
             } else if (existFile && endpoint.getFileExist() == 
GenericFileExist.Move) {
                 // move any existing file first
-                doMoveExistingFile(name, targetName);
+                
this.endpoint.getMoveExistingFileStrategy().moveExistingFile(endpoint, this, 
targetName);
             }
         }
 
@@ -715,61 +715,6 @@ private boolean doStoreFile(String name, String 
targetName, Exchange exchange) t
         }
     }
 
-    /**
-     * Moves any existing file due fileExists=Move is in use.
-     */
-    private void doMoveExistingFile(String name, String targetName) throws 
GenericFileOperationFailedException {
-        // need to evaluate using a dummy and simulate the file first, to have 
access to all the file attributes
-        // create a dummy exchange as Exchange is needed for expression 
evaluation
-        // we support only the following 3 tokens.
-        Exchange dummy = endpoint.createExchange();
-        // we only support relative paths for the ftp component, so dont 
provide any parent
-        String parent = null;
-        String onlyName = FileUtil.stripPath(targetName);
-        dummy.getIn().setHeader(Exchange.FILE_NAME, targetName);
-        dummy.getIn().setHeader(Exchange.FILE_NAME_ONLY, onlyName);
-        dummy.getIn().setHeader(Exchange.FILE_PARENT, parent);
-
-        String to = endpoint.getMoveExisting().evaluate(dummy, String.class);
-        // we only support relative paths for the ftp component, so strip any 
leading paths
-        to = FileUtil.stripLeadingSeparator(to);
-        // normalize accordingly to configuration
-        to = endpoint.getConfiguration().normalizePath(to);
-        if (ObjectHelper.isEmpty(to)) {
-            throw new GenericFileOperationFailedException("moveExisting 
evaluated as empty String, cannot move existing file: " + name);
-        }
-
-        // do we have a sub directory
-        String dir = FileUtil.onlyPath(to);
-        if (dir != null) {
-            // ensure directory exists
-            buildDirectory(dir, false);
-        }
-
-        // deal if there already exists a file
-        if (existsFile(to)) {
-            if (endpoint.isEagerDeleteTargetFile()) {
-                log.trace("Deleting existing file: {}", to);
-                boolean result;
-                try {
-                    result = client.deleteFile(to);
-                    if (!result) {
-                        throw new GenericFileOperationFailedException("Cannot 
delete file: " + to);
-                    }
-                } catch (IOException e) {
-                    throw new 
GenericFileOperationFailedException(client.getReplyCode(), 
client.getReplyString(), "Cannot delete file: " + to, e);
-                }
-            } else {
-                throw new GenericFileOperationFailedException("Cannot move 
existing file from: " + name + " to: " + to + " as there already exists a file: 
" + to);
-            }
-        }
-
-        log.trace("Moving existing file: {} to: {}", name, to);
-        if (!renameFile(targetName, to)) {
-            throw new GenericFileOperationFailedException("Cannot rename file 
from: " + name + " to: " + to);
-        }
-    }
-
     public boolean existsFile(String name) throws 
GenericFileOperationFailedException {
         log.trace("existsFile({})", name);
         if (endpoint.isFastExistsCheck()) {
@@ -980,5 +925,9 @@ private boolean buildDirectoryChunks(String dirName) throws 
IOException {
 
         return success;
     }
+    
+    public FTPClient getClient() {
+        return client;
+    }
 
 }
diff --git 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpDefaultMoveExistingFileStrategy.java
 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpDefaultMoveExistingFileStrategy.java
new file mode 100644
index 00000000000..48a8fb4cba0
--- /dev/null
+++ 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpDefaultMoveExistingFileStrategy.java
@@ -0,0 +1,83 @@
+/**
+ * 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.remote;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.component.file.GenericFileEndpoint;
+import org.apache.camel.component.file.GenericFileOperationFailedException;
+import org.apache.camel.component.file.GenericFileOperations;
+import org.apache.camel.component.file.strategy.FileMoveExistingStrategy;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.ObjectHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SftpDefaultMoveExistingFileStrategy implements 
FileMoveExistingStrategy {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(SftpDefaultMoveExistingFileStrategy.class);
+    /**
+     * Moves any existing file due fileExists=Move is in use.
+     */
+    @Override
+    public boolean moveExistingFile(GenericFileEndpoint endpoint, 
GenericFileOperations operations, String fileName)
+            throws GenericFileOperationFailedException {
+        // need to evaluate using a dummy and simulate the file first, to have 
access to all the file attributes
+        // create a dummy exchange as Exchange is needed for expression 
evaluation
+        // we support only the following 3 tokens.
+        Exchange dummy = endpoint.createExchange();
+        // we only support relative paths for the ftp component, so dont 
provide any parent
+        String parent = null;
+        String onlyName = FileUtil.stripPath(fileName);
+        dummy.getIn().setHeader(Exchange.FILE_NAME, fileName);
+        dummy.getIn().setHeader(Exchange.FILE_NAME_ONLY, onlyName);
+        dummy.getIn().setHeader(Exchange.FILE_PARENT, parent);
+
+        String to = endpoint.getMoveExisting().evaluate(dummy, String.class);
+        // we only support relative paths for the ftp component, so strip any 
leading paths
+        to = FileUtil.stripLeadingSeparator(to);
+        // normalize accordingly to configuration
+        to = ((SftpEndpoint)endpoint).getConfiguration().normalizePath(to);
+        if (ObjectHelper.isEmpty(to)) {
+            throw new GenericFileOperationFailedException("moveExisting 
evaluated as empty String, cannot move existing file: " + fileName);
+        }
+
+        // do we have a sub directory
+        String dir = FileUtil.onlyPath(to);
+        if (dir != null) {
+            // ensure directory exists
+            operations.buildDirectory(dir, false);
+        }
+
+        // deal if there already exists a file
+        if (operations.existsFile(to)) {
+            if (endpoint.isEagerDeleteTargetFile()) {
+                LOG.trace("Deleting existing file: {}", to);
+                operations.deleteFile(to);
+            } else {
+                throw new GenericFileOperationFailedException("Cannot move 
existing file from: " + fileName + " to: " + to + " as there already exists a 
file: " + to);
+            }
+        }
+
+        LOG.trace("Moving existing file: {} to: {}", fileName, to);
+        if (!operations.renameFile(fileName, to)) {
+            throw new GenericFileOperationFailedException("Cannot rename file 
from: " + fileName + " to: " + to);
+        }
+    
+        return true;
+    }
+
+}
diff --git 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpEndpoint.java
 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpEndpoint.java
index 5f82b3a1bee..069dfa7411d 100644
--- 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpEndpoint.java
+++ 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpEndpoint.java
@@ -20,6 +20,7 @@
 import org.apache.camel.Processor;
 import org.apache.camel.component.file.GenericFileConfiguration;
 import org.apache.camel.component.file.GenericFileProducer;
+import org.apache.camel.component.file.strategy.FileMoveExistingStrategy;
 import org.apache.camel.spi.UriEndpoint;
 import org.apache.camel.spi.UriParam;
 
@@ -65,9 +66,20 @@ public void setConfiguration(GenericFileConfiguration 
configuration) {
     }
 
     protected GenericFileProducer<SftpRemoteFile> buildProducer() {
+        if (this.getMoveExistingFileStrategy() == null) {
+            
this.setMoveExistingFileStrategy(createDefaultSftpMoveExistingFileStrategy());
+        }
         return new RemoteFileProducer<>(this, createRemoteFileOperations());
     }
 
+    /**
+     * Default Existing File Move Strategy
+     * @return the default implementation for sftp component
+     */
+    private FileMoveExistingStrategy 
createDefaultSftpMoveExistingFileStrategy() {
+        return new SftpDefaultMoveExistingFileStrategy();
+    }
+
     public RemoteFileOperations<SftpRemoteFile> createRemoteFileOperations() {
         SftpOperations operations = new SftpOperations(proxy);
         operations.setEndpoint(this);
diff --git 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
index 8c3a1e7cc53..1db1c84e210 100644
--- 
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
+++ 
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
@@ -900,7 +900,7 @@ private boolean doStoreFile(String name, String targetName, 
Exchange exchange) t
                 throw new GenericFileOperationFailedException("File already 
exist: " + name + ". Cannot write new file.");
             } else if (existFile && endpoint.getFileExist() == 
GenericFileExist.Move) {
                 // move any existing file first
-                doMoveExistingFile(name, targetName);
+                
this.endpoint.getMoveExistingFileStrategy().moveExistingFile(endpoint, this, 
targetName);
             }
         }
 
@@ -965,54 +965,7 @@ private boolean doStoreFile(String name, String 
targetName, Exchange exchange) t
             IOHelper.close(is, "store: " + name, LOG);
         }
     }
-
-    /**
-     * Moves any existing file due fileExists=Move is in use.
-     */
-    private void doMoveExistingFile(String name, String targetName) throws 
GenericFileOperationFailedException {
-        // need to evaluate using a dummy and simulate the file first, to have 
access to all the file attributes
-        // create a dummy exchange as Exchange is needed for expression 
evaluation
-        // we support only the following 3 tokens.
-        Exchange dummy = endpoint.createExchange();
-        // we only support relative paths for the ftp component, so dont 
provide any parent
-        String parent = null;
-        String onlyName = FileUtil.stripPath(targetName);
-        dummy.getIn().setHeader(Exchange.FILE_NAME, targetName);
-        dummy.getIn().setHeader(Exchange.FILE_NAME_ONLY, onlyName);
-        dummy.getIn().setHeader(Exchange.FILE_PARENT, parent);
-
-        String to = endpoint.getMoveExisting().evaluate(dummy, String.class);
-        // we only support relative paths for the ftp component, so strip any 
leading paths
-        to = FileUtil.stripLeadingSeparator(to);
-        // normalize accordingly to configuration
-        to = endpoint.getConfiguration().normalizePath(to);
-        if (ObjectHelper.isEmpty(to)) {
-            throw new GenericFileOperationFailedException("moveExisting 
evaluated as empty String, cannot move existing file: " + name);
-        }
-
-        // do we have a sub directory
-        String dir = FileUtil.onlyPath(to);
-        if (dir != null) {
-            // ensure directory exists
-            buildDirectory(dir, false);
-        }
-
-        // deal if there already exists a file
-        if (existsFile(to)) {
-            if (endpoint.isEagerDeleteTargetFile()) {
-                LOG.trace("Deleting existing file: {}", to);
-                deleteFile(to);
-            } else {
-                throw new GenericFileOperationFailedException("Cannot move 
existing file from: " + name + " to: " + to + " as there already exists a file: 
" + to);
-            }
-        }
-
-        LOG.trace("Moving existing file: {} to: {}", name, to);
-        if (!renameFile(targetName, to)) {
-            throw new GenericFileOperationFailedException("Cannot rename file 
from: " + name + " to: " + to);
-        }
-    }
-
+    
     public synchronized boolean existsFile(String name) throws 
GenericFileOperationFailedException {
         LOG.trace("existsFile({})", name);
         if (endpoint.isFastExistsCheck()) {
diff --git a/components/camel-jsch/src/main/docs/scp-component.adoc 
b/components/camel-jsch/src/main/docs/scp-component.adoc
index 836162ab3dd..7303285b145 100644
--- a/components/camel-jsch/src/main/docs/scp-component.adoc
+++ b/components/camel-jsch/src/main/docs/scp-component.adoc
@@ -83,7 +83,7 @@ with the following path and query parameters:
 |===
 
 
-==== Query Parameters (20 parameters):
+==== Query Parameters (21 parameters):
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
@@ -96,6 +96,7 @@ with the following path and query parameters:
 | *strictHostKeyChecking* (producer) | Sets whether to use strict host key 
checking. Possible values are: no, yes | no | String
 | *allowNullBody* (producer) | Used to specify if a null body is allowed 
during file writing. If set to true then an empty file will be created, when 
set to false, and attempting to send a null body to the file component, a 
GenericFileWriteException of 'Cannot write null body to file.' will be thrown. 
If the fileExist option is set to 'Override', then the file will be truncated, 
and if set to append the file will remain unchanged. | false | boolean
 | *disconnectOnBatchComplete* (producer) | Whether or not to disconnect from 
remote FTP server right after a Batch upload is complete. 
disconnectOnBatchComplete will only disconnect the current connection to the 
FTP server. | false | boolean
+| *moveExistingFileStrategy* (producer) | Strategy (Custom Strategy) used to 
move file with special naming token to use when fileExist=Move is configured. 
By default, there is an implementation used if no custom strategy is provided | 
 | FileMoveExisting Strategy
 | *connectTimeout* (advanced) | Sets the connect timeout for waiting for a 
connection to be established Used by both FTPClient and JSCH | 10000 | int
 | *soTimeout* (advanced) | Sets the so timeout Used only by FTPClient | 300000 
| int
 | *synchronous* (advanced) | Sets whether synchronous processing should be 
strictly used, or Camel is allowed to use asynchronous processing (if 
supported). | false | boolean


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


> Add fileExist option to write file with a new name in case file already exists
> ------------------------------------------------------------------------------
>
>                 Key: CAMEL-5558
>                 URL: https://issues.apache.org/jira/browse/CAMEL-5558
>             Project: Camel
>          Issue Type: New Feature
>          Components: camel-core, camel-ftp
>    Affects Versions: 2.10.0
>            Reporter: Claus Ibsen
>            Assignee: Önder Sezgin
>            Priority: Major
>             Fix For: 2.23.0
>
>
> See nabble
> http://camel.465427.n5.nabble.com/Camel-FTP-component-Append-a-sequence-number-if-the-filename-already-exists-tp5718469.html
> We should allow to configure fileExists=RenameAndRetry option, which allows 
> you to rename the file and retry writing the file again, in case there 
> already exists a file. 
> The trick is to find a optimal solution if  you append a sequence number to a 
> file. And when there is a lot of files. You kinda want to know what is the 
> highest filename, (some way of listing and sorting). 
> Maybe a way to get a list of existing files based on a file name pattern, and 
> a sort configuration. Then people can check in the list, to find a free 
> sequence number to use etc.



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

Reply via email to