1

I need to copy/duplicate remote file in Sftp server also rename when copied, I read here that copying remote file in Sftp isn't supported so the only the available option I had is to GET file into Local and then PUT again to Sftp & delete the Local file, I have successfully achieved my goal but the problem is there is a log printing from org.springframework.core.log.LogAccessor: I have no idea from where it is coming.

Code that helps in copying remote file:

 @Bean
public IntegrationFlow copyRemoteFile() {
    return IntegrationFlows.from("integration.channel.copy")
            .handle(Sftp.outboundGateway(sftpSessionFactory(),
                    AbstractRemoteFileOutboundGateway.Command.GET,
                    "headers[" + COPY_SOURCE_PATH.value + "]+'/'+" +
                            "headers[" + COPY_SOURCE_FILENAME.value + "]")
                    .autoCreateLocalDirectory(true)
                    .fileExistsMode(FileExistsMode.REPLACE)
                    .localDirectory(new File(localPath)))
            .log(LoggingHandler.Level.INFO, "SftpCopyService")
            .handle(Sftp.outboundGateway(sftpSessionFactory(),
                    AbstractRemoteFileOutboundGateway.Command.PUT,
                    "payload")
                    .remoteDirectoryExpression("headers[" + COPY_DEST_PATH.value + "]")
                    .fileNameGenerator(n -> (String)n.getHeaders().get(COPY_DEST_FILENAME.value))
                    .fileExistsMode(FileExistsMode.REPLACE))
            .log(LoggingHandler.Level.INFO, "SftpCopyService")
            .handle((p, h) -> {
                try {
                      return Files.deleteIfExists(
                            Paths.get(localPath + File.separator + h.get(COPY_SOURCE_FILENAME.value)));
                } catch (IOException e) {
                    e.printStackTrace();
                    return false;
                }
            })
            .get();

Here is the log.

2021-02-16 18:10:22,577 WARN  [http-nio-9090-exec-1] org.springframework.core.log.LogAccessor: Failed to delete C:\Users\DELL\Desktop\GetTest\Spring Integration.txt
2021-02-16 18:10:22,784 INFO  [http-nio-9090-exec-1] org.springframework.core.log.LogAccessor: GenericMessage [payload=C:\Users\DELL\Desktop\GetTest\Spring Integration.txt, headers={file_remoteHostPort=X.X.X.X:22, replyChannel=nullChannel, sourceFileName=Spring Integration.txt, file_remoteDirectory=/uploads/, destFileName=Spring Integrat.txt, destPath=uploads/dest, id=5105bdd1-8180-1185-3661-2ed708e07ab9, sourcePath=/uploads, file_remoteFile=Spring Integration.txt, timestamp=1613479222779}]
2021-02-16 18:10:23,011 INFO  [http-nio-9090-exec-1] org.springframework.core.log.LogAccessor: GenericMessage [payload=uploads/dest/Spring Integrat.txt, headers={file_remoteHostPort=X.X.X.X:22, replyChannel=nullChannel, sourceFileName=Spring Integration.txt, file_remoteDirectory=/uploads/, destFileName=Spring Integrat.txt, destPath=uploads/dest, id=1bf83b0f-3b24-66bd-ffbf-2a9018b499fb, sourcePath=/uploads, file_remoteFile=Spring Integration.txt, timestamp=1613479223011}]

The more surprising part is, it appears very early even before the flow is executed, though I have handled file deletion at very last. How can i get rid of this log message? though it doesn't effect my process but the log message is misleading

Also is there any better way to copy remote file to another path inside sftp

EDIT

Like you suggested I tried the SftpRemoteFileTemplate.execute() method to copy files in sftp but when the session.write(InputStream stream,String path) method is called the method control never returns it keeps the control forever

I tried debugging, the control is lost when the execution reaches here:

for(_ackcount = this.seq - startid; _ackcount > ackcount && this.checkStatus((int[])null, header); ++ackcount) {
            }

This code sits inside _put method of ChannelSftp.class

Here is the sample code that I'm trying

 public boolean copy() {
   return remoteFileTemplate.execute(session -> {
        if (!session.exists("uploads/Spring Integration.txt")){
            return false;
        }
        if (!session.exists("uploads/dest")){
            session.mkdir("uploads/dest");
        }
        InputStream inputStream = session.readRaw("uploads/Spring Integration.txt");
        session.write(inputStream, "uploads/dest/spring.txt");
        session.finalizeRaw();
        return true;
    });
}

Would you please point out what mistake I'm doing here?

1 Answers1

1

Instead of writing the whole flow via local file copy, I'd suggest to look into a single service activator for the SftpRemoteFileTemplate.execute(SessionCallback<F, T>). The provided SftpSession in that callback can be used for the InputStream readRaw() and write(InputStream inputStream, String destination). In the end you must call finalizeRaw().

The LogAccessor issue is not clear. What Spring Integration version do you use? Do you override Spring Core version though?

I think we can improve that WARN message and don't call File.delete() if it does not exists().

Feel free to provide such a contribution!

UPDATE

The JUnit test to demonstrate how to perform a copy on SFTP server:

@Test
public void testSftpCopy() throws Exception {
    this.template.execute(session -> {
        PipedInputStream in = new PipedInputStream();
        PipedOutputStream out = new PipedOutputStream(in);
        session.read("sftpSource/sftpSource2.txt", out);
        session.write(in, "sftpTarget/sftpTarget2.txt");
        return null;
    });

    Session<?> session = this.sessionFactory.getSession();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    FileCopyUtils.copy(session.readRaw("sftpTarget/sftpTarget2.txt"), baos);
    assertThat(session.finalizeRaw()).isTrue();
    assertThat(new String(baos.toByteArray())).isEqualTo("source2");

    baos = new ByteArrayOutputStream();
    FileCopyUtils.copy(session.readRaw("sftpSource/sftpSource2.txt"), baos);
    assertThat(session.finalizeRaw()).isTrue();
    assertThat(new String(baos.toByteArray())).isEqualTo("source2");

    session.close();
}
Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • No, I'm not overriding Spring Core version. I'm using Spring boot 2.4.1 – abdur rehman Feb 18 '21 at 05:20
  • I tried the `SftpRemoteFileTemplate.execute(SessionCallback)` method to accomplish my task but it doesn't seem to work, I have edited my question to provide more details. – abdur rehman Feb 18 '21 at 12:50
  • Please, find an UPDATE in my answer. I think the `ChannelSftp` just can't deal with several concurrent opened streams... – Artem Bilan Feb 18 '21 at 16:00