In the last years I’ve done everything I could to avoid using FTP as a mechanism to pass data from one procedure to the other inside our company.

Tools such Spring Integration and RabbitMQ helped me a lot in this process (making my life so much better)

However we still have to use FTP as exchange mechanism with several external partners.

Spring Integration has a wonderful FTP module that covers all of my needs, however I found a case which required me some googling to shed light on.

I had this scenario: a partner produces a file every n minutes and store in its own FTP server, with a never changing file name. I regularly check the FTP, get the file locally and put it in a RabbitMQ exchange.

So, basically, I have this kind of configuration (not the actual configuration, just an example):

<int-ftp:inbound-channel-adapter id="ftpChannel"
        channel="toRabbitChannel"
        session-factory="ftpSessionFactory"
        charset="UTF-8"
        auto-create-local-directory="true"
        delete-remote-files="true"
        filename-pattern="foo.csv"
        remote-directory="/tmp/source"
        remote-file-separator="/"
        local-directory="/tmp/local_storage">
        <int:poller fixed-rate="1000"/>
</int-ftp:inbound-channel-adapter>

<int-amqp:outbound-channel-adapter
        id="outboundToRabbit"
        channel="toRabbitChannel"
        exchange-name="test.exchange"
        amqp-template="amqpTemplate"
        routing-key="">

     <int-amqp:request-handler-advice-chain>
        <bean class="org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice">
             <property name="onSuccessExpression" value="payload.delete()"/>
        </bean>
    </int-amqp:request-handler-advice-chain>

</int-amqp:outbound-channel-adapter>

this works nicely, with an important exception: if, for any reason, the outbound gateway cannot deliver the file to RabbitMQ (for example if the server is unreacheable, or restarting, or in maintenance) then this is what happens (suppose that the interesting file is called foo.csv):

  1. the file foo.csv is moved from remote server to the local filesystem
  2. the files is passed from the FTP inbound to the RabbitMQ outbound
  3. the FTP inbound marks the file as “processed”;
  4. the RabbitMQ outbound fails to deliver the message to the broker (so it doesn’t delete the local file)

this seems the wanted behaviour (and, it is!), anyway, the problems arises at the next polling cycle:

  1. the FTP inbound sees that he already have a local file named foo.csv, so he won’t dowload the new one from the remote server
  2. anyway, it doesn’t either process the local foo.csv because he marked it as already “processed”;

at this point, or chain is stuck and won’t start working again unless we restart our application.

The solution comes from the documentation (as always!)

From that page you can learn that the FTP inbound uses a FileListFilter to decide which local file process. Such filter can be changed setting the local-filter attribute. The default  filter is an AcceptOnceFileListFilter, which, as stated by its name, accept a file only once.

The list of accepted files is stored in memory, and that’s why restarting the application makes our foo.csv file processed again (of course, in case you need it, there are solutions to it persistent)

Going back to our problem, the solution is to set the filter to an instance of the class AcceptAllFileListFilter.


<bean id="acceptAllFiles" class="org.springframework.integration.file.filters.AcceptAllFileListFilter"/>

<int-ftp:inbound-channel-adapter id="ftpChannel"
        channel="toRabbitChannel"
        session-factory="ftpSessionFactory"
        charset="UTF-8"
        auto-create-local-directory="true"
        delete-remote-files="true"
        filename-pattern="*"
        remote-directory="/tmp/source"
        remote-file-separator="/"
        local-filter="acceptAllFiles"
        local-directory="/tmp/local_storage">
        <int:poller fixed-rate="1000"/>
</int-ftp:inbound-channel-adapter>

With this configuration in place, the remote foo.dat file won’t be downloaded unless the local copy has been deleted, and the local copy will be deleted after the first polling cycle which can succesfully deliver its content to the RabbitMQ broker.

UPDATE: I noticed that if the new file on the remote FTP appears in the same hour and minute of the file previously downloaded, it won’t be recognized as a new file and hence not downloaded.


visitors