Search code examples
spring-bootapache-minaspring-integration-sftp

Determining when FileWrittenEvent has completed writing the entire file


This is my first question here so please bear with me.
In a recent release of Spring 5.2 there were certain and extremely helpful components added to Spring Integration as seen in this link:
https://docs.spring.io/spring-integration/reference/html/sftp.html#sftp-server-events
Apache MINA was integrated with a new listener "ApacheMinaSftpEventListener" which

listens for certain Apache Mina SFTP server events and publishes them as ApplicationEvents

So far my application can capture the application events as noted in the documentation from the link provided but I can't seem to figure out when the event finishes... if that makes sense (probably not).

In a process flow the application starts up and activates as an SFTP Server on a specified port.
I can use the user name and password to connect to and "put" a file on the system which initiates the transfer.
When I sign on I can capture the "SessionOpenedEvent"
When I transfer a file I can capture the "FileWrittenEvent"
When I sign off or break the connection I can capture the "SessionClosedEvent"
When the file is a larger size I can capture ALL of the "FileWrittenEvent" events which tells me the transfer occurs on a stream of a predetermined or calculated sized buffer.

What I'm trying to determine is "How can I find out when that stream is finished". This will help me answer "As an SFTP Server accepting a file, when can I access the completed file?"

My Listener bean (which is attached to Apache Mina on start up via the SubSystemFactory)

@Configuration
public class SftpConfiguration {
    @Bean
    public ApacheMinaSftpEventListener apacheMinaSftpEventListener() {
        return new ApacheMinaSftpEventListener();
    }   
}
SftpSubsystemFactory subSystem = new SftpSubsystemFactory();
subSystem.addSftpEventListener(listener);

My Event Listener: this is here so I can see some output in a logger which is when I realized, on a few GB file, the FileWrittenEvent went a little crazy.

@Async
@EventListener
public void sftpEventListener(ApacheMinaSftpEvent sftpEvent) {
    log.info("Capturing Event: ", sftpEvent.getClass().getSimpleName());
    log.info("Event Details: ", sftpEvent.toString());
}

These few pieces were all I really needed to start capturing the events

I was thinking that I would need to override a method to help me capture when the stream finishes so I can move on with my business logic but I'm not sure which one.
I seem to be able to access the file (read/write) prior to the stream being done so I don't seem to be able to use logic that attempts to "move" the file and wait for it to throw an error, though that approach seemed like bad practice to me.

Any guidance would be greatly appreciated, thank you.

Versioning Information

  • Spring 5.2.3
  • Spring Boot 2.2.3
  • Apache Mina 2.1.3
  • Java 1.8

Solution

  • This may not be helpful for others but I've found a way around my initial problem by integrating a related solution combined with the new Apache MINA classes found in this answer:
    https://stackoverflow.com/a/45513680/12806809
    My solution:
    Create a class that extends the new ApacheMinaSftpEventListener while overridding the 'open' and 'close' methods to ensure my SFTP Server business logic know when a file is done writing.

    public class WatcherSftpEventListener extends ApacheMinaSftpEventListener {
        ...
        ...
        @Override public void open(ServerSession session, String remoteHandle, Handle localHandle) throws IOException {
            File file = localHandle.getFile().toFile();
            if (file.isFile() && file.exists()) {
                log.debug("File Open: {}", file.toString());
            }
            // Keep around the super call for now
            super.open(session, remoteHandle, localHandle);
        }
    
        @Override
        public void close(ServerSession session, String remoteHandle, Handle localHandle) {
            File file = localHandle.getFile().toFile();
            if (file.isFile() && file.exists()) {
                log.debug("RemoteHandle: {}", remoteHandle);
                log.debug("File Closed: {}", file.toString());
                for (SftpFileUploadCompleteListener listener : fileReadyListeners) {
                    try {
                        listener.onFileReady(file);
                    } catch (Exception e) {
                        String msg = String.format("File '%s' caused an error in processing '%s'", file.getName(), e.getMessage());
                        log.error(msg);
                        try {
                            session.disconnect(0, msg);
                        } catch (IOException io) {
                            log.error("Could not properly disconnect from session {}; closing future state", session);
                            session.close(false);
                        }
                    }
                }
            }
            // Keep around the super call for now
            super.close(session, remoteHandle, localHandle);
        }
    
    }
    

    When I start the SSHD Server I added my new listener bean to the SftpSubsystemFactory which uses a customized event handler class to apply my business logic against the incoming files.

            watcherSftpEventListener.addFileReadyListener(new SftpFileUploadCompleteListener() {
                @Override
                public void onFileReady(File file) throws Exception {
                    new WatcherSftpEventHandler(file, properties.getSftphost());
                }
            });
            subSystem.addSftpEventListener(watcherSftpEventListener);
    

    There was a bit more to this solution but since this question isn't getting that much traffic and it's more for my reference and learning than anything now, I won't provide anything more unless asked.