高度な構成

DefaultFtpSessionFactory は、(Spring Integration 2.0 以降)Apache Commons ネット (英語) である基礎となるクライアント API を抽象化します。これにより、org.apache.commons.net.ftp.FTPClient の低レベルの構成の詳細から解放されます。いくつかの一般的なプロパティがセッションファクトリで公開されています(バージョン 4.0 以降、これには connectTimeoutdefaultTimeoutdataTimeout が含まれるようになりました)。ただし、より高度な構成(アクティブモードのポート範囲の設定など)を実現するために、低レベルの FTPClient 構成にアクセスする必要がある場合があります。そのために、AbstractFtpSessionFactory (すべての FTP セッションファクトリの基本クラス)は、次のリストに示す 2 つの後処理メソッドの形式でフックを公開します。

/**
 * Will handle additional initialization after client.connect() method was invoked,
 * but before any action on the client has been taken
 */
protected void postProcessClientAfterConnect(T t) throws IOException {
    // NOOP
}
/**
 * Will handle additional initialization before client.connect() method was invoked.
 */
protected void postProcessClientBeforeConnect(T client) throws IOException {
    // NOOP
}

ご覧のとおり、これら 2 つのメソッドにはデフォルトの実装はありません。ただし、次の例に示すように、DefaultFtpSessionFactory を継承することにより、これらのメソッドをオーバーライドして、FTPClient のより高度な構成を提供できます。

public class AdvancedFtpSessionFactory extends DefaultFtpSessionFactory {

    protected void postProcessClientBeforeConnect(FTPClient ftpClient) throws IOException {
       ftpClient.setActivePortRange(4000, 5000);
    }
}

FTPS および共有 SSLSession

FTP over SSL または TLS を使用する場合、一部のサーバーでは、制御接続とデータ接続で同じ SSLSession を使用する必要があります。これは、データ接続の「盗用」を防ぐためです。詳細については、scarybeastsecurity.blogspot.cz/2009/02/vsftpd-210-released.html (英語) を参照してください。

現在、Apache FTPSClient はこの機能をサポートしていません。NET-408 [Apache] (英語) を参照してください。

次のソリューションは、Stack Overflow (英語) の厚意により、sun.security.ssl.SSLSessionContextImpl でリフレクションを使用するため、他の JVM では機能しない可能性があります。スタックオーバーフローの回答は 2015 年に提出され、ソリューションは JDK 1.8.0_112 で Spring Integration チームによってテストされました。

次の例は、FTPS セッションを作成する方法を示しています。

@Bean
public DefaultFtpsSessionFactory sf() {
    DefaultFtpsSessionFactory sf = new DefaultFtpsSessionFactory() {

        @Override
        protected FTPSClient createClientInstance() {
            return new SharedSSLFTPSClient();
        }

    };
    sf.setHost("...");
    sf.setPort(21);
    sf.setUsername("...");
    sf.setPassword("...");
    sf.setNeedClientAuth(true);
    return sf;
}

private static final class SharedSSLFTPSClient extends FTPSClient {

    @Override
    protected void _prepareDataSocket_(final Socket socket) throws IOException {
        if (socket instanceof SSLSocket) {
            // Control socket is SSL
            final SSLSession session = ((SSLSocket) _socket_).getSession();
            final SSLSessionContext context = session.getSessionContext();
            context.setSessionCacheSize(0); // you might want to limit the cache
            try {
                final Field sessionHostPortCache = context.getClass()
                        .getDeclaredField("sessionHostPortCache");
                sessionHostPortCache.setAccessible(true);
                final Object cache = sessionHostPortCache.get(context);
                final Method method = cache.getClass().getDeclaredMethod("put", Object.class,
                        Object.class);
                method.setAccessible(true);
                String key = String.format("%s:%s", socket.getInetAddress().getHostName(),
                        String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT);
                method.invoke(cache, key, session);
                key = String.format("%s:%s", socket.getInetAddress().getHostAddress(),
                        String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT);
                method.invoke(cache, key, session);
            }
            catch (NoSuchFieldException e) {
                // Not running in expected JRE
                logger.warn("No field sessionHostPortCache in SSLSessionContext", e);
            }
            catch (Exception e) {
                // Not running in expected JRE
                logger.warn(e.getMessage());
            }
        }

    }

}