Search code examples
javaspringspring-integrationspring-integration-sftp

Is it possible to dynamically change the filename-regex of inbound-channel-adapter


In our application there are huge number of files downloaded from remote machine to local machine (server where code is running). We have opted to use Spring SFTP to do the download. We are still in the process of development.

The download process is initiated when the user selects a file clicks on a button in the UI to download that file. At a time multiple users might be selecting different files and downloading them from remote machine to local machine (server where code is running). Remote machine (and path from where download would happen) and local machine (and path where file would be downloaded) is same for all download requests just the file name would be different.

In my code below, I am setting filename-regex in int-sftp:inbound-channel-adapter. Problem is filename-regex is static. I need to set filename-regex dynamically. Because each user will be downloading a different file. I cannot use regex in filename-regex, as only the selected file needs to be downloaded.

Is it possible set it dynamically. What changes would I have to do to my code to be able to do that. All suggestions are welcome. Thanks in advance.

<bean id="sftpSessionFactory" class="org.springframework.integration.file.remote.session.CachingSessionFactory">
    <constructor-arg ref="defaultSftpSessionFactory" />
</bean>

<bean id="defaultSftpSessionFactory"
      class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
    <property name="host" value="${sftp.host}"/>
    <property name="privateKey" value="${sftp.private.keyfile}"/>
    <property name="privateKeyPassphrase" value="${sftp.passphrase}"/>
    <property name="port" value="${sftp.port}"/>
    <property name="user" value="${sftp.username}"/>
    <property name="allowUnknownKeys" value="true"/>
</bean>

<bean class="com.rizwan.test.sftp_inbound_channel_adapter.EmbeddedSftpServer">
    <property name="port" value="${sftp.port}"/>
    <property name="defaultSftpSessionFactory" ref="defaultSftpSessionFactory"/>
</bean>

<int-sftp:inbound-channel-adapter id="sftpInbondAdapter"
        auto-startup="false"
        channel="receiveChannel"
        session-factory="sftpSessionFactory"
        local-directory="file:local-dir"
        remote-directory="si.sftp.sample"
        auto-create-local-directory="true"
        delete-remote-files="false"
        filename-regex="a.txt">
    <int:poller fixed-rate="0" max-messages-per-poll="-1"/>
</int-sftp:inbound-channel-adapter>

<int:channel id="receiveChannel">
    <int:queue/>
</int:channel>

Below is my java code inside main method.

PollableChannel localFileChannel = context.getBean("receiveChannel", PollableChannel.class);
SourcePollingChannelAdapter adapter = context.getBean(SourcePollingChannelAdapter.class);
adapter.start();
adapter.stop();
Message<?> received = localFileChannel.receive();

Have used this link for my reference - https://github.com/spring-projects/spring-integration-samples/tree/master/basic/sftp


Posting the configuration to make this work based on the answer given by Artem Bilan.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-sftp="http://www.springframework.org/schema/integration/sftp"
xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/integration/sftp http://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd">

<import resource="SftpSampleCommon.xml"/>

<int:gateway id="gw" service-interface="com.rizwan.test.sftp_outbound_gateway.ToSftpFlowGateway"
    default-request-channel="toGet"/>

<int-sftp:outbound-gateway id="gatewayGET"
    local-directory="C:\Users\503017993\Perforce\rizwan.shaikh1_G7LGTPC2E_7419\NGI\DEV\Jetstream_Branches\C360_Falcon2_1_Main\sftp-outbound-gateway"
    session-factory="sftpSessionFactory"
    request-channel="toGet"
    remote-directory="/si.sftp.sample"
    command="get"
    command-options="-P"
    expression="payload">
    <int-sftp:request-handler-advice-chain>
        <int:retry-advice />
    </int-sftp:request-handler-advice-chain>
</int-sftp:outbound-gateway>
</beans>

Java code:

ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(
            "classpath:/META-INF/spring-context.xml");
DownloadRemoteFileGateway downloadGateway = ctx.getBean(DownloadRemoteFileGateway.class);
String downloadedFilePath = downloadGateway.downloadRemoteFile("si.sftp.sample/2ftptest");

Solution

  • The approach you are pursuing is absolutely wrong. You don't take into account the concurrent access to the component from different users. Now let's imaging that we would be able to change the filename-regex from end-user perspective. One of them wants to download his/her own file, so he/she sets an appropriate pattern and calls adapter.start(). At the same time another user would like to download his/her file. And voila - we have a race condition, because you are changing the state of the component concurrently when it doesn't work in the user scope. The nature of the SourcePollingChannelAdapter to be active. Once you start it the background scheduled task will spin this component until stop.

    Another bottleneck in your solution is an access to the PollableChannel. Let's imagine that we can make SourcePollingChannelAdapter to work in the user context and we don't have any concurrent mutations and race conditions. But when this component downloads files it places messages to that PollableChannel. Is there guarantee that one user won't pull from this queue files for another user?..

    What you need for your solution should be passive and stateless. And this indeed has to be initiated by end-user and return some answer in the same thread. For this purpose you should take a look into the <int-sftp:outbound-gateway> and its get or mget commands. Exactly this one is able to get a file name/pattern from the Message you send, downloads it from the SFTP and returns it to the caller. You can place @MessagingGateway in front of that SFTP gateway and have just plain Java code from the MVC Controller.

    See Reference Manual for more information. The sample you mention in your questions brings some gateway snippet as well.