Using DelegatingSessionFactory with RemoteFileTemplate.execute(SessionCallback)

Viewed 197

I'm trying to declare multiple SFTP sessions, wrap them in a DelegatingSessionFactory, then later use SftpRemoteFileTemplate.execute(...) during a cron job.

On the execute part of things, the code is very simple, it is already used for a single session, but I want to expand it to multiple possible sessions.

Below I extended my single session code. I just copied the methods for reference. At the end I'll show how I think the new methods should look.

public class XSession extends SftpSession {

    @Scheduled(cron = "${sftp.scan.x.schedule}")
    void scan() {
        List<FileHistoryEntity> fileList = template.execute(this::processFiles);
        ...
    }
    
    private List<FileHistoryEntity> processFiles(Session<ChannelSftp.LsEntry> session) {
        List.of(session.list(this.remoteDir)).forEach(file -> doWhatever());
        ...
    }
}

But now I have multiple sessions. So I declare the following class:

@Slf4j
@Configuration
@RequiredArgsConstructor
public class DelegateSftpSessionHandler {

    private final SessionFactory<ChannelSftp.LsEntry> session1;
    private final SessionFactory<ChannelSftp.LsEntry> session2;
    private final SessionFactory<ChannelSftp.LsEntry> session3;
    private final SessionFactory<ChannelSftp.LsEntry> session4;
    private final SessionFactory<ChannelSftp.LsEntry> session5;
    
    @RequiredArgsConstructor
    public enum DelegateSessionConfig {
        SESSION_1("IN_REALITY_A_RELEVANT_NAME_1");
        SESSION_2("IN_REALITY_A_RELEVANT_NAME_2");
        SESSION_3("IN_REALITY_A_RELEVANT_NAME_3");
        SESSION_4("IN_REALITY_A_RELEVANT_NAME_4");
        SESSION_5("IN_REALITY_A_RELEVANT_NAME_5");

        public final String threadKey;
    }

    @Bean
    @Primary
    public DelegatingSessionFactory<ChannelSftp.LsEntry> delegatingSessionFactory() {
        Map<Object, SessionFactory<ChannelSftp.LsEntry>> sessionMap = new HashMap<>();
        sessionMap.put(DelegateSessionConfig.SESSION_1.threadKey, session1);
        sessionMap.put(DelegateSessionConfig.SESSION_2.threadKey, session2);
        sessionMap.put(DelegateSessionConfig.SESSION_3.threadKey, session3);
        sessionMap.put(DelegateSessionConfig.SESSION_4.threadKey, session4);
        sessionMap.put(DelegateSessionConfig.SESSION_5.threadKey, session5);

        DefaultSessionFactoryLocator<ChannelSftp.LsEntry> sessionLocator = new DefaultSessionFactoryLocator<>(sessionMap);
        return new DelegatingSessionFactory<>(sessionLocator);
    }

    @Bean
    SftpRemoteFileTemplate ftpRemoteFileTemplate(DelegatingSessionFactory<ChannelSftp.LsEntry> dsf) {
        return new SftpRemoteFileTemplate(dsf);
    }
}

Ting is, I have no idea how any of this works, and the spring sftp / fpt documentation is by no means clear. The code is virtually undocumented. And I'm just guessing. I think that I have to do the following:

public class XSession extends SftpSession {

    @Autowire
    DelegatingSessionFactory<ChannelSftp.LsEntry> delegatingSessionFactory;
    
    @Autowired
    SftpRemoteFileTemplate template;

    @Scheduled(cron = "${sftp.scan.x.schedule}") // x == SESSION_1
    @Async // for thread key
    void scan() {
        delegatingSessionFactory.setThreadKey(DelegateSessionConfig.SESSION_1.threadKey);
        
        // because thread key changes the session globally? So I don't need specify
        // which session this template is working with???
        List<FileHistoryEntity> fileList = template.execute(this::processFiles);
       
        ...
       
        delegatingSessionFactory.clearThreadKey();
    }
    
    private List<FileHistoryEntity> processFiles(Session<ChannelSftp.LsEntry> session) {
        List.of(session.list(this.remoteDir)).forEach(file -> doWhatever());
        ...
    }
}

I'm basing what I'm saying on the following link, github spring integration test

Honestly, I hardly understand what is happening. But it seems like setting the thread key, changes the session globally.

My only other idea is to just ... create the RemoteFileTemplate on demand

public static SftpRemoteFileTemplate getTemplateFor(DelegatingSessionFactory<ChannelSftp.LsEntry> dsf, DelegateSessionConfig session) {
    return new SftpRemoteFileTemplate(dsf.getFactoryLocator().getSessionFactory(session.threadKey));
}
1 Answers

It does not set it globally. That's how a ThreadLocal variable works: you set a value in some thread and only this thread can see it. If you use the same object concurrently, other threads don't see that value because it does not belong to their thread state.

Not sure what is your concern, but pattern to extend an SftpSession for custom logic is not right. You should consider to use an SftpRemoteFileTemplate.execute(SessionCallback<F, T> callback) instead, but thread key must be set into a DelegatingSessionFactory before anyway and in the same thread you going to call that execute().

Related