First of all you can find my code at https://github.com/keiseithunder/spring-sftp-xml-to-json/blob/main/src/main/java/com/demo/sftp/SftpApplication.java
I have a spring integration that read file from SFTP server into a stream and send to kafka and then trying to move a remote file to other directory in the same remote server after succesfully produce the message. But, the file doesn't move at all and didn't throw any error.
I have try using this code to move a file from upload/test.xml (the file in upload/file/test.xml is a backup that I use to copy to upload directory) to upload/processed/test.xml.
#Bean
#ServiceActivator(inputChannel = "success")
public MessageHandler handler() {
return new SftpOutboundGateway(sftpSessionFactory(), "mv", "");
}
I already set file_renameTo=upload/processed/test.xml in the headers. Not sure, what I do wrong. Or there is a way to use something like advice.setOnSuccessExpressionString("#template.copy(headers['file_remoteDirectory']+'/'+headers['file_remoteFile'])"); to move a file?
My Message is
"GenericMessage [payload=Note(to=Toves, from=Jani, heading=Reminder, body=Don't forget me this weekend!!!!!), headers={file_remoteHostPort=localhost:2222, file_remoteFileInfo={"directory":false,"filename":"test.xml","link":false,"modified":1674550080000,"permissions":"rw-r--r--","remoteDirectory":"upload","size":122}, kafka_messageKey=test.xml, file_remoteDirectory=upload, kafka_recordMetadata=test-0#298, file_renameTo=upload/processed/test.xml, id=708d04c4-5abc-9f45-e83b-1fea7ffa5e8d, closeableResource=org.springframework.integration.file.remote.session.CachingSessionFactory$CachedSession#2e0aa05, file_remoteFile=test.xml, timestamp=1674556856288}]"
PS. I have try using debugger to see what wrong and found that it seem to raise an error at
private String obtainRemoteFilePath(Message<?> requestMessage) {
Error here----> String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
Assert.state(remoteFilePath != null,
() -> "The 'fileNameProcessor' evaluated to null 'remoteFilePath' from message: " + requestMessage);
return remoteFilePath;
}
"class com.demo.sftp.models.Note cannot be cast to class java.lang.String (com.demo.sftp.models.Note is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader #387f9ed2; java.lang.String is in module java.base of loader 'bootstrap')"
EDIT 1: Add solution
need to define an InboundChannelAdapter as PublishSubscribeChannel like
#Bean
public MessageChannel streamChannel() {
return new PublishSubscribeChannel();
}
Then add OutboundGateway as the LOWEST_PRECEDENCE order like
#Bean
#Order(Ordered.LOWEST_PRECEDENCE)
#ServiceActivator(inputChannel = "streamChannel")
public MessageHandler moveFile() {
SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpSessionFactory(), Command.MV.getCommand(),
"headers['file_remoteDirectory'] + '/' + headers['file_remoteFile']");
sftpOutboundGateway
.setRenameExpressionString(
"headers['file_remoteDirectory'] + '/processed/' +headers['timestamp'] + '-' + headers['file_remoteFile']");
sftpOutboundGateway.setRequiresReply(false);
sftpOutboundGateway.setUseTemporaryFileName(true);
sftpOutboundGateway.setOutputChannelName("nullChannel");
sftpOutboundGateway.setOrder(Ordered.LOWEST_PRECEDENCE);
sftpOutboundGateway.setAsync(true);
return sftpOutboundGateway;
}
Extra: in case we want to move the file that have error(from errorChannel). We need to change setRenameExpressionString to match the ErrorMessage structure. Eg.
#Bean
#Order(Ordered.LOWEST_PRECEDENCE)
#ServiceActivator(inputChannel = IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME)
public MessageHandler moveErrorFile() {
SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpSessionFactory(), Command.MV.getCommand(),
"payload['failedMessage']['headers']['file_remoteDirectory'] + '/' + payload['failedMessage']['headers']['file_remoteFile']");
sftpOutboundGateway
.setRenameExpressionString(
"payload['failedMessage']['headers']['file_remoteDirectory'] + '/error/' + payload['failedMessage']['headers']['timestamp'] + '-' + payload['failedMessage']['headers']['file_remoteFile']");
sftpOutboundGateway.setRequiresReply(false);
sftpOutboundGateway.setUseTemporaryFileName(true);
sftpOutboundGateway.setOutputChannelName("nullChannel");
sftpOutboundGateway.setOrder(Ordered.HIGHEST_PRECEDENCE);
sftpOutboundGateway.setAsync(true);
return sftpOutboundGateway;
}
As artem-bilan answer in Spring integration - SFTP rename or move file in remote server after copying
I can solve the issue by change my InboundChannelAdapter to PublishSubscribeChannel and create new subscriber like below
#Bean
#Order(Ordered.LOWEST_PRECEDENCE)
#ServiceActivator(inputChannel = "streamChannel")
public MessageHandler moveFile() {
SftpOutboundGateway sftpOutboundGateway = new SftpOutboundGateway(sftpSessionFactory(), Command.MV.getCommand(),
"headers['file_remoteDirectory'] + '/' + headers['file_remoteFile']");
sftpOutboundGateway
.setRenameExpressionString("headers['file_remoteDirectory'] + '/processed/' +headers['timestamp'] + '-' + headers['file_remoteFile']");
sftpOutboundGateway.setRequiresReply(false);
sftpOutboundGateway.setUseTemporaryFileName(true);
sftpOutboundGateway.setOutputChannelName("nullChannel");
sftpOutboundGateway.setOrder(Ordered.LOWEST_PRECEDENCE);
sftpOutboundGateway.setAsync(true);
return sftpOutboundGateway;
}
When you handle an ErrorMessage, you should follow a bit different path to get access to the original message and its headers.
We are going to mitigate this inconvenience in the next version: https://github.com/spring-projects/spring-integration/issues/3984
Related
I'm trying to write files to smb folder, it works fine for the first write but when the same file is written again it throws error
Caused by: org.springframework.core.NestedIOException: Failed to rename [/remote-target-dir/smbTest.test.writing] to [/remote-target-dir/smbTest.test].; nested exception is jcifs.smb.SmbException: Cannot create a file when that file already exists.
Im' using the FileTransferringMessageHandler which has FileExistsMode.REPLACE as default file exists mode but still it fails to replace.
I debugged into the SmbSession class, when flow reaches to this code in rename(String _pathFrom, String _pathTo) method
if (this.smbShare.isReplaceFile() && smbFileTo.exists()) {
smbFileTo.delete();
}
I see that the smbShare's isReplaceFile() is false, which is why I assume it is unable to delete the previous file with the same name
Here is the full code:
Connection:
#Bean
public SmbSessionFactory smbSessionFactory() {
SmbSessionFactory smbSession = new SmbSessionFactory();
smbSession.setHost("localhost");
smbSession.setPort(445);
smbSession.setDomain("DESKTOP-07O79IT");
smbSession.setUsername("Dell");
smbSession.setPassword("changeIt");
smbSession.setShareAndDir("Users\\DELL\\Desktop\\Shared");
smbSession.setSmbMinVersion(DialectVersion.SMB210);
smbSession.setSmbMaxVersion(DialectVersion.SMB311);
return smbSession;
}
Outbound to send files to smb:
#ServiceActivator(inputChannel = "storeToSmb")
#Bean
public MessageHandler smbMessageHandler(SmbSessionFactory smbSessionFactory) {
FileTransferringMessageHandler<SmbFile> handler =
new FileTransferringMessageHandler<>(smbSessionFactory);
handler.setRemoteDirectoryExpression(
new LiteralExpression("/remote-target-dir"));
handler.setFileNameGenerator(m -> "smbTest.test");
handler.setAutoCreateDirectory(true);
return handler;
}
Gateway:
#Override
public void run(ApplicationArguments args) throws Exception {
File file = new File("smbFile.txt");
try(FileWriter writer = new FileWriter(file)){
writer.write("This is sample smb write");
messageGateway.storeToSmb(file);
}catch (IOException e){
System.out.println("Error: "+e);
}
}
Can you please help with this issue?
The SmbSessionFactory must come with the setReplaceFile(true). The FileTransferringMessageHandler does nothing about its dependency and can't mutate it although it has its own logic about that FileExistsMode.REPLACE.
How can i get response if file is uploaded succesfuly to sftp? This is the code which i have
#Bean
public SessionFactory<LsEntry> axisSftpSessionFactory() {
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true);
factory.setHost(axisSftpProperties.getSftpHost());
factory.setPort(axisSftpProperties.getSftpPort());
factory.setUser(axisSftpProperties.getSftpUser());
factory.setPassword(axisSftpProperties.getSftpPassword());
factory.setAllowUnknownKeys(true);
return new CachingSessionFactory<>(factory);
}
/**
* Handler message handler.
*
* #return the message handler
*/
#Bean
#ServiceActivator(inputChannel = TO_SFTP_CHANNEL)
public MessageHandler handler() {
SftpMessageHandler handler = new SftpMessageHandler(axisSftpSessionFactory());
handler.setRemoteDirectoryExpression(new LiteralExpression(axisSftpProperties.getSftpRemoteDirectory()));
handler.setFileNameGenerator(message -> (String) message.getHeaders().get(FILENAME));
return handler;
}
#Component
#MessagingGateway
public interface UploadGateway {
#Gateway(requestChannel = TO_SFTP_CHANNEL)
String upload(#Header(FILENAME) String filename, #Payload byte[] bytes);
}
And the idea here is to catch any error if the file is not successfully uploaded to sftp to be able to retry it.
If i use SftpOutboundGateway how can setup a remote directory path?
SftpOutboundGateway gateway = new SftpOutboundGateway(sessionFactory(), "put", "payload");
See the documentation:
The message payload resulting from a put operation is a String that contains the full path of the file on the server after transfer.
Since you have a void return, it is discarded.
So...
String upload(File file);
EDIT
When using the gateway, the third constructor argument is the expression for the file name; the remote directory is provided in the remote file template...
#Bean
public SftpRemoteFileTemplate template() {
SftpRemoteFileTemplate template = new SftpRemoteFileTemplate(sessionFactory());
template.setRemoteDirectoryExpression(new LiteralExpression("foo/test"));
return template;
}
and
new SftpOutboundGateway(template(). "put", "headers['file_name']")
and
System.out.println(gate.upload("foo.txt", "foo".getBytes()));
and
foo/test/foo.txt
I am reading the file from remote directory using SFTP. I am able to get file by stream using outbound gateway, and move it to archive folder even.
I am processing the data in file but if there is some issue in data then I am throwing an error. I do not want to rename the file if there is any error thrown while processing the data, how can I achieve that. It will be very helpful if I can get some good practices for having error handler while using spring integration.
.handle(Sftp.outboundGateway(sftpSessionFactory(), GET, "payload.remoteDirectory + payload.filename").options(STREAM).temporaryFileSuffix("_reading"))
.handle(readData(),c->c.advice(afterReading()))
.enrichHeaders(h -> h
.headerExpression(FileHeaders.RENAME_TO, "headers[file_remoteDirectory] + 'archive/' + headers[file_remoteFile]")
.headerExpression(FileHeaders.REMOTE_FILE, "headers[file_remoteFile]")
.header(FileHeaders.REMOTE_DIRECTORY, "headers[file_remoteDirectory]"))
.handle(Sftp.outboundGateway(sftpSessionFactory(), MV, "headers[file_remoteDirectory]+headers[file_remoteFile]").renameExpression("headers['file_renameTo']"))
.get();
#Bean
public ExpressionEvaluatingRequestHandlerAdvice afterReading() {
ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice();
advice.setSuccessChannelName("successReading.input");
advice.setOnSuccessExpressionString("payload + ' was successful streamed'");
advice.setFailureChannelName("failureReading.input");
advice.setOnFailureExpressionString("payload + ' was bad, with reason: ' + #exception.cause.message");
advice.setTrapException(true);
advice.setPropagateEvaluationFailures(true);
return advice;
}
#Bean
public IntegrationFlow successReading() {
return f -> f.log();
}
#Bean
public IntegrationFlow failureReading() {
return f -> f.log(ERROR);
}
public GenericHandler readData() {
return new GenericHandler() {
#Override
public Object handle(Object o, Map map) {
InputStream file = (InputStream) o;
String fileName = (String) map.get(REMOTE_FILE);
try {
// processing data
} catch (Exception e) {
return new SftpException(500, String.format("Error while processing the file %s because of Error: %s and reason %s", fileName, e.getMessage(), e.getCause()));
}
Closeable closeable = (Closeable) map.get(CLOSEABLE_RESOURCE);
if (closeable != null) {
try {
closeable.close();
file.close();
} catch (Exception e) {
logger.error(String.format("Session didn`t get closed after reading the stream data for file %s and error %s"), fileName, e.getMessage());
}
}
return map;
}
};
}
Updated
Add an ExpressionEvaluatingRequestHandlerAdvice to the .handler() endpoint .handle(readData(), e -> e.advice(...)).
The final supplied advice class is the o.s.i.handler.advice.ExpressionEvaluatingRequestHandlerAdvice. This advice is more general than the other two advices. It provides a mechanism to evaluate an expression on the original inbound message sent to the endpoint. Separate expressions are available to be evaluated, after either success or failure. Optionally, a message containing the evaluation result, together with the input message, can be sent to a message channel.
I want to add a watch on the remote machine for newly added CSV files or unread. Once files are identified read it according to their timestamp which will be there in the file name. The file will be read using streaming rather coping to the local machine. While the file is getting read, append _reading to the filename and append _read once the file is read. The file will be read over SFTP protocol and I am planning to use spring integration sftp. In case of error while reading file or data in the file is not as per expectation I want to move that file in sub-directory.
I have tried to poll the remote directory and reading once CSV file. Once read I am removing the file from the directory.
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-sftp</artifactId>
<version>5.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
Spring boot version 2.0.3.RELEASE
#Bean
public SessionFactory<ChannelSftp.LsEntry> sftpSessionFactory() {
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true);
factory.setHost(hostname);
factory.setPort(22);
factory.setUser(username);
factory.setPassword(password);
factory.setAllowUnknownKeys(true);
return new CachingSessionFactory<ChannelSftp.LsEntry>(factory);
}
#Bean
public MessageSource<InputStream> sftpMessageSource() {
SftpStreamingMessageSource messageSource = new SftpStreamingMessageSource(template());
messageSource.setRemoteDirectory(path);
messageSource.setFilter(compositeFilters());
return messageSource;
}
public CompositeFileListFilter compositeFilters() {
return new CompositeFileListFilter()
.addFilter(new SftpRegexPatternFileListFilter(".*csv"));
}
#Bean
public SftpRemoteFileTemplate template() {
return new SftpRemoteFileTemplate(sftpSessionFactory());
}
#Bean
public IntegrationFlow sftpOutboundListFlow() {
return IntegrationFlows.from(this.sftpMessageSource(), e -> e.poller(Pollers.fixedDelay(5, TimeUnit.SECONDS)))
.handle(Sftp.outboundGateway(template(), NLST, path).options(Option.RECURSIVE)))
.filter(compositeFilters())
.transform(sorter())
.split()
.handle(Sftp.outboundGateway(template(), GET, "headers['file_remoteDirectory'] + headers['file_remoteFile']").options(STREAM))
.transform(csvToPojoTransformer())
.handle(service())
.handle(Sftp.outboundGateway(template(), MV, "headers['file_remoteDirectory'] + headers['file_remoteFile'] + _read"))
.handle(after())
.get();
}
#Bean
public MessageHandler sorter() {
return new MessageHandler() {
#Override
public void handleMessage(Message<?> message) throws MessagingException {
List<String> fileNames = (List<String>) message.getPayload();
Collections.sort(fileNames);
}
};
}
#Bean
public MessageHandler csvToPojoTransformer() {
return new MessageHandler() {
#Override
public void handleMessage(Message<?> message) throws MessagingException {
InputStream streamData = (InputStream) message.getPayload();
convertStreamtoObject(streamData, Class.class);
}
};
}
public List<?> convertStreamtoObject(InputStream inputStream, Class clazz) {
HeaderColumnNameMappingStrategy ms = new HeaderColumnNameMappingStrategy();
ms.setType(clazz);
Reader reader = new InputStreamReader(inputStream);
CsvToBean cb = new CsvToBeanBuilder(reader)
.withType(clazz)
.withMappingStrategy(ms)
.withSkipLines(0)
.withSeparator('|')
.withThrowExceptions(true)
.build();
return cb.parse();
}
#Bean
public MessageHandler service() {
return new MessageHandler() {
#Override
public void handleMessage(Message<?> message) throws MessagingException {
List<Class> csvDataAsListOfPojo = List < Class > message.getPayload();
// use this
}
};
}
#Bean
public ExpressionEvaluatingRequestHandlerAdvice after() {
ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice();
advice.setSuccessChannelName("success.input");
advice.setOnSuccessExpressionString("payload + ' was successful'");
advice.setFailureChannelName("failure.input");
advice.setOnFailureExpressionString("payload + ' was bad, with reason: ' + #exception.cause.message");
advice.setTrapException(true);
return advice;
}
#Bean
public IntegrationFlow success() {
return f -> f.handle(System.out::println);
}
#Bean
public IntegrationFlow failure() {
return f -> f.handle(System.out::println);
}
Updated Code
For complex scenarios (list, move, fetch, remove, etc), you should use SFTP remote file gateways instead.
The SFTP outbound gateway provides a limited set of commands that let you interact with a remote SFTP server:
ls (list files)
nlst (list file names)
get (retrieve a file)
mget (retrieve multiple files)
rm (remove file(s))
mv (move and rename file)
put (send a file)
mput (send multiple files)
Or use the SftpRemoteFileTemplate directly from your code.
EDIT
In response to your comments; you need something like this
Inbound Channel Adapter (with poller) - returns directory name
LS Gateway
Filter (remove any files already fetched)
Transformer (sort the list)
Splitter
GET Gateway(stream option)
Transformer (csv to POJO)
Service (process POJO)
If you add
RM Gateway
after your service (to remove the remote file), you don't need the filter step.
You might find the Java DSL simpler to assemble this flow...
#Bean
public IntegrationFlow flow() {
return IntegrationFlows.from(() -> "some/dir", e -> e.poller(...))
.handle(...) // LS Gateway
.filter(...)
.transform(sorter())
.split
.handle(...) // GET Gateway
.transform(csvToPojoTransformer())
.handle(myService())
.get()
}
I am trying to make Spring Integration SFTP read files (.txt) from a remote server recursively from all subfolders. The remote folder is something like "/tmp/remoteFolder" and all subfolders are date folders like "/tmp/remoteFolder/20180830", "/tmp/remoteFolder/20170902".
This is the code that I have until now
#Bean
#InboundChannelAdapter(value = "sftpMgetInputChannel",
poller = #Poller(fixedDelay = "5000"))
public IntegrationFlow sftpMGetFlow() {
return IntegrationFlows.from("sftpMgetInputChannel")
.handleWithAdapter(h -> h.sftpGateway(this.sftpSessionFactory,
Command.MGET, "'/tmp/remoteDirectory/*'")
.options(Option.RECURSIVE)
.regexFileNameFilter("((\\d{8})|*\\.txt)")
.localDirectoryExpression("sftp-inbound" + "/" + "#remoteDirectory"))
.channel(remoteFileOutputChannel())
.get();
}
#Bean
public MessageChannel sftpMgetInboundChannel(){
return new DirectChannel();
}
#Bean
public PollableChannel remoteFileOutputChannel() {
return new QueueChannel();
}
How do I specify the root remote directory for sftp mget to be /tmp/remoteFolder? Why isn't this working? Why do I need to specifiy the output channel?
Update: Instead of calling channel(remoteFileOutputChannel()) I call a handler like this
#Bean
public MessageHandler messageHandler(){
return new MessageHandler() { ... }
}
Code updated:
#InboundChannelAdapter(value = "sftpMgetInputChannel",
poller = #Poller(fixedDelay = "5000"))
public String filesForMGET(){
return "'/tmp/input/remoteDirectory/*'";
}
#Bean
public IntegrationFlow sftpMGetFlow() {
return IntegrationFlows.from("sftpMgetInputChannel")
.handleWithAdapter(h -> h.sftpGateway(this.sftpSessionFactory,
Command.MGET, "payload")
.options(Option.RECURSIVE)
.regexFileNameFilter("((\\d{8})|*\\.txt)")
.localDirectoryExpression("'sftp-inbound/'" + "#remoteDirectory"))
.handler(messageHandler())
.get();
}
#Bean
public MessageChannel sftpMgetInboundChannel(){
return new DirectChannel();
}
#Bean
public MessageHandler messageHandler(){
return new MessageHandler() { ... }
}
With this updated code, I get the following error:
rg.springframework.core.NestedIOException: failed to read file; nested exception is 2: No such file
at org.springframework.integration.sftp.session.SftpSession.read(SftpSession.java:100)
at org.springframework.integration.file.remote.session.CachingSessionFactory$CachedSession.read(CachingSessionFactory.java:137)
at org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer.copyFileToLocalDirectory(AbstractInboundFileSynchronizer.java:176)
at org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer.synchronizeToLocalDirectory(AbstractInboundFileSynchronizer.java:138)
at org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizingMessageSource.receive(AbstractInboundFileSynchronizingMessageSource.java:144)
at org.springframework.integration.endpoint.SourcePollingChannelAdapter.doPoll(SourcePollingChannelAdapter.java:89)
at org.springframework.integration.endpoint.AbstractPollingEndpoint$1.call(AbstractPollingEndpoint.java:146)
at org.springframework.integration.endpoint.AbstractPollingEndpoint$1.call(AbstractPollingEndpoint.java:144)
at org.springframework.integration.endpoint.AbstractPollingEndpoint$Poller$1.run(AbstractPollingEndpoint.java:207)
at org.springframework.integration.util.ErrorHandlingTaskExecutor$1.run(ErrorHandlingTaskExecutor.java:52)
at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:48)
at org.springframework.integration.util.ErrorHandlingTaskExecutor.execute(ErrorHandlingTaskExecutor.java:49)
at org.springframework.integration.endpoint.AbstractPollingEndpoint$Poller.run(AbstractPollingEndpoint.java:202)
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:51)
at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:98)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:206)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:680)
Caused by: 2: No such file
at com.jcraft.jsch.ChannelSftp.throwStatusError(ChannelSftp.java:2289)
at com.jcraft.jsch.ChannelSftp._stat(ChannelSftp.java:1741)
at com.jcraft.jsch.ChannelSftp.get(ChannelSftp.java:1011)
at com.jcraft.jsch.ChannelSftp.get(ChannelSftp.java:986)
at org.springframework.integration.sftp.session.SftpSession.read(SftpSession.java:96)
... 22 more
With the expression set to payload (as was the case in your question before the edit), the message payload sent to the gateway should be /tmp/remoteFolder/* which internally is split into remote directory and remote filename (*).
Why do I need to specifiy the output channel?
The result of the MGET (list of retrieved files) needs to go somewhere.
EDIT
You misunderstond; you can't add the #InboundChannelAdapter annotation to the flow; you need something like this...
#InboundChannelAdapter(value = "sftpMgetInputChannel",
poller = #Poller(fixedDelay = "5000"))
public String filesForMGET() {
return "/tmp/remoteDirectory/";
}
#Bean
public IntegrationFlow sftpMGetFlow() {
return IntegrationFlows.from("sftpMgetInputChannel")
.handleWithAdapter(h -> h.sftpGateway(this.sftpSessionFactory,
Command.MGET, "payload")
.options(Option.RECURSIVE)
.regexFileNameFilter("((\\d{8})|*\\.txt)")
.localDirectoryExpression("sftp-inbound" + "/" + "#remoteDirectory"))
.channel(remoteFileOutputChannel())
.get();
}