GH-3572: Migrate SFTP from jsch to sshd-sftp (#3892)
* GH-3572: Migrate SFTP from `jsch` to `sshd-sftp` Fixes https://github.com/spring-projects/spring-integration/issues/3572 * Rework SFTP module from the JSch API to more modern `sshd-sftp` * Migrate generics of most API from `ChannelSftp.LsEntry` to the `SftpClient.DirEntry` * Rework `DefaultSftpSessionFactory` to deal with an `SshClient` and create `SftpClient` wrapped to the `SftpSession` * Implement a `ResourceKnownHostsServerKeyVerifier` to load `known-hosts` from any possible resource * Implement an expected `SftpSession.list()` with just file name to take or pattern matching * Remove some unused tests and their config * Remove tests for custom `UserInfo` since we don't provide any custom out-of-the-box * Test a new `ResourceKnownHostsServerKeyVerifier` against default `known-hosts` file * * Some tests improvements * * Improve generics handling for `FileUtils`
This commit is contained in:
@@ -76,7 +76,6 @@ ext {
|
||||
jmsApiVersion = '3.0.0'
|
||||
jpaApiVersion = '3.0.3'
|
||||
jrubyVersion = '9.3.8.0'
|
||||
jschVersion = '0.1.55'
|
||||
jsonpathVersion = '2.7.0'
|
||||
junit4Version = '4.13.2'
|
||||
junitJupiterVersion = '5.9.0'
|
||||
@@ -900,13 +899,11 @@ project('spring-integration-sftp') {
|
||||
description = 'Spring Integration SFTP Support'
|
||||
dependencies {
|
||||
api project(':spring-integration-file')
|
||||
api "com.jcraft:jsch:$jschVersion"
|
||||
api 'org.springframework:spring-context-support'
|
||||
optionalApi ("org.apache.sshd:sshd-sftp:$apacheSshdVersion") {
|
||||
api ("org.apache.sshd:sshd-sftp:$apacheSshdVersion") {
|
||||
exclude group: 'org.slf4j', module: 'jcl-over-slf4j'
|
||||
}
|
||||
|
||||
testImplementation "org.apache.sshd:sshd-core:$apacheSshdVersion"
|
||||
testImplementation project(':spring-integration-event')
|
||||
testImplementation project(':spring-integration-file').sourceSets.test.output
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-2020 the original author or authors.
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -290,7 +290,7 @@ public abstract class RemoteFileInboundChannelAdapterSpec<F, S extends RemoteFil
|
||||
* @return the spec.
|
||||
* @since 5.2.9
|
||||
*/
|
||||
public S remoteComparator(Comparator<F> remoteComparator) {
|
||||
public S remoteComparator(Comparator<? extends F> remoteComparator) {
|
||||
this.synchronizer.setComparator(remoteComparator);
|
||||
return _this();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-2021 the original author or authors.
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -62,11 +62,11 @@ import org.springframework.util.ObjectUtils;
|
||||
public abstract class AbstractRemoteFileStreamingMessageSource<F>
|
||||
extends AbstractFetchLimitingMessageSource<InputStream> implements ManageableLifecycle {
|
||||
|
||||
private final RemoteFileTemplate<F> remoteFileTemplate;
|
||||
private final RemoteFileTemplate<? extends F> remoteFileTemplate;
|
||||
|
||||
private final BlockingQueue<AbstractFileInfo<F>> toBeReceived = new LinkedBlockingQueue<>();
|
||||
|
||||
private final Comparator<F> comparator;
|
||||
private final Comparator<? extends F> comparator;
|
||||
|
||||
private final AtomicBoolean running = new AtomicBoolean();
|
||||
|
||||
@@ -86,8 +86,8 @@ public abstract class AbstractRemoteFileStreamingMessageSource<F>
|
||||
*/
|
||||
private FileListFilter<F> filter;
|
||||
|
||||
protected AbstractRemoteFileStreamingMessageSource(RemoteFileTemplate<F> template,
|
||||
@Nullable Comparator<F> comparator) {
|
||||
protected AbstractRemoteFileStreamingMessageSource(RemoteFileTemplate<? extends F> template,
|
||||
@Nullable Comparator<? extends F> comparator) {
|
||||
|
||||
Assert.notNull(template, "'template' must not be null");
|
||||
this.remoteFileTemplate = template;
|
||||
@@ -143,7 +143,7 @@ public abstract class AbstractRemoteFileStreamingMessageSource<F>
|
||||
this.fileInfoJson = fileInfoJson;
|
||||
}
|
||||
|
||||
protected RemoteFileTemplate<F> getRemoteFileTemplate() {
|
||||
protected RemoteFileTemplate<? extends F> getRemoteFileTemplate() {
|
||||
return this.remoteFileTemplate;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -123,7 +123,7 @@ public abstract class AbstractInboundFileSynchronizer<F>
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
@Nullable
|
||||
private Comparator<F> comparator;
|
||||
private Comparator<? extends F> comparator;
|
||||
|
||||
private MetadataStore remoteFileMetadataStore = new SimpleMetadataStore();
|
||||
|
||||
@@ -141,7 +141,7 @@ public abstract class AbstractInboundFileSynchronizer<F>
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Comparator<F> getComparator() {
|
||||
protected Comparator<? extends F> getComparator() {
|
||||
return this.comparator;
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ public abstract class AbstractInboundFileSynchronizer<F>
|
||||
* @param comparator the comparator.
|
||||
* @since 5.1
|
||||
*/
|
||||
public void setComparator(@Nullable Comparator<F> comparator) {
|
||||
public void setComparator(@Nullable Comparator<? extends F> comparator) {
|
||||
this.comparator = comparator;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,6 +29,7 @@ import org.springframework.util.ObjectUtils;
|
||||
* Utilities for operations on Files.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*
|
||||
@@ -47,8 +48,8 @@ public final class FileUtils {
|
||||
* @since 5.0.7
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <F> F[] purgeUnwantedElements(F[] fileArray, Predicate<F> predicate,
|
||||
@Nullable Comparator<F> comparator) {
|
||||
public static <F> F[] purgeUnwantedElements(F[] fileArray, Predicate<? extends F> predicate,
|
||||
@Nullable Comparator<? extends F> comparator) {
|
||||
|
||||
if (ObjectUtils.isEmpty(fileArray)) {
|
||||
return fileArray;
|
||||
@@ -56,13 +57,13 @@ public final class FileUtils {
|
||||
else {
|
||||
if (comparator == null) {
|
||||
return Arrays.stream(fileArray)
|
||||
.filter(predicate.negate())
|
||||
.filter((Predicate<? super F>) predicate.negate())
|
||||
.toArray(size -> (F[]) Array.newInstance(fileArray[0].getClass(), size));
|
||||
}
|
||||
else {
|
||||
return Arrays.stream(fileArray)
|
||||
.filter(predicate.negate())
|
||||
.sorted(comparator)
|
||||
.filter((Predicate<? super F>) predicate.negate())
|
||||
.sorted((Comparator<? super F>) comparator)
|
||||
.toArray(size -> (F[]) Array.newInstance(fileArray[0].getClass(), size));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ package org.springframework.integration.sftp.dsl;
|
||||
import java.io.File;
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.remote.MessageSessionCallback;
|
||||
import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
|
||||
@@ -27,9 +29,6 @@ import org.springframework.integration.file.support.FileExistsMode;
|
||||
import org.springframework.integration.sftp.gateway.SftpOutboundGateway;
|
||||
import org.springframework.integration.sftp.session.SftpRemoteFileTemplate;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* The factory for SFTP components.
|
||||
*
|
||||
@@ -46,7 +45,7 @@ public final class Sftp {
|
||||
* @param sessionFactory the session factory.
|
||||
* @return the spec.
|
||||
*/
|
||||
public static SftpInboundChannelAdapterSpec inboundAdapter(SessionFactory<ChannelSftp.LsEntry> sessionFactory) {
|
||||
public static SftpInboundChannelAdapterSpec inboundAdapter(SessionFactory<SftpClient.DirEntry> sessionFactory) {
|
||||
return inboundAdapter(sessionFactory, null);
|
||||
}
|
||||
|
||||
@@ -56,7 +55,7 @@ public final class Sftp {
|
||||
* @param receptionOrderComparator the comparator.
|
||||
* @return the spec.
|
||||
*/
|
||||
public static SftpInboundChannelAdapterSpec inboundAdapter(SessionFactory<ChannelSftp.LsEntry> sessionFactory,
|
||||
public static SftpInboundChannelAdapterSpec inboundAdapter(SessionFactory<SftpClient.DirEntry> sessionFactory,
|
||||
Comparator<File> receptionOrderComparator) {
|
||||
|
||||
return new SftpInboundChannelAdapterSpec(sessionFactory, receptionOrderComparator);
|
||||
@@ -69,7 +68,7 @@ public final class Sftp {
|
||||
* @return the spec.
|
||||
*/
|
||||
public static SftpStreamingInboundChannelAdapterSpec inboundStreamingAdapter(
|
||||
RemoteFileTemplate<LsEntry> remoteFileTemplate) {
|
||||
RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate) {
|
||||
|
||||
return inboundStreamingAdapter(remoteFileTemplate, null);
|
||||
}
|
||||
@@ -82,8 +81,8 @@ public final class Sftp {
|
||||
* @return the spec.
|
||||
*/
|
||||
public static SftpStreamingInboundChannelAdapterSpec inboundStreamingAdapter(
|
||||
RemoteFileTemplate<LsEntry> remoteFileTemplate,
|
||||
Comparator<LsEntry> receptionOrderComparator) {
|
||||
RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate,
|
||||
Comparator<SftpClient.DirEntry> receptionOrderComparator) {
|
||||
|
||||
return new SftpStreamingInboundChannelAdapterSpec(remoteFileTemplate, receptionOrderComparator);
|
||||
}
|
||||
@@ -93,7 +92,7 @@ public final class Sftp {
|
||||
* @param sessionFactory the session factory.
|
||||
* @return the spec.
|
||||
*/
|
||||
public static SftpMessageHandlerSpec outboundAdapter(SessionFactory<ChannelSftp.LsEntry> sessionFactory) {
|
||||
public static SftpMessageHandlerSpec outboundAdapter(SessionFactory<SftpClient.DirEntry> sessionFactory) {
|
||||
return new SftpMessageHandlerSpec(sessionFactory);
|
||||
}
|
||||
|
||||
@@ -103,7 +102,7 @@ public final class Sftp {
|
||||
* @param fileExistsMode the file exists mode.
|
||||
* @return the spec.
|
||||
*/
|
||||
public static SftpMessageHandlerSpec outboundAdapter(SessionFactory<ChannelSftp.LsEntry> sessionFactory,
|
||||
public static SftpMessageHandlerSpec outboundAdapter(SessionFactory<SftpClient.DirEntry> sessionFactory,
|
||||
FileExistsMode fileExistsMode) {
|
||||
|
||||
return outboundAdapter(new SftpRemoteFileTemplate(sessionFactory), fileExistsMode);
|
||||
@@ -141,7 +140,7 @@ public final class Sftp {
|
||||
* @param expression the remoteFilePath SpEL expression.
|
||||
* @return the {@link SftpOutboundGatewaySpec}
|
||||
*/
|
||||
public static SftpOutboundGatewaySpec outboundGateway(SessionFactory<ChannelSftp.LsEntry> sessionFactory,
|
||||
public static SftpOutboundGatewaySpec outboundGateway(SessionFactory<SftpClient.DirEntry> sessionFactory,
|
||||
AbstractRemoteFileOutboundGateway.Command command, String expression) {
|
||||
|
||||
return outboundGateway(sessionFactory, command.getCommand(), expression);
|
||||
@@ -157,7 +156,7 @@ public final class Sftp {
|
||||
* @return the {@link SftpOutboundGatewaySpec}
|
||||
* @see RemoteFileTemplate
|
||||
*/
|
||||
public static SftpOutboundGatewaySpec outboundGateway(SessionFactory<ChannelSftp.LsEntry> sessionFactory,
|
||||
public static SftpOutboundGatewaySpec outboundGateway(SessionFactory<SftpClient.DirEntry> sessionFactory,
|
||||
String command, String expression) {
|
||||
|
||||
return new SftpOutboundGatewaySpec(new SftpOutboundGateway(sessionFactory, command, expression));
|
||||
@@ -172,7 +171,7 @@ public final class Sftp {
|
||||
* @return the {@link SftpOutboundGatewaySpec}
|
||||
* @see RemoteFileTemplate
|
||||
*/
|
||||
public static SftpOutboundGatewaySpec outboundGateway(RemoteFileTemplate<ChannelSftp.LsEntry> remoteFileTemplate,
|
||||
public static SftpOutboundGatewaySpec outboundGateway(RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate,
|
||||
AbstractRemoteFileOutboundGateway.Command command, String expression) {
|
||||
|
||||
return outboundGateway(remoteFileTemplate, command.getCommand(), expression);
|
||||
@@ -187,7 +186,7 @@ public final class Sftp {
|
||||
* @return the {@link SftpOutboundGatewaySpec}
|
||||
* @see RemoteFileTemplate
|
||||
*/
|
||||
public static SftpOutboundGatewaySpec outboundGateway(RemoteFileTemplate<ChannelSftp.LsEntry> remoteFileTemplate,
|
||||
public static SftpOutboundGatewaySpec outboundGateway(RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate,
|
||||
String command, String expression) {
|
||||
|
||||
return new SftpOutboundGatewaySpec(new SftpOutboundGateway(remoteFileTemplate, command, expression));
|
||||
@@ -201,8 +200,8 @@ public final class Sftp {
|
||||
* @return the {@link SftpOutboundGatewaySpec}
|
||||
* @see MessageSessionCallback
|
||||
*/
|
||||
public static SftpOutboundGatewaySpec outboundGateway(SessionFactory<ChannelSftp.LsEntry> sessionFactory,
|
||||
MessageSessionCallback<ChannelSftp.LsEntry, ?> messageSessionCallback) {
|
||||
public static SftpOutboundGatewaySpec outboundGateway(SessionFactory<SftpClient.DirEntry> sessionFactory,
|
||||
MessageSessionCallback<SftpClient.DirEntry, ?> messageSessionCallback) {
|
||||
|
||||
return new SftpOutboundGatewaySpec(new SftpOutboundGateway(sessionFactory, messageSessionCallback));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2020 the original author or authors.
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,6 +19,8 @@ package org.springframework.integration.sftp.dsl;
|
||||
import java.io.File;
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.dsl.RemoteFileInboundChannelAdapterSpec;
|
||||
import org.springframework.integration.file.filters.CompositeFileListFilter;
|
||||
import org.springframework.integration.file.filters.FileListFilter;
|
||||
@@ -30,8 +32,6 @@ import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFil
|
||||
import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizer;
|
||||
import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizingMessageSource;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
|
||||
/**
|
||||
* A {@link RemoteFileInboundChannelAdapterSpec} for an {@link SftpInboundFileSynchronizingMessageSource}.
|
||||
*
|
||||
@@ -40,10 +40,10 @@ import com.jcraft.jsch.ChannelSftp;
|
||||
* @since 5.0
|
||||
*/
|
||||
public class SftpInboundChannelAdapterSpec
|
||||
extends RemoteFileInboundChannelAdapterSpec<ChannelSftp.LsEntry, SftpInboundChannelAdapterSpec,
|
||||
SftpInboundFileSynchronizingMessageSource> {
|
||||
extends RemoteFileInboundChannelAdapterSpec<SftpClient.DirEntry, SftpInboundChannelAdapterSpec,
|
||||
SftpInboundFileSynchronizingMessageSource> {
|
||||
|
||||
protected SftpInboundChannelAdapterSpec(SessionFactory<ChannelSftp.LsEntry> sessionFactory,
|
||||
protected SftpInboundChannelAdapterSpec(SessionFactory<SftpClient.DirEntry> sessionFactory,
|
||||
Comparator<File> comparator) {
|
||||
|
||||
super(new SftpInboundFileSynchronizer(sessionFactory));
|
||||
@@ -70,9 +70,10 @@ public class SftpInboundChannelAdapterSpec
|
||||
return filter(composeFilters(new SftpRegexPatternFileListFilter(regex)));
|
||||
}
|
||||
|
||||
private CompositeFileListFilter<ChannelSftp.LsEntry> composeFilters(FileListFilter<ChannelSftp.LsEntry>
|
||||
private CompositeFileListFilter<SftpClient.DirEntry> composeFilters(FileListFilter<SftpClient.DirEntry>
|
||||
fileListFilter) {
|
||||
CompositeFileListFilter<ChannelSftp.LsEntry> compositeFileListFilter = new CompositeFileListFilter<>();
|
||||
|
||||
CompositeFileListFilter<SftpClient.DirEntry> compositeFileListFilter = new CompositeFileListFilter<>();
|
||||
compositeFileListFilter.addFilters(fileListFilter,
|
||||
new SftpPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "sftpMessageSource"));
|
||||
return compositeFileListFilter;
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
|
||||
package org.springframework.integration.sftp.dsl;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.dsl.FileTransferringMessageHandlerSpec;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.file.support.FileExistsMode;
|
||||
import org.springframework.integration.sftp.outbound.SftpMessageHandler;
|
||||
import org.springframework.integration.sftp.session.SftpRemoteFileTemplate;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @author Joaquin Santana
|
||||
@@ -32,9 +32,9 @@ import com.jcraft.jsch.ChannelSftp;
|
||||
* @since 5.0
|
||||
*/
|
||||
public class SftpMessageHandlerSpec
|
||||
extends FileTransferringMessageHandlerSpec<ChannelSftp.LsEntry, SftpMessageHandlerSpec> {
|
||||
extends FileTransferringMessageHandlerSpec<SftpClient.DirEntry, SftpMessageHandlerSpec> {
|
||||
|
||||
protected SftpMessageHandlerSpec(SessionFactory<ChannelSftp.LsEntry> sessionFactory) {
|
||||
protected SftpMessageHandlerSpec(SessionFactory<SftpClient.DirEntry> sessionFactory) {
|
||||
this.target = new SftpMessageHandler(sessionFactory);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
package org.springframework.integration.sftp.dsl;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.dsl.RemoteFileOutboundGatewaySpec;
|
||||
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
|
||||
import org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilter;
|
||||
import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFilter;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
@@ -30,10 +30,10 @@ import com.jcraft.jsch.ChannelSftp;
|
||||
* @since 5.0
|
||||
*/
|
||||
public class SftpOutboundGatewaySpec
|
||||
extends RemoteFileOutboundGatewaySpec<ChannelSftp.LsEntry, SftpOutboundGatewaySpec> {
|
||||
extends RemoteFileOutboundGatewaySpec<SftpClient.DirEntry, SftpOutboundGatewaySpec> {
|
||||
|
||||
|
||||
protected SftpOutboundGatewaySpec(AbstractRemoteFileOutboundGateway<ChannelSftp.LsEntry> outboundGateway) {
|
||||
protected SftpOutboundGatewaySpec(AbstractRemoteFileOutboundGateway<SftpClient.DirEntry> outboundGateway) {
|
||||
super(outboundGateway);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-2020 the original author or authors.
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,8 @@ package org.springframework.integration.sftp.dsl;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.dsl.RemoteFileStreamingInboundChannelAdapterSpec;
|
||||
import org.springframework.integration.file.filters.CompositeFileListFilter;
|
||||
import org.springframework.integration.file.filters.FileListFilter;
|
||||
@@ -28,8 +30,6 @@ import org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilt
|
||||
import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFilter;
|
||||
import org.springframework.integration.sftp.inbound.SftpStreamingMessageSource;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
*
|
||||
@@ -37,11 +37,11 @@ import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
*
|
||||
*/
|
||||
public class SftpStreamingInboundChannelAdapterSpec
|
||||
extends RemoteFileStreamingInboundChannelAdapterSpec<LsEntry, SftpStreamingInboundChannelAdapterSpec,
|
||||
SftpStreamingMessageSource> {
|
||||
extends RemoteFileStreamingInboundChannelAdapterSpec<SftpClient.DirEntry,
|
||||
SftpStreamingInboundChannelAdapterSpec, SftpStreamingMessageSource> {
|
||||
|
||||
protected SftpStreamingInboundChannelAdapterSpec(RemoteFileTemplate<LsEntry> remoteFileTemplate,
|
||||
Comparator<LsEntry> comparator) {
|
||||
protected SftpStreamingInboundChannelAdapterSpec(RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate,
|
||||
Comparator<SftpClient.DirEntry> comparator) {
|
||||
|
||||
this.target = new SftpStreamingMessageSource(remoteFileTemplate, comparator);
|
||||
}
|
||||
@@ -68,8 +68,10 @@ public class SftpStreamingInboundChannelAdapterSpec
|
||||
return filter(composeFilters(new SftpRegexPatternFileListFilter(regex)));
|
||||
}
|
||||
|
||||
private CompositeFileListFilter<LsEntry> composeFilters(FileListFilter<LsEntry> fileListFilter) {
|
||||
CompositeFileListFilter<LsEntry> compositeFileListFilter = new CompositeFileListFilter<>();
|
||||
private CompositeFileListFilter<SftpClient.DirEntry> composeFilters(
|
||||
FileListFilter<SftpClient.DirEntry> fileListFilter) {
|
||||
|
||||
CompositeFileListFilter<SftpClient.DirEntry> compositeFileListFilter = new CompositeFileListFilter<>();
|
||||
compositeFileListFilter.addFilters(fileListFilter,
|
||||
new SftpPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "sftpStreamingMessageSource"));
|
||||
return compositeFileListFilter;
|
||||
|
||||
@@ -16,40 +16,42 @@
|
||||
|
||||
package org.springframework.integration.sftp.filters;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.filters.AbstractPersistentAcceptOnceFileListFilter;
|
||||
import org.springframework.integration.metadata.ConcurrentMetadataStore;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* Persistent file list filter using the server's file timestamp to detect if we've already
|
||||
* 'seen' this file.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author David Liu
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
*/
|
||||
public class SftpPersistentAcceptOnceFileListFilter extends AbstractPersistentAcceptOnceFileListFilter<LsEntry> {
|
||||
public class SftpPersistentAcceptOnceFileListFilter
|
||||
extends AbstractPersistentAcceptOnceFileListFilter<SftpClient.DirEntry> {
|
||||
|
||||
public SftpPersistentAcceptOnceFileListFilter(ConcurrentMetadataStore store, String prefix) {
|
||||
super(store, prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long modified(LsEntry file) {
|
||||
return ((long) file.getAttrs().getMTime()) * 1000; // NOSONAR magic number
|
||||
protected long modified(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().getModifyTime().toMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String fileName(LsEntry file) {
|
||||
protected String fileName(SftpClient.DirEntry file) {
|
||||
return file.getFilename();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(LsEntry file) {
|
||||
return file.getAttrs().isDir();
|
||||
protected boolean isDirectory(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().isDirectory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,19 +18,20 @@ package org.springframework.integration.sftp.filters;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.integration.file.filters.AbstractRegexPatternFileListFilter;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import org.springframework.integration.file.filters.AbstractRegexPatternFileListFilter;
|
||||
|
||||
/**
|
||||
* Implementation of {@link AbstractRegexPatternFileListFilter} for SFTP.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SftpRegexPatternFileListFilter extends AbstractRegexPatternFileListFilter<ChannelSftp.LsEntry> {
|
||||
public class SftpRegexPatternFileListFilter extends AbstractRegexPatternFileListFilter<SftpClient.DirEntry> {
|
||||
|
||||
public SftpRegexPatternFileListFilter(String pattern) {
|
||||
super(pattern);
|
||||
@@ -42,13 +43,13 @@ public class SftpRegexPatternFileListFilter extends AbstractRegexPatternFileList
|
||||
|
||||
|
||||
@Override
|
||||
protected String getFilename(LsEntry entry) {
|
||||
protected String getFilename(SftpClient.DirEntry entry) {
|
||||
return (entry != null) ? entry.getFilename() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(LsEntry file) {
|
||||
return file.getAttrs().isDir();
|
||||
protected boolean isDirectory(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().isDirectory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,19 +16,20 @@
|
||||
|
||||
package org.springframework.integration.sftp.filters;
|
||||
|
||||
import org.springframework.integration.file.filters.AbstractSimplePatternFileListFilter;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import org.springframework.integration.file.filters.AbstractSimplePatternFileListFilter;
|
||||
|
||||
/**
|
||||
* Implementation of {@link AbstractSimplePatternFileListFilter} for SFTP.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SftpSimplePatternFileListFilter extends AbstractSimplePatternFileListFilter<ChannelSftp.LsEntry> {
|
||||
public class SftpSimplePatternFileListFilter extends AbstractSimplePatternFileListFilter<SftpClient.DirEntry> {
|
||||
|
||||
public SftpSimplePatternFileListFilter(String pattern) {
|
||||
super(pattern);
|
||||
@@ -36,13 +37,13 @@ public class SftpSimplePatternFileListFilter extends AbstractSimplePatternFileLi
|
||||
|
||||
|
||||
@Override
|
||||
protected String getFilename(LsEntry entry) {
|
||||
protected String getFilename(SftpClient.DirEntry entry) {
|
||||
return (entry != null) ? entry.getFilename() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(LsEntry file) {
|
||||
return file.getAttrs().isDir();
|
||||
protected boolean isDirectory(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().isDirectory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,40 +19,45 @@ package org.springframework.integration.sftp.filters;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.filters.AbstractMarkerFilePresentFileListFilter;
|
||||
import org.springframework.integration.file.filters.FileListFilter;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* SFTP implementation of {@link AbstractMarkerFilePresentFileListFilter}.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*
|
||||
*/
|
||||
public class SftpSystemMarkerFilePresentFileListFilter extends AbstractMarkerFilePresentFileListFilter<LsEntry> {
|
||||
public class SftpSystemMarkerFilePresentFileListFilter
|
||||
extends AbstractMarkerFilePresentFileListFilter<SftpClient.DirEntry> {
|
||||
|
||||
public SftpSystemMarkerFilePresentFileListFilter(FileListFilter<LsEntry> filter) {
|
||||
public SftpSystemMarkerFilePresentFileListFilter(FileListFilter<SftpClient.DirEntry> filter) {
|
||||
super(filter);
|
||||
}
|
||||
|
||||
public SftpSystemMarkerFilePresentFileListFilter(FileListFilter<LsEntry> filter, String suffix) {
|
||||
public SftpSystemMarkerFilePresentFileListFilter(FileListFilter<SftpClient.DirEntry> filter, String suffix) {
|
||||
super(filter, suffix);
|
||||
}
|
||||
|
||||
public SftpSystemMarkerFilePresentFileListFilter(FileListFilter<LsEntry> filter,
|
||||
public SftpSystemMarkerFilePresentFileListFilter(FileListFilter<SftpClient.DirEntry> filter,
|
||||
Function<String, String> function) {
|
||||
|
||||
super(filter, function);
|
||||
}
|
||||
|
||||
public SftpSystemMarkerFilePresentFileListFilter(
|
||||
Map<FileListFilter<LsEntry>, Function<String, String>> filtersAndFunctions) {
|
||||
Map<FileListFilter<SftpClient.DirEntry>, Function<String, String>> filtersAndFunctions) {
|
||||
|
||||
super(filtersAndFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFilename(LsEntry file) {
|
||||
protected String getFilename(SftpClient.DirEntry file) {
|
||||
return file.getFilename();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,11 +16,14 @@
|
||||
|
||||
package org.springframework.integration.sftp.gateway;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.ClientCallbackWithoutResult;
|
||||
import org.springframework.integration.file.remote.MessageSessionCallback;
|
||||
@@ -30,12 +33,6 @@ import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOut
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.sftp.session.SftpFileInfo;
|
||||
import org.springframework.integration.sftp.session.SftpRemoteFileTemplate;
|
||||
import org.springframework.integration.sftp.support.GeneralSftpException;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.SftpException;
|
||||
|
||||
/**
|
||||
* Outbound Gateway for performing remote file operations via SFTP.
|
||||
@@ -45,16 +42,7 @@ import com.jcraft.jsch.SftpException;
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEntry> {
|
||||
|
||||
private static final Method LS_ENTRY_SET_FILENAME_METHOD;
|
||||
|
||||
static {
|
||||
LS_ENTRY_SET_FILENAME_METHOD = ReflectionUtils.findMethod(LsEntry.class, "setFilename", String.class);
|
||||
if (LS_ENTRY_SET_FILENAME_METHOD != null) {
|
||||
ReflectionUtils.makeAccessible(LS_ENTRY_SET_FILENAME_METHOD);
|
||||
}
|
||||
}
|
||||
public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<SftpClient.DirEntry> {
|
||||
|
||||
/**
|
||||
* Construct an instance using the provided session factory and callback for
|
||||
@@ -62,8 +50,8 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
* @param sessionFactory the session factory.
|
||||
* @param messageSessionCallback the callback.
|
||||
*/
|
||||
public SftpOutboundGateway(SessionFactory<LsEntry> sessionFactory,
|
||||
MessageSessionCallback<LsEntry, ?> messageSessionCallback) {
|
||||
public SftpOutboundGateway(SessionFactory<SftpClient.DirEntry> sessionFactory,
|
||||
MessageSessionCallback<SftpClient.DirEntry, ?> messageSessionCallback) {
|
||||
|
||||
this(new SftpRemoteFileTemplate(sessionFactory), messageSessionCallback);
|
||||
remoteFileTemplateExplicitlySet(false);
|
||||
@@ -75,8 +63,8 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
* @param remoteFileTemplate the remote file template.
|
||||
* @param messageSessionCallback the callback.
|
||||
*/
|
||||
public SftpOutboundGateway(RemoteFileTemplate<LsEntry> remoteFileTemplate,
|
||||
MessageSessionCallback<LsEntry, ?> messageSessionCallback) {
|
||||
public SftpOutboundGateway(RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate,
|
||||
MessageSessionCallback<SftpClient.DirEntry, ?> messageSessionCallback) {
|
||||
|
||||
super(remoteFileTemplate, messageSessionCallback);
|
||||
}
|
||||
@@ -88,7 +76,7 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
* @param command the command.
|
||||
* @param expression the filename expression.
|
||||
*/
|
||||
public SftpOutboundGateway(SessionFactory<LsEntry> sessionFactory, String command, String expression) {
|
||||
public SftpOutboundGateway(SessionFactory<SftpClient.DirEntry> sessionFactory, String command, String expression) {
|
||||
this(new SftpRemoteFileTemplate(sessionFactory), command, expression);
|
||||
remoteFileTemplateExplicitlySet(false);
|
||||
}
|
||||
@@ -100,46 +88,46 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
* @param command the command.
|
||||
* @param expression the filename expression.
|
||||
*/
|
||||
public SftpOutboundGateway(RemoteFileTemplate<LsEntry> remoteFileTemplate, String command, String expression) {
|
||||
public SftpOutboundGateway(RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate, String command, String expression) {
|
||||
super(remoteFileTemplate, command, expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(LsEntry file) {
|
||||
return file.getAttrs().isDir();
|
||||
protected boolean isDirectory(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isLink(LsEntry file) {
|
||||
return file.getAttrs().isLink();
|
||||
protected boolean isLink(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().isSymbolicLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFilename(LsEntry file) {
|
||||
protected String getFilename(SftpClient.DirEntry file) {
|
||||
return file.getFilename();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFilename(AbstractFileInfo<LsEntry> file) {
|
||||
protected String getFilename(AbstractFileInfo<SftpClient.DirEntry> file) {
|
||||
return file.getFilename();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AbstractFileInfo<LsEntry>> asFileInfoList(Collection<LsEntry> files) {
|
||||
protected List<AbstractFileInfo<SftpClient.DirEntry>> asFileInfoList(Collection<SftpClient.DirEntry> files) {
|
||||
return files.stream()
|
||||
.map(SftpFileInfo::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getModified(LsEntry file) {
|
||||
return ((long) file.getAttrs().getMTime()) * 1000; // NOSONAR magic number
|
||||
protected long getModified(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().getModifyTime().toMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LsEntry enhanceNameWithSubDirectory(LsEntry file, String directory) {
|
||||
ReflectionUtils.invokeMethod(LS_ENTRY_SET_FILENAME_METHOD, file, directory + file.getFilename());
|
||||
return file;
|
||||
protected SftpClient.DirEntry enhanceNameWithSubDirectory(SftpClient.DirEntry file, String directory) {
|
||||
return new SftpClient.DirEntry(directory + file.getFilename(), directory + file.getFilename(),
|
||||
file.getAttributes());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,17 +141,18 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doChmod(RemoteFileOperations<LsEntry> remoteFileOperations, final String path, final int chmod) {
|
||||
remoteFileOperations
|
||||
.executeWithClient((ClientCallbackWithoutResult<ChannelSftp>) client -> {
|
||||
try {
|
||||
client.chmod(chmod, path);
|
||||
}
|
||||
catch (SftpException e) {
|
||||
throw new GeneralSftpException(
|
||||
"Failed to execute 'chmod " + Integer.toOctalString(chmod) + " " + path + "'", e);
|
||||
}
|
||||
});
|
||||
protected void doChmod(RemoteFileOperations<SftpClient.DirEntry> remoteFileOperations, String path, int chmod) {
|
||||
remoteFileOperations.executeWithClient((ClientCallbackWithoutResult<SftpClient>) client -> {
|
||||
try {
|
||||
SftpClient.Attributes attributes = client.stat(path);
|
||||
attributes.setPermissions(chmod);
|
||||
client.setStat(path, attributes);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(
|
||||
"Failed to execute 'chmod " + Integer.toOctalString(chmod) + " " + path + "'", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
package org.springframework.integration.sftp.inbound;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer;
|
||||
import org.springframework.integration.metadata.SimpleMetadataStore;
|
||||
import org.springframework.integration.sftp.filters.SftpPersistentAcceptOnceFileListFilter;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* Handles the synchronization between a remote SFTP directory and a local mount.
|
||||
*
|
||||
@@ -33,30 +33,30 @@ import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SftpInboundFileSynchronizer extends AbstractInboundFileSynchronizer<LsEntry> {
|
||||
public class SftpInboundFileSynchronizer extends AbstractInboundFileSynchronizer<SftpClient.DirEntry> {
|
||||
|
||||
/**
|
||||
* Create a synchronizer with the {@code SessionFactory} used to acquire {@code Session} instances.
|
||||
* @param sessionFactory The session factory.
|
||||
*/
|
||||
public SftpInboundFileSynchronizer(SessionFactory<LsEntry> sessionFactory) {
|
||||
public SftpInboundFileSynchronizer(SessionFactory<SftpClient.DirEntry> sessionFactory) {
|
||||
super(sessionFactory);
|
||||
doSetFilter(new SftpPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "sftpMessageSource"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isFile(LsEntry file) {
|
||||
return (file != null && file.getAttrs() != null && !file.getAttrs().isDir() && !file.getAttrs().isLink());
|
||||
protected boolean isFile(SftpClient.DirEntry file) {
|
||||
return file != null && file.getAttributes().isRegularFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFilename(LsEntry file) {
|
||||
return (file != null ? file.getFilename() : null);
|
||||
protected String getFilename(SftpClient.DirEntry file) {
|
||||
return file != null ? file.getFilename() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getModified(LsEntry file) {
|
||||
return (long) file.getAttrs().getMTime() * 1000; // NOSONAR magic number
|
||||
protected long getModified(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().getModifyTime().toMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,31 +19,34 @@ package org.springframework.integration.sftp.inbound;
|
||||
import java.io.File;
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer;
|
||||
import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizingMessageSource;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.integration.core.MessageSource} implementation for SFTP
|
||||
* that delegates to an InboundFileSynchronizer.
|
||||
*
|
||||
* @author Josh Long
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SftpInboundFileSynchronizingMessageSource extends AbstractInboundFileSynchronizingMessageSource<ChannelSftp.LsEntry> {
|
||||
public class SftpInboundFileSynchronizingMessageSource
|
||||
extends AbstractInboundFileSynchronizingMessageSource<SftpClient.DirEntry> {
|
||||
|
||||
public SftpInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer<LsEntry> synchronizer) {
|
||||
public SftpInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer<SftpClient.DirEntry> synchronizer) {
|
||||
super(synchronizer);
|
||||
}
|
||||
|
||||
public SftpInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer<LsEntry> synchronizer, Comparator<File> comparator) {
|
||||
public SftpInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer<SftpClient.DirEntry> synchronizer,
|
||||
Comparator<File> comparator) {
|
||||
|
||||
super(synchronizer, comparator);
|
||||
}
|
||||
|
||||
|
||||
public String getComponentType() {
|
||||
return "sftp:inbound-channel-adapter";
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.AbstractRemoteFileStreamingMessageSource;
|
||||
import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
@@ -28,8 +30,6 @@ import org.springframework.integration.metadata.SimpleMetadataStore;
|
||||
import org.springframework.integration.sftp.filters.SftpPersistentAcceptOnceFileListFilter;
|
||||
import org.springframework.integration.sftp.session.SftpFileInfo;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* Message source for streaming SFTP remote file contents.
|
||||
*
|
||||
@@ -39,13 +39,13 @@ import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
* @since 4.3
|
||||
*
|
||||
*/
|
||||
public class SftpStreamingMessageSource extends AbstractRemoteFileStreamingMessageSource<LsEntry> {
|
||||
public class SftpStreamingMessageSource extends AbstractRemoteFileStreamingMessageSource<SftpClient.DirEntry> {
|
||||
|
||||
/**
|
||||
* Construct an instance with the supplied template.
|
||||
* @param template the template.
|
||||
*/
|
||||
public SftpStreamingMessageSource(RemoteFileTemplate<LsEntry> template) {
|
||||
public SftpStreamingMessageSource(RemoteFileTemplate<SftpClient.DirEntry> template) {
|
||||
this(template, null);
|
||||
}
|
||||
|
||||
@@ -56,8 +56,9 @@ public class SftpStreamingMessageSource extends AbstractRemoteFileStreamingMessa
|
||||
* @param template the template.
|
||||
* @param comparator the comparator.
|
||||
*/
|
||||
public SftpStreamingMessageSource(RemoteFileTemplate<LsEntry> template,
|
||||
Comparator<LsEntry> comparator) {
|
||||
public SftpStreamingMessageSource(RemoteFileTemplate<SftpClient.DirEntry> template,
|
||||
Comparator<SftpClient.DirEntry> comparator) {
|
||||
|
||||
super(template, comparator);
|
||||
doSetFilter(new SftpPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "sftpStreamingMessageSource"));
|
||||
}
|
||||
@@ -68,17 +69,17 @@ public class SftpStreamingMessageSource extends AbstractRemoteFileStreamingMessa
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AbstractFileInfo<LsEntry>> asFileInfoList(Collection<LsEntry> files) {
|
||||
List<AbstractFileInfo<LsEntry>> canonicalFiles = new ArrayList<AbstractFileInfo<LsEntry>>();
|
||||
for (LsEntry file : files) {
|
||||
protected List<AbstractFileInfo<SftpClient.DirEntry>> asFileInfoList(Collection<SftpClient.DirEntry> files) {
|
||||
List<AbstractFileInfo<SftpClient.DirEntry>> canonicalFiles = new ArrayList<>();
|
||||
for (SftpClient.DirEntry file : files) {
|
||||
canonicalFiles.add(new SftpFileInfo(file));
|
||||
}
|
||||
return canonicalFiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(LsEntry file) {
|
||||
return file != null && file.getAttrs().isDir();
|
||||
protected boolean isDirectory(SftpClient.DirEntry file) {
|
||||
return file != null && file.getAttributes().isDirectory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,26 +16,28 @@
|
||||
|
||||
package org.springframework.integration.sftp.outbound;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.remote.ClientCallbackWithoutResult;
|
||||
import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.file.support.FileExistsMode;
|
||||
import org.springframework.integration.sftp.session.SftpRemoteFileTemplate;
|
||||
import org.springframework.integration.sftp.support.GeneralSftpException;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.SftpException;
|
||||
|
||||
/**
|
||||
* Subclass of {@link FileTransferringMessageHandler} for SFTP.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artme Bilan
|
||||
*
|
||||
* @since 4.3
|
||||
*
|
||||
*/
|
||||
public class SftpMessageHandler extends FileTransferringMessageHandler<LsEntry> {
|
||||
public class SftpMessageHandler extends FileTransferringMessageHandler<SftpClient.DirEntry> {
|
||||
|
||||
/**
|
||||
* @param remoteFileTemplate the template.
|
||||
@@ -62,7 +64,7 @@ public class SftpMessageHandler extends FileTransferringMessageHandler<LsEntry>
|
||||
* @see FileTransferringMessageHandler#FileTransferringMessageHandler
|
||||
* (SessionFactory)
|
||||
*/
|
||||
public SftpMessageHandler(SessionFactory<LsEntry> sessionFactory) {
|
||||
public SftpMessageHandler(SessionFactory<SftpClient.DirEntry> sessionFactory) {
|
||||
this(new SftpRemoteFileTemplate(sessionFactory));
|
||||
}
|
||||
|
||||
@@ -72,14 +74,16 @@ public class SftpMessageHandler extends FileTransferringMessageHandler<LsEntry>
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doChmod(RemoteFileTemplate<LsEntry> remoteFileTemplate, final String path, final int chmod) {
|
||||
remoteFileTemplate.executeWithClient((ClientCallbackWithoutResult<ChannelSftp>) client -> {
|
||||
protected void doChmod(RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate, String path, int chmod) {
|
||||
remoteFileTemplate.executeWithClient((ClientCallbackWithoutResult<SftpClient>) client -> {
|
||||
try {
|
||||
client.chmod(chmod, path);
|
||||
SftpClient.Attributes attributes = client.stat(path);
|
||||
attributes.setPermissions(chmod);
|
||||
client.setStat(path, attributes);
|
||||
}
|
||||
catch (SftpException e) {
|
||||
throw new GeneralSftpException(
|
||||
"Failed to execute 'chmod " + Integer.toOctalString(chmod) + " " + path + "'", e);
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(
|
||||
"Failed to execute 'chmod " + Integer.toOctalString(chmod) + " " + path + "'", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,31 +17,38 @@
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Properties;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.sshd.client.SshClient;
|
||||
import org.apache.sshd.client.auth.keyboard.UserInteraction;
|
||||
import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
|
||||
import org.apache.sshd.client.config.hosts.HostConfigEntry;
|
||||
import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier;
|
||||
import org.apache.sshd.client.keyverifier.RejectAllServerKeyVerifier;
|
||||
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
|
||||
import org.apache.sshd.client.session.ClientSession;
|
||||
import org.apache.sshd.common.SshConstants;
|
||||
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
||||
import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
|
||||
import org.apache.sshd.common.util.io.resource.AbstractIoResource;
|
||||
import org.apache.sshd.common.util.io.resource.IoResource;
|
||||
import org.apache.sshd.common.util.net.SshdSocketAddress;
|
||||
import org.apache.sshd.common.util.security.SecurityUtils;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.client.SftpClientFactory;
|
||||
import org.apache.sshd.sftp.client.SftpVersionSelector;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.JavaUtils;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.file.remote.session.SharedSessionCapable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.JSch;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.Proxy;
|
||||
import com.jcraft.jsch.SocketFactory;
|
||||
import com.jcraft.jsch.UIKeyboardInteractive;
|
||||
import com.jcraft.jsch.UserInfo;
|
||||
|
||||
/**
|
||||
* Factory for creating {@link SftpSession} instances.
|
||||
@@ -57,62 +64,43 @@ import com.jcraft.jsch.UserInfo;
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, SharedSessionCapable {
|
||||
public class DefaultSftpSessionFactory implements SessionFactory<SftpClient.DirEntry>, SharedSessionCapable {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(DefaultSftpSessionFactory.class);
|
||||
private final SshClient sshClient;
|
||||
|
||||
static {
|
||||
JSch.setLogger(new JschLogger());
|
||||
}
|
||||
|
||||
private final UserInfo userInfoWrapper = new UserInfoWrapper();
|
||||
|
||||
private final JSch jsch;
|
||||
private final AtomicBoolean initialized = new AtomicBoolean();
|
||||
|
||||
private final boolean isSharedSession;
|
||||
|
||||
private final Lock sharedSessionLock;
|
||||
|
||||
private boolean isInnerClient = false;
|
||||
|
||||
private String host;
|
||||
|
||||
private int port = 22; // NOSONAR magic number. The default
|
||||
private int port = SshConstants.DEFAULT_PORT;
|
||||
|
||||
private String user;
|
||||
|
||||
private String password;
|
||||
|
||||
private HostConfigEntry hostConfig;
|
||||
|
||||
private Resource knownHosts;
|
||||
|
||||
private Resource privateKey;
|
||||
|
||||
private String privateKeyPassphrase;
|
||||
|
||||
private Properties sessionConfig;
|
||||
|
||||
private Proxy proxy;
|
||||
|
||||
private SocketFactory socketFactory;
|
||||
|
||||
private Integer timeout;
|
||||
|
||||
private String clientVersion;
|
||||
|
||||
private String hostKeyAlias;
|
||||
|
||||
private Integer serverAliveInterval;
|
||||
|
||||
private Integer serverAliveCountMax;
|
||||
|
||||
private Boolean enableDaemonThread;
|
||||
|
||||
private UserInfo userInfo;
|
||||
private UserInteraction userInteraction;
|
||||
|
||||
private boolean allowUnknownKeys = false;
|
||||
|
||||
private Duration channelConnectTimeout;
|
||||
private Integer timeout;
|
||||
|
||||
private volatile JSchSessionWrapper sharedJschSession;
|
||||
private SftpVersionSelector sftpVersionSelector = SftpVersionSelector.CURRENT;
|
||||
|
||||
private volatile SftpClient sharedSftpClient;
|
||||
|
||||
public DefaultSftpSessionFactory() {
|
||||
this(false);
|
||||
@@ -122,16 +110,18 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
* @param isSharedSession true if the session is to be shared.
|
||||
*/
|
||||
public DefaultSftpSessionFactory(boolean isSharedSession) {
|
||||
this(new JSch(), isSharedSession);
|
||||
this(SshClient.setUpDefaultClient(), isSharedSession);
|
||||
this.isInnerClient = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intended for use in tests so the jsch can be mocked.
|
||||
* @param jsch The jsch instance.
|
||||
* @param sshClient The SshClient instance.
|
||||
* @param isSharedSession true if the session is to be shared.
|
||||
*/
|
||||
public DefaultSftpSessionFactory(JSch jsch, boolean isSharedSession) {
|
||||
this.jsch = jsch;
|
||||
public DefaultSftpSessionFactory(SshClient sshClient, boolean isSharedSession) {
|
||||
Assert.notNull(sshClient, "'sshClient' must not be null");
|
||||
this.sshClient = sshClient;
|
||||
this.isSharedSession = isSharedSession;
|
||||
if (this.isSharedSession) {
|
||||
this.sharedSessionLock = new ReentrantLock();
|
||||
@@ -142,9 +132,9 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
}
|
||||
|
||||
/**
|
||||
* The url of the host you want connect to. This is a mandatory property.
|
||||
* The url of the host you want to connect to. This is a mandatory property.
|
||||
* @param host The host.
|
||||
* @see JSch#getSession(String, String, int)
|
||||
* @see SshClient#connect(String, String, int)
|
||||
*/
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
@@ -155,7 +145,7 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
* this value defaults to <code>22</code>. If specified, this properties must
|
||||
* be a positive number.
|
||||
* @param port The port.
|
||||
* @see JSch#getSession(String, String, int)
|
||||
* @see SshClient#connect(String, String, int)
|
||||
*/
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
@@ -164,7 +154,7 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
/**
|
||||
* The remote user to use. This is a mandatory property.
|
||||
* @param user The user.
|
||||
* @see JSch#getSession(String, String, int)
|
||||
* @see SshClient#connect(String, String, int)
|
||||
*/
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
@@ -174,23 +164,36 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
* The password to authenticate against the remote host. If a password is
|
||||
* not provided, then a {@link DefaultSftpSessionFactory#setPrivateKey(Resource) privateKey} is
|
||||
* mandatory.
|
||||
* Not allowed if {@link #setUserInfo(UserInfo) userInfo} is provided - the password is obtained
|
||||
* from that object.
|
||||
* @param password The password.
|
||||
* @see com.jcraft.jsch.Session#setPassword(String)
|
||||
* @see SshClient#setPasswordIdentityProvider(PasswordIdentityProvider)
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
Assert.state(this.isInnerClient,
|
||||
"A password must be configured on the externally provided SshClient instance");
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the filename that will be used for a host key repository.
|
||||
* The file has the same format as OpenSSH's known_hosts file.
|
||||
* Provide a {@link HostConfigEntry} as an alternative for the user/host/port options.
|
||||
* Can be configured with a proxy jump property.
|
||||
* @param hostConfig the {@link HostConfigEntry} for connection.
|
||||
* @since 6.0
|
||||
* @see SshClient#connect(HostConfigEntry)
|
||||
*/
|
||||
public void setHostConfig(HostConfigEntry hostConfig) {
|
||||
this.hostConfig = hostConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a {@link Resource} that will be used for a host key repository.
|
||||
* The data has to have the same format as OpenSSH's known_hosts file.
|
||||
* @param knownHosts the resource for known hosts.
|
||||
* @since 5.2.5
|
||||
* @see JSch#setKnownHosts(java.io.InputStream)
|
||||
* @see SshClient#setServerKeyVerifier(ServerKeyVerifier)
|
||||
*/
|
||||
public void setKnownHostsResource(Resource knownHosts) {
|
||||
Assert.state(this.isInnerClient,
|
||||
"Known hosts must be configured on the externally provided SshClient instance");
|
||||
this.knownHosts = knownHosts;
|
||||
}
|
||||
|
||||
@@ -198,152 +201,43 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
* Allows you to set a {@link Resource}, which represents the location of the
|
||||
* private key used for authenticating against the remote host. If the privateKey
|
||||
* is not provided, then the {@link DefaultSftpSessionFactory#setPassword(String) password}
|
||||
* property is mandatory (or {@link #setUserInfo(UserInfo) userInfo} that returns a
|
||||
* password.
|
||||
* property is mandatory.
|
||||
* @param privateKey The private key.
|
||||
* @see JSch#addIdentity(String)
|
||||
* @see JSch#addIdentity(String, String)
|
||||
* @see SshClient#setKeyIdentityProvider(KeyIdentityProvider)
|
||||
*/
|
||||
public void setPrivateKey(Resource privateKey) {
|
||||
Assert.state(this.isInnerClient,
|
||||
"A private key auth must be configured on the externally provided SshClient instance");
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* The password for the private key. Optional.
|
||||
* Not allowed if {@link #setUserInfo(UserInfo) userInfo} is provided - the passphrase is obtained
|
||||
* from that object.
|
||||
* @param privateKeyPassphrase The private key passphrase.
|
||||
* @see JSch#addIdentity(String, String)
|
||||
* @see SshClient#setKeyIdentityProvider(KeyIdentityProvider)
|
||||
*/
|
||||
public void setPrivateKeyPassphrase(String privateKeyPassphrase) {
|
||||
Assert.state(this.isInnerClient,
|
||||
"A private key auth must be configured on the externally provided SshClient instance");
|
||||
this.privateKeyPassphrase = privateKeyPassphrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using {@link Properties}, you can set additional configuration settings on
|
||||
* the underlying JSch {@link com.jcraft.jsch.Session}.
|
||||
* @param sessionConfig The session configuration properties.
|
||||
* @see com.jcraft.jsch.Session#setConfig(Properties)
|
||||
*/
|
||||
public void setSessionConfig(Properties sessionConfig) {
|
||||
this.sessionConfig = sessionConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows for specifying a JSch-based {@link Proxy}. If set, then the proxy
|
||||
* object is used to create the connection to the remote host.
|
||||
* @param proxy The proxy.
|
||||
* @see com.jcraft.jsch.Session#setProxy(Proxy)
|
||||
*/
|
||||
public void setProxy(Proxy proxy) {
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to pass in a {@link SocketFactory}. The socket factory is used
|
||||
* to create a socket to the target host. When a {@link Proxy} is used, the
|
||||
* socket factory is passed to the proxy. By default plain TCP sockets are used.
|
||||
* @param socketFactory The socket factory.
|
||||
* @see com.jcraft.jsch.Session#setSocketFactory(SocketFactory)
|
||||
*/
|
||||
public void setSocketFactory(SocketFactory socketFactory) {
|
||||
this.socketFactory = socketFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* The timeout property is used as the socket timeout parameter, as well as
|
||||
* the default connection timeout. Defaults to <code>0</code>, which means,
|
||||
* that no timeout will occur.
|
||||
* @param timeout The timeout.
|
||||
* @see com.jcraft.jsch.Session#setTimeout(int)
|
||||
*/
|
||||
public void setTimeout(Integer timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to set the client version property. It's default depends on the
|
||||
* underlying JSch version but it will look like <code>SSH-2.0-JSCH-0.1.45</code>
|
||||
* @param clientVersion The client version.
|
||||
* @see com.jcraft.jsch.Session#setClientVersion(String)
|
||||
*/
|
||||
public void setClientVersion(String clientVersion) {
|
||||
this.clientVersion = clientVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the host key alias, used when comparing the host key to the known
|
||||
* hosts list.
|
||||
* @param hostKeyAlias The host key alias.
|
||||
* @see com.jcraft.jsch.Session#setHostKeyAlias(String)
|
||||
*/
|
||||
public void setHostKeyAlias(String hostKeyAlias) {
|
||||
this.hostKeyAlias = hostKeyAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout interval (milliseconds) before a server alive message is
|
||||
* sent, in case no message is received from the server.
|
||||
* @param serverAliveInterval The server alive interval.
|
||||
* @see com.jcraft.jsch.Session#setServerAliveInterval(int)
|
||||
*/
|
||||
public void setServerAliveInterval(Integer serverAliveInterval) {
|
||||
this.serverAliveInterval = serverAliveInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the number of server-alive messages, which will be sent without
|
||||
* any reply from the server before disconnecting. If not set, this property
|
||||
* defaults to <code>1</code>.
|
||||
* @param serverAliveCountMax The server alive count max.
|
||||
* @see com.jcraft.jsch.Session#setServerAliveCountMax(int)
|
||||
*/
|
||||
public void setServerAliveCountMax(Integer serverAliveCountMax) {
|
||||
this.serverAliveCountMax = serverAliveCountMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, all threads will be daemon threads. If set to <code>false</code>,
|
||||
* normal non-daemon threads will be used. This property will be set on the
|
||||
* underlying {@link com.jcraft.jsch.Session} using
|
||||
* {@link com.jcraft.jsch.Session#setDaemonThread(boolean)}. There, this
|
||||
* property will default to <code>false</code>, if not explicitly set.
|
||||
* @param enableDaemonThread true to enable a daemon thread.
|
||||
* @see com.jcraft.jsch.Session#setDaemonThread(boolean)
|
||||
*/
|
||||
public void setEnableDaemonThread(Boolean enableDaemonThread) {
|
||||
this.enableDaemonThread = enableDaemonThread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a {@link UserInfo} which exposes control over dealing with new keys or key
|
||||
* Provide a {@link UserInteraction} which exposes control over dealing with new keys or key
|
||||
* changes. As Spring Integration will not normally allow user interaction, the
|
||||
* implementation must respond to Jsch calls in a suitable way.
|
||||
* <p>
|
||||
* Jsch calls {@link UserInfo#promptYesNo(String)} when connecting to an unknown host,
|
||||
* or when a known host's key has changed (see {@link #setKnownHostsResource(Resource)}
|
||||
* knownHosts}). Generally, it should return false as returning true will accept all
|
||||
* new keys or key changes.
|
||||
* <p>
|
||||
* If no {@link UserInfo} is provided, the behavior is defined by
|
||||
* {@link #setAllowUnknownKeys(boolean) allowUnknownKeys}.
|
||||
* <p>
|
||||
* If {@link #setPassword(String) setPassword} is invoked with a non-null password, it will
|
||||
* override any password in the supplied {@link UserInfo}.
|
||||
* <p>
|
||||
* <b>NOTE: When this is provided, the {@link #setPassword(String) password} and
|
||||
* {@link #setPrivateKeyPassphrase(String) passphrase} are not allowed because those values
|
||||
* will be obtained from the {@link UserInfo}.</b>
|
||||
* @param userInfo the UserInfo.
|
||||
* implementation must respond to SSH protocol calls in a suitable way.
|
||||
* @param userInteraction the UserInteraction.
|
||||
* @since 4.1.7
|
||||
* @see com.jcraft.jsch.Session#setUserInfo(com.jcraft.jsch.UserInfo)
|
||||
* @see SshClient#setUserInteraction(UserInteraction)
|
||||
*/
|
||||
public void setUserInfo(UserInfo userInfo) {
|
||||
this.userInfo = userInfo;
|
||||
public void setUserInteraction(UserInteraction userInteraction) {
|
||||
Assert.state(this.isInnerClient,
|
||||
"A `UserInteraction` must be configured on the externally provided SshClient instance");
|
||||
this.userInteraction = userInteraction;
|
||||
}
|
||||
|
||||
/**
|
||||
* When no {@link UserInfo} has been provided, set to true to unconditionally allow
|
||||
* When no {@link #knownHosts} has been provided, set to true to unconditionally allow
|
||||
* connecting to an unknown host or when a host's key has changed (see
|
||||
* {@link #setKnownHostsResource(Resource) knownHosts}). Default false (since 4.2).
|
||||
* Set to true if a knownHosts file is not provided.
|
||||
@@ -351,17 +245,25 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
* @since 4.1.7
|
||||
*/
|
||||
public void setAllowUnknownKeys(boolean allowUnknownKeys) {
|
||||
Assert.state(this.isInnerClient,
|
||||
"An `AcceptAllServerKeyVerifier` must be configured on the externally provided SshClient instance");
|
||||
this.allowUnknownKeys = allowUnknownKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection timeout.
|
||||
* @param timeout the timeout to set.
|
||||
* @since 5.2
|
||||
* The timeout property is used as the socket timeout parameter, as well as
|
||||
* the default connection timeout. Defaults to <code>0</code>, which means,
|
||||
* that no timeout will occur.
|
||||
* @param timeout The timeout.
|
||||
* @see org.apache.sshd.client.future.ConnectFuture#verify(long)
|
||||
*/
|
||||
public void setChannelConnectTimeout(Duration timeout) {
|
||||
Assert.notNull(timeout, "'connectTimeout' cannot be null");
|
||||
this.channelConnectTimeout = timeout;
|
||||
public void setTimeout(Integer timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public void setSftpVersionSelector(SftpVersionSelector sftpVersionSelector) {
|
||||
Assert.notNull(sftpVersionSelector, "'sftpVersionSelector' must noy be null");
|
||||
this.sftpVersionSelector = sftpVersionSelector;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -370,18 +272,19 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
if (this.sharedSessionLock != null) {
|
||||
this.sharedSessionLock.lock();
|
||||
}
|
||||
JSchSessionWrapper jschSession = this.sharedJschSession;
|
||||
SftpClient sftpClient = this.sharedSftpClient;
|
||||
try {
|
||||
boolean freshJschSession = false;
|
||||
if (jschSession == null || !jschSession.isConnected()) {
|
||||
jschSession = new JSchSessionWrapper(initJschSession());
|
||||
freshJschSession = true;
|
||||
boolean freshSftpClient = false;
|
||||
if (sftpClient == null || !sftpClient.isOpen()) {
|
||||
sftpClient =
|
||||
SftpClientFactory.instance()
|
||||
.createSftpClient(initClientSession(), this.sftpVersionSelector);
|
||||
freshSftpClient = true;
|
||||
}
|
||||
sftpSession = new SftpSession(jschSession);
|
||||
JavaUtils.INSTANCE.acceptIfNotNull(this.channelConnectTimeout, sftpSession::setChannelConnectTimeout);
|
||||
sftpSession = new SftpSession(sftpClient);
|
||||
sftpSession.connect();
|
||||
if (this.isSharedSession && freshJschSession) {
|
||||
this.sharedJschSession = jschSession;
|
||||
if (this.isSharedSession && freshSftpClient) {
|
||||
this.sharedSftpClient = sftpClient;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
@@ -392,60 +295,68 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
this.sharedSessionLock.unlock();
|
||||
}
|
||||
}
|
||||
jschSession.addChannel();
|
||||
return sftpSession;
|
||||
}
|
||||
|
||||
private com.jcraft.jsch.Session initJschSession() throws JSchException, IOException {
|
||||
private ClientSession initClientSession() throws IOException {
|
||||
Assert.hasText(this.host, "host must not be empty");
|
||||
Assert.hasText(this.user, "user must not be empty");
|
||||
Assert.isTrue(StringUtils.hasText(this.userInfoWrapper.getPassword()) || this.privateKey != null,
|
||||
"either a password or a private key is required");
|
||||
|
||||
if (this.port <= 0) {
|
||||
this.port = 22; // NOSONAR magic number
|
||||
}
|
||||
if (this.knownHosts != null) {
|
||||
this.jsch.setKnownHosts(this.knownHosts.getInputStream());
|
||||
}
|
||||
initClient();
|
||||
|
||||
// private key
|
||||
if (this.privateKey != null) {
|
||||
byte[] keyByteArray = FileCopyUtils.copyToByteArray(this.privateKey.getInputStream());
|
||||
String passphrase = this.userInfoWrapper.getPassphrase();
|
||||
if (StringUtils.hasText(passphrase)) {
|
||||
this.jsch.addIdentity(this.user, keyByteArray, null, passphrase.getBytes());
|
||||
}
|
||||
else {
|
||||
this.jsch.addIdentity(this.user, keyByteArray, null, null);
|
||||
}
|
||||
Duration verifyTimeout = this.timeout != null ? Duration.ofMillis(this.timeout) : null;
|
||||
HostConfigEntry hostConfig = this.hostConfig;
|
||||
if (hostConfig == null) {
|
||||
hostConfig =
|
||||
new HostConfigEntry(SshdSocketAddress.isIPv6Address(this.host) ? "" : this.host, this.host,
|
||||
this.port, this.user);
|
||||
}
|
||||
com.jcraft.jsch.Session jschSession = this.jsch.getSession(this.user, this.host, this.port);
|
||||
JavaUtils.INSTANCE
|
||||
.acceptIfNotNull(this.sessionConfig, jschSession::setConfig)
|
||||
.acceptIfHasText(this.userInfoWrapper.getPassword(), jschSession::setPassword);
|
||||
jschSession.setUserInfo(this.userInfoWrapper);
|
||||
ClientSession clientSession =
|
||||
this.sshClient.connect(hostConfig)
|
||||
.verify(verifyTimeout)
|
||||
.getSession();
|
||||
|
||||
try {
|
||||
if (this.timeout != null) {
|
||||
jschSession.setTimeout(this.timeout);
|
||||
clientSession.auth().verify(verifyTimeout);
|
||||
|
||||
return clientSession;
|
||||
}
|
||||
|
||||
private void initClient() throws IOException {
|
||||
if (this.initialized.compareAndSet(false, true)) {
|
||||
if (this.port <= 0) {
|
||||
this.port = SshConstants.DEFAULT_PORT;
|
||||
}
|
||||
if (this.serverAliveInterval != null) {
|
||||
jschSession.setServerAliveInterval(this.serverAliveInterval);
|
||||
ServerKeyVerifier serverKeyVerifier =
|
||||
this.allowUnknownKeys ? AcceptAllServerKeyVerifier.INSTANCE : RejectAllServerKeyVerifier.INSTANCE;
|
||||
if (this.knownHosts != null) {
|
||||
serverKeyVerifier = new ResourceKnownHostsServerKeyVerifier(this.knownHosts);
|
||||
}
|
||||
JavaUtils.INSTANCE
|
||||
.acceptIfNotNull(this.proxy, jschSession::setProxy)
|
||||
.acceptIfNotNull(this.socketFactory, jschSession::setSocketFactory)
|
||||
.acceptIfHasText(this.clientVersion, jschSession::setClientVersion)
|
||||
.acceptIfHasText(this.hostKeyAlias, jschSession::setHostKeyAlias)
|
||||
.acceptIfNotNull(this.serverAliveCountMax, jschSession::setServerAliveCountMax)
|
||||
.acceptIfNotNull(this.enableDaemonThread, jschSession::setDaemonThread);
|
||||
this.sshClient.setServerKeyVerifier(serverKeyVerifier);
|
||||
|
||||
this.sshClient.setPasswordIdentityProvider(PasswordIdentityProvider.wrapPasswords(this.password));
|
||||
if (this.privateKey != null) {
|
||||
IoResource<Resource> privateKeyResource =
|
||||
new AbstractIoResource<>(Resource.class, this.privateKey) {
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
return getResourceValue().getInputStream();
|
||||
}
|
||||
};
|
||||
try {
|
||||
Collection<KeyPair> keys =
|
||||
SecurityUtils.getKeyPairResourceParser()
|
||||
.loadKeyPairs(null, privateKeyResource,
|
||||
FilePasswordProvider.of(this.privateKeyPassphrase));
|
||||
this.sshClient.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keys));
|
||||
}
|
||||
catch (GeneralSecurityException ex) {
|
||||
throw new IOException("Cannot load private key: " + this.privateKey.getFilename(), ex);
|
||||
}
|
||||
}
|
||||
this.sshClient.setUserInteraction(this.userInteraction);
|
||||
this.sshClient.start();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new BeanCreationException("Attempt to set additional properties of " +
|
||||
"the com.jcraft.jsch.Session resulted in error: " + e.getMessage(), e);
|
||||
}
|
||||
return jschSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -456,130 +367,7 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
@Override
|
||||
public void resetSharedSession() {
|
||||
Assert.state(this.isSharedSession, "Shared sessions are not being used");
|
||||
this.sharedJschSession = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class will delegate calls to a configured {@link UserInfo}, providing
|
||||
* sensible defaults if null. As the password is configured in this Factory, the
|
||||
* wrapper will return the factory's configured password and only delegate to the
|
||||
* UserInfo if null.
|
||||
*
|
||||
* @since 4.1.7
|
||||
*/
|
||||
private class UserInfoWrapper implements UserInfo, UIKeyboardInteractive {
|
||||
|
||||
UserInfoWrapper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience to check whether enclosing factory's UserInfo is configured.
|
||||
* @return true if there's a delegate.
|
||||
*/
|
||||
private boolean hasDelegate() {
|
||||
return getDelegate() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience to retrieve enclosing factory's UserInfo.
|
||||
* @return the {@link #userInfo} or null if not present.
|
||||
*/
|
||||
private UserInfo getDelegate() {
|
||||
return DefaultSftpSessionFactory.this.userInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassphrase() {
|
||||
if (hasDelegate()) {
|
||||
Assert.state(!StringUtils.hasText(DefaultSftpSessionFactory.this.privateKeyPassphrase),
|
||||
"When a 'UserInfo' is provided, 'privateKeyPassphrase' is not allowed");
|
||||
return getDelegate().getPassphrase();
|
||||
}
|
||||
else {
|
||||
return DefaultSftpSessionFactory.this.privateKeyPassphrase;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
if (hasDelegate()) {
|
||||
Assert.state(!StringUtils.hasText(DefaultSftpSessionFactory.this.password),
|
||||
"When a 'UserInfo' is provided, 'password' is not allowed");
|
||||
return getDelegate().getPassword();
|
||||
}
|
||||
else {
|
||||
return DefaultSftpSessionFactory.this.password;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean promptPassword(String message) {
|
||||
if (hasDelegate()) {
|
||||
return getDelegate().promptPassword(message);
|
||||
}
|
||||
else {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("No UserInfo provided - " + message + ", returning: true");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean promptPassphrase(String message) {
|
||||
if (hasDelegate()) {
|
||||
return getDelegate().promptPassphrase(message);
|
||||
}
|
||||
else {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("No UserInfo provided - " + message + ", returning: true");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean promptYesNo(String message) {
|
||||
LOGGER.info(message);
|
||||
if (hasDelegate()) {
|
||||
return getDelegate().promptYesNo(message);
|
||||
}
|
||||
else {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("No UserInfo provided - " + message + ", returning: "
|
||||
+ DefaultSftpSessionFactory.this.allowUnknownKeys);
|
||||
}
|
||||
return DefaultSftpSessionFactory.this.allowUnknownKeys;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showMessage(String message) {
|
||||
if (hasDelegate()) {
|
||||
getDelegate().showMessage(message);
|
||||
}
|
||||
else {
|
||||
LOGGER.debug(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] promptKeyboardInteractive(String destination, String name, String instruction, String[] prompt,
|
||||
boolean[] echo) {
|
||||
|
||||
if (hasDelegate() && getDelegate() instanceof UIKeyboardInteractive) {
|
||||
return ((UIKeyboardInteractive) getDelegate()).promptKeyboardInteractive(destination, name,
|
||||
instruction, prompt, echo);
|
||||
}
|
||||
else {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("No UIKeyboardInteractive provided - " + destination + ":" + name + ":" + instruction
|
||||
+ ":" + Arrays.asList(prompt) + ":" + Arrays.toString(echo));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
this.sharedSftpClient = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.jcraft.jsch.Session;
|
||||
|
||||
/**
|
||||
* A wrapper for a JSch session that maintains a channel count and
|
||||
* physically disconnects when the last channel is closed.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 3.0
|
||||
*
|
||||
*/
|
||||
class JSchSessionWrapper {
|
||||
|
||||
private final Session session;
|
||||
|
||||
private final AtomicInteger channels = new AtomicInteger();
|
||||
|
||||
JSchSessionWrapper(Session session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public void addChannel() {
|
||||
this.channels.incrementAndGet();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (this.channels.decrementAndGet() <= 0) {
|
||||
this.session.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public final Session getSession() {
|
||||
return this.session;
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return this.session.isConnected();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import com.jcraft.jsch.Logger;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0.1
|
||||
*/
|
||||
class JschLogger implements Logger {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog("com.jcraft.jsch");
|
||||
|
||||
public boolean isEnabled(int level) {
|
||||
switch (level) {
|
||||
case Logger.INFO:
|
||||
return LOGGER.isInfoEnabled();
|
||||
case Logger.WARN:
|
||||
return LOGGER.isWarnEnabled();
|
||||
case Logger.DEBUG:
|
||||
return LOGGER.isDebugEnabled();
|
||||
case Logger.ERROR:
|
||||
return LOGGER.isErrorEnabled();
|
||||
case Logger.FATAL:
|
||||
return LOGGER.isFatalEnabled();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void log(int level, String message) {
|
||||
switch (level) {
|
||||
case Logger.INFO:
|
||||
LOGGER.info(message);
|
||||
break;
|
||||
case Logger.WARN:
|
||||
LOGGER.warn(message);
|
||||
break;
|
||||
case Logger.DEBUG:
|
||||
LOGGER.debug(message);
|
||||
break;
|
||||
case Logger.ERROR:
|
||||
LOGGER.error(message);
|
||||
break;
|
||||
case Logger.FATAL:
|
||||
LOGGER.fatal(message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import org.springframework.beans.factory.config.AbstractFactoryBean;
|
||||
|
||||
import com.jcraft.jsch.Proxy;
|
||||
import com.jcraft.jsch.ProxyHTTP;
|
||||
import com.jcraft.jsch.ProxySOCKS4;
|
||||
import com.jcraft.jsch.ProxySOCKS5;
|
||||
|
||||
/**
|
||||
* Spring-friendly factory bean to create Jsch {@link Proxy} objects.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 4.3
|
||||
*
|
||||
*/
|
||||
public class JschProxyFactoryBean extends AbstractFactoryBean<Proxy> {
|
||||
|
||||
public enum Type {
|
||||
HTTP, SOCKS4, SOCKS5
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
|
||||
private final String host;
|
||||
|
||||
private final int port;
|
||||
|
||||
private final String user;
|
||||
|
||||
private final String password;
|
||||
|
||||
public JschProxyFactoryBean(Type type, String host, int port, String user, String password) {
|
||||
this.type = type;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
switch (this.type) {
|
||||
case SOCKS5:
|
||||
return ProxySOCKS5.class;
|
||||
case SOCKS4:
|
||||
return ProxySOCKS4.class;
|
||||
case HTTP:
|
||||
return ProxyHTTP.class;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid type:" + this.type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Proxy createInstance() {
|
||||
switch (this.type) {
|
||||
case SOCKS5:
|
||||
ProxySOCKS5 socks5proxy = new ProxySOCKS5(this.host, this.port);
|
||||
socks5proxy.setUserPasswd(this.user, this.password);
|
||||
return socks5proxy;
|
||||
case SOCKS4:
|
||||
ProxySOCKS4 socks4proxy = new ProxySOCKS4(this.host, this.port);
|
||||
socks4proxy.setUserPasswd(this.user, this.password);
|
||||
return socks4proxy;
|
||||
case HTTP:
|
||||
ProxyHTTP httpProxy = new ProxyHTTP(this.host, this.port);
|
||||
httpProxy.setUserPasswd(this.user, this.password);
|
||||
return httpProxy;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid type:" + this.type);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.sshd.client.config.hosts.KnownHostEntry;
|
||||
import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier;
|
||||
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
|
||||
import org.apache.sshd.client.session.ClientSession;
|
||||
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
|
||||
import org.apache.sshd.common.config.keys.KeyUtils;
|
||||
import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
|
||||
import org.apache.sshd.common.util.GenericUtils;
|
||||
import org.apache.sshd.common.util.net.SshdSocketAddress;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link ServerKeyVerifier} implementation for a {@link Resource} abstraction.
|
||||
* The logic is similar to the {@link KnownHostsServerKeyVerifier}, but in read-only mode.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 6.0
|
||||
*
|
||||
* @see KnownHostsServerKeyVerifier
|
||||
*/
|
||||
public class ResourceKnownHostsServerKeyVerifier implements ServerKeyVerifier {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ResourceKnownHostsServerKeyVerifier.class);
|
||||
|
||||
private final Supplier<Collection<KnownHostsServerKeyVerifier.HostEntryPair>> keysSupplier;
|
||||
|
||||
public ResourceKnownHostsServerKeyVerifier(Resource knownHostsResource) {
|
||||
Assert.notNull(knownHostsResource, "'knownHostsResource' must not be null");
|
||||
this.keysSupplier = GenericUtils.memoizeLock(getKnownHostSupplier(knownHostsResource));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyServerKey(ClientSession clientSession, SocketAddress remoteAddress, PublicKey serverKey) {
|
||||
Collection<KnownHostsServerKeyVerifier.HostEntryPair> knownHosts = this.keysSupplier.get();
|
||||
KnownHostsServerKeyVerifier.HostEntryPair match = findKnownHostEntry(clientSession, remoteAddress, knownHosts);
|
||||
if (match == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
KnownHostEntry entry = match.getHostEntry();
|
||||
PublicKey expected = match.getServerKey();
|
||||
if (KeyUtils.compareKeys(expected, serverKey)) {
|
||||
return !"revoked".equals(entry.getMarker());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Supplier<Collection<KnownHostsServerKeyVerifier.HostEntryPair>> getKnownHostSupplier(
|
||||
Resource knownHostsResource) {
|
||||
|
||||
return () -> {
|
||||
try {
|
||||
Collection<KnownHostEntry> entries =
|
||||
KnownHostEntry.readKnownHostEntries(knownHostsResource.getInputStream(), true);
|
||||
List<KnownHostsServerKeyVerifier.HostEntryPair> keys = new ArrayList<>(entries.size());
|
||||
for (KnownHostEntry entry : entries) {
|
||||
keys.add(new KnownHostsServerKeyVerifier.HostEntryPair(entry, resolveHostKey(entry)));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.warn("Known hosts cannot be loaded from the: " + knownHostsResource, ex);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static PublicKey resolveHostKey(KnownHostEntry entry) throws IOException, GeneralSecurityException {
|
||||
AuthorizedKeyEntry authEntry = entry.getKeyEntry();
|
||||
Assert.notNull(authEntry, () -> "No key extracted from " + entry);
|
||||
return authEntry.resolvePublicKey(null, PublicKeyEntryResolver.IGNORING);
|
||||
}
|
||||
|
||||
private static KnownHostsServerKeyVerifier.HostEntryPair findKnownHostEntry(
|
||||
ClientSession clientSession, SocketAddress remoteAddress,
|
||||
Collection<KnownHostsServerKeyVerifier.HostEntryPair> knownHosts) {
|
||||
|
||||
Collection<SshdSocketAddress> candidates = resolveHostNetworkIdentities(clientSession, remoteAddress);
|
||||
|
||||
if (GenericUtils.isEmpty(candidates)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (KnownHostsServerKeyVerifier.HostEntryPair match : knownHosts) {
|
||||
KnownHostEntry entry = match.getHostEntry();
|
||||
for (SshdSocketAddress host : candidates) {
|
||||
if (entry.isHostMatch(host.getHostName(), host.getPort())) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null; // no match found
|
||||
}
|
||||
|
||||
private static Collection<SshdSocketAddress> resolveHostNetworkIdentities(
|
||||
ClientSession clientSession, SocketAddress remoteAddress) {
|
||||
/*
|
||||
* NOTE !!! we do not resolve the fully-qualified name to avoid long DNS timeouts. Instead we use the reported
|
||||
* peer address and the original connection target host
|
||||
*/
|
||||
Collection<SshdSocketAddress> candidates = new TreeSet<>(SshdSocketAddress.BY_HOST_AND_PORT);
|
||||
candidates.add(SshdSocketAddress.toSshdSocketAddress(remoteAddress));
|
||||
SocketAddress connectAddress = clientSession.getConnectAddress();
|
||||
candidates.add(SshdSocketAddress.toSshdSocketAddress(connectAddress));
|
||||
return candidates;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,12 +16,14 @@
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.common.SftpHelper;
|
||||
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.integration.file.remote.FileInfo} implementation for SFTP.
|
||||
*
|
||||
@@ -30,37 +32,37 @@ import com.jcraft.jsch.SftpATTRS;
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public class SftpFileInfo extends AbstractFileInfo<LsEntry> {
|
||||
public class SftpFileInfo extends AbstractFileInfo<SftpClient.DirEntry> {
|
||||
|
||||
private final LsEntry lsEntry;
|
||||
private final SftpClient.DirEntry lsEntry;
|
||||
|
||||
private final SftpATTRS attrs;
|
||||
private final SftpClient.Attributes attrs;
|
||||
|
||||
|
||||
public SftpFileInfo(LsEntry lsEntry) {
|
||||
public SftpFileInfo(SftpClient.DirEntry lsEntry) {
|
||||
Assert.notNull(lsEntry, "'lsEntry' must not be null");
|
||||
this.lsEntry = lsEntry;
|
||||
this.attrs = lsEntry.getAttrs();
|
||||
this.attrs = lsEntry.getAttributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SftpATTRS#isDir()
|
||||
* @see SftpClient.Attributes#isDirectory()
|
||||
*/
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return this.attrs.isDir();
|
||||
return this.attrs.isDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SftpATTRS#isLink()
|
||||
* @see SftpClient.Attributes#isSymbolicLink()
|
||||
*/
|
||||
@Override
|
||||
public boolean isLink() {
|
||||
return this.attrs.isLink();
|
||||
return this.attrs.isSymbolicLink();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SftpATTRS#getSize()
|
||||
* @see SftpClient.Attributes#getSize()
|
||||
*/
|
||||
@Override
|
||||
public long getSize() {
|
||||
@@ -68,15 +70,15 @@ public class SftpFileInfo extends AbstractFileInfo<LsEntry> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SftpATTRS#getMTime()
|
||||
* @see SftpClient.Attributes#getModifyTime()
|
||||
*/
|
||||
@Override
|
||||
public long getModified() {
|
||||
return ((long) this.attrs.getMTime()) * 1000; // NOSONAR magic number
|
||||
return this.attrs.getModifyTime().toMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see LsEntry#getFilename()
|
||||
* @see SftpClient.DirEntry#getFilename()
|
||||
*/
|
||||
@Override
|
||||
public String getFilename() {
|
||||
@@ -85,11 +87,11 @@ public class SftpFileInfo extends AbstractFileInfo<LsEntry> {
|
||||
|
||||
@Override
|
||||
public String getPermissions() {
|
||||
return this.attrs.getPermissionsString();
|
||||
return PosixFilePermissions.toString(SftpHelper.permissionsToAttributes(this.attrs.getPermissions()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LsEntry getFileInfo() {
|
||||
public SftpClient.DirEntry getFileInfo() {
|
||||
return this.lsEntry;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,35 +16,36 @@
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.remote.ClientCallback;
|
||||
import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* SFTP version of {@code RemoteFileTemplate} providing type-safe access to
|
||||
* the underlying ChannelSftp object.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artme Bilan
|
||||
*
|
||||
* @since 4.1
|
||||
*
|
||||
*/
|
||||
public class SftpRemoteFileTemplate extends RemoteFileTemplate<LsEntry> {
|
||||
public class SftpRemoteFileTemplate extends RemoteFileTemplate<SftpClient.DirEntry> {
|
||||
|
||||
public SftpRemoteFileTemplate(SessionFactory<LsEntry> sessionFactory) {
|
||||
public SftpRemoteFileTemplate(SessionFactory<SftpClient.DirEntry> sessionFactory) {
|
||||
super(sessionFactory);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T, C> T executeWithClient(final ClientCallback<C, T> callback) {
|
||||
return doExecuteWithClient((ClientCallback<ChannelSftp, T>) callback);
|
||||
return doExecuteWithClient((ClientCallback<SftpClient, T>) callback);
|
||||
}
|
||||
|
||||
protected <T> T doExecuteWithClient(final ClientCallback<ChannelSftp, T> callback) {
|
||||
return execute(session -> callback.doWithClient((ChannelSftp) session.getClientInstance()));
|
||||
protected <T> T doExecuteWithClient(final ClientCallback<SftpClient, T> callback) {
|
||||
return execute(session -> callback.doWithClient((SftpClient) session.getClientInstance()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,22 +21,19 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.sshd.sftp.SftpModuleProperties;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.common.SftpConstants;
|
||||
import org.apache.sshd.sftp.common.SftpException;
|
||||
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
import com.jcraft.jsch.SftpException;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Default SFTP {@link Session} implementation. Wraps a JSCH session instance.
|
||||
@@ -50,115 +47,66 @@ import com.jcraft.jsch.SftpException;
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SftpSession implements Session<LsEntry> {
|
||||
public class SftpSession implements Session<SftpClient.DirEntry> {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(SftpSession.class);
|
||||
private final SftpClient sftpClient;
|
||||
|
||||
private static final String SESSION_IS_NOT_CONNECTED = "session is not connected";
|
||||
|
||||
private static final Duration DEFAULT_CHANNEL_CONNECT_TIMEOUT = Duration.ofSeconds(5);
|
||||
|
||||
private final com.jcraft.jsch.Session jschSession;
|
||||
|
||||
private final JSchSessionWrapper wrapper;
|
||||
|
||||
private int channelConnectTimeout = (int) DEFAULT_CHANNEL_CONNECT_TIMEOUT.toMillis();
|
||||
|
||||
private volatile ChannelSftp channel;
|
||||
|
||||
private volatile boolean closed;
|
||||
|
||||
|
||||
public SftpSession(com.jcraft.jsch.Session jschSession) {
|
||||
Assert.notNull(jschSession, "jschSession must not be null");
|
||||
this.jschSession = jschSession;
|
||||
this.wrapper = null;
|
||||
}
|
||||
|
||||
public SftpSession(JSchSessionWrapper wrapper) {
|
||||
Assert.notNull(wrapper, "wrapper must not be null");
|
||||
this.jschSession = wrapper.getSession();
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connect timeout.
|
||||
* @param timeout the timeout to set.
|
||||
* @since 5.2
|
||||
*/
|
||||
public void setChannelConnectTimeout(Duration timeout) {
|
||||
Assert.notNull(timeout, "'timeout' cannot be null");
|
||||
this.channelConnectTimeout = (int) timeout.toMillis();
|
||||
public SftpSession(SftpClient sftpClient) {
|
||||
Assert.notNull(sftpClient, "'sftpClient' must not be null");
|
||||
this.sftpClient = sftpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(String path) throws IOException {
|
||||
Assert.state(this.channel != null, SESSION_IS_NOT_CONNECTED);
|
||||
try {
|
||||
this.channel.rm(path);
|
||||
return true;
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
throw new IOException("Failed to remove file.", ex);
|
||||
}
|
||||
this.sftpClient.remove(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LsEntry[] list(String path) throws IOException {
|
||||
Assert.state(this.channel != null, SESSION_IS_NOT_CONNECTED);
|
||||
try {
|
||||
Vector<?> lsEntries = this.channel.ls(path); // NOSONAR (Vector)
|
||||
if (lsEntries != null) {
|
||||
LsEntry[] entries = new LsEntry[lsEntries.size()];
|
||||
for (int i = 0; i < lsEntries.size(); i++) {
|
||||
Object next = lsEntries.get(i);
|
||||
Assert.state(next instanceof LsEntry, "expected only LsEntry instances from channel.ls()");
|
||||
entries[i] = (LsEntry) next;
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
throw new IOException("Failed to list files", ex);
|
||||
}
|
||||
return new LsEntry[0];
|
||||
public SftpClient.DirEntry[] list(String path) throws IOException {
|
||||
return doList(path)
|
||||
.toArray(SftpClient.DirEntry[]::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] listNames(String path) throws IOException {
|
||||
LsEntry[] entries = this.list(path);
|
||||
List<String> names = new ArrayList<>();
|
||||
for (LsEntry entry : entries) {
|
||||
String fileName = entry.getFilename();
|
||||
SftpATTRS attrs = entry.getAttrs();
|
||||
if (!attrs.isDir() && !attrs.isLink()) {
|
||||
names.add(fileName);
|
||||
}
|
||||
}
|
||||
return names.toArray(new String[0]);
|
||||
return doList(path)
|
||||
.map(SftpClient.DirEntry::getFilename)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public Stream<SftpClient.DirEntry> doList(String path) throws IOException {
|
||||
String remotePath = StringUtils.trimTrailingCharacter(StringUtils.trimLeadingCharacter(path, '/'), '/');
|
||||
String remoteDir = remotePath;
|
||||
int lastIndex = remotePath.lastIndexOf('/');
|
||||
if (lastIndex > 0) {
|
||||
remoteDir = remoteDir.substring(0, lastIndex);
|
||||
}
|
||||
String remoteFile = lastIndex > 0 ? remotePath.substring(lastIndex + 1) : null;
|
||||
boolean isPattern = remoteFile != null && remoteFile.contains("*");
|
||||
|
||||
if (!isPattern && remoteFile != null) {
|
||||
SftpClient.Attributes attributes = this.sftpClient.lstat(path);
|
||||
if (!attributes.isDirectory()) {
|
||||
return Stream.of(new SftpClient.DirEntry(remoteFile, path, attributes));
|
||||
}
|
||||
else {
|
||||
remoteDir = remotePath;
|
||||
}
|
||||
}
|
||||
return StreamSupport.stream(this.sftpClient.readDir(remoteDir).spliterator(), false)
|
||||
.filter((entry) -> !isPattern || PatternMatchUtils.simpleMatch(remoteFile, entry.getFilename()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(String source, OutputStream os) throws IOException {
|
||||
Assert.state(this.channel != null, SESSION_IS_NOT_CONNECTED);
|
||||
try {
|
||||
InputStream is = this.channel.get(source);
|
||||
FileCopyUtils.copy(is, os);
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
throw new IOException("failed to read file " + source, ex);
|
||||
}
|
||||
InputStream is = this.sftpClient.read(source);
|
||||
FileCopyUtils.copy(is, os);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream readRaw(String source) throws IOException {
|
||||
try {
|
||||
return this.channel.get(source);
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
throw new IOException("failed to read file " + source, ex);
|
||||
}
|
||||
return this.sftpClient.read(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -168,149 +116,91 @@ public class SftpSession implements Session<LsEntry> {
|
||||
|
||||
@Override
|
||||
public void write(InputStream inputStream, String destination) throws IOException {
|
||||
Assert.state(this.channel != null, SESSION_IS_NOT_CONNECTED);
|
||||
try {
|
||||
this.channel.put(inputStream, destination);
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
throw new IOException("failed to write file", ex);
|
||||
synchronized (this.sftpClient) {
|
||||
OutputStream outputStream = this.sftpClient.write(destination);
|
||||
FileCopyUtils.copy(inputStream, outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(InputStream inputStream, String destination) throws IOException {
|
||||
Assert.state(this.channel != null, SESSION_IS_NOT_CONNECTED);
|
||||
try {
|
||||
this.channel.put(inputStream, destination, ChannelSftp.APPEND);
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
throw new IOException("failed to write file", ex);
|
||||
synchronized (this.sftpClient) {
|
||||
OutputStream outputStream =
|
||||
this.sftpClient.write(destination, SftpClient.OpenMode.Create, SftpClient.OpenMode.Append);
|
||||
FileCopyUtils.copy(inputStream, outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.closed = true;
|
||||
if (this.wrapper != null) {
|
||||
if (this.channel != null) {
|
||||
this.channel.disconnect();
|
||||
}
|
||||
this.wrapper.close();
|
||||
try {
|
||||
this.sftpClient.close();
|
||||
}
|
||||
else {
|
||||
if (this.jschSession.isConnected()) {
|
||||
this.jschSession.disconnect();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException("failed to close an SFTP client", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return !this.closed && this.jschSession.isConnected();
|
||||
return this.sftpClient.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(String pathFrom, String pathTo) throws IOException {
|
||||
try {
|
||||
this.channel.rename(pathFrom, pathTo);
|
||||
}
|
||||
catch (SftpException sftpex) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Initial File rename failed, possibly because file already exists. " +
|
||||
"Will attempt to delete file: " + pathTo + " and execute rename again.");
|
||||
}
|
||||
try {
|
||||
remove(pathTo);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Delete file: " + pathTo + " succeeded. Will attempt rename again");
|
||||
}
|
||||
}
|
||||
catch (IOException ioex) {
|
||||
IOException exception = new IOException("Failed to delete file " + pathTo, sftpex);
|
||||
exception.addSuppressed(ioex);
|
||||
throw exception;
|
||||
}
|
||||
try {
|
||||
// attempt to rename again
|
||||
this.channel.rename(pathFrom, pathTo);
|
||||
}
|
||||
catch (SftpException sftpex2) {
|
||||
IOException exception =
|
||||
new IOException("failed to rename from " + pathFrom + " to " + pathTo, sftpex);
|
||||
exception.addSuppressed(sftpex2);
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("File: " + pathFrom + " was successfully renamed to " + pathTo);
|
||||
}
|
||||
this.sftpClient.rename(pathFrom, pathTo, SftpClient.CopyMode.Overwrite);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mkdir(String remoteDirectory) throws IOException {
|
||||
try {
|
||||
this.channel.mkdir(remoteDirectory);
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
if (ex.id != ChannelSftp.SSH_FX_FAILURE || !exists(remoteDirectory)) {
|
||||
throw new IOException("failed to create remote directory '" + remoteDirectory + "'.", ex);
|
||||
}
|
||||
}
|
||||
this.sftpClient.mkdir(remoteDirectory);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rmdir(String remoteDirectory) throws IOException {
|
||||
try {
|
||||
this.channel.rmdir(remoteDirectory);
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
throw new IOException("failed to remove remote directory '" + remoteDirectory + "'.", ex);
|
||||
}
|
||||
this.sftpClient.rmdir(remoteDirectory);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String path) {
|
||||
try {
|
||||
this.channel.lstat(path);
|
||||
this.sftpClient.lstat(path);
|
||||
return true;
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
if (ex.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
|
||||
catch (IOException ex) {
|
||||
if (ex instanceof SftpException sftpException &&
|
||||
SftpConstants.SSH_FX_NO_SUCH_FILE == sftpException.getStatus()) {
|
||||
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
throw new UncheckedIOException("Cannot check 'lstat' for path " + path,
|
||||
new IOException(ex));
|
||||
}
|
||||
throw new UncheckedIOException("Cannot check 'lstat' for path " + path, ex);
|
||||
}
|
||||
}
|
||||
|
||||
void connect() {
|
||||
try {
|
||||
if (!this.jschSession.isConnected()) {
|
||||
this.jschSession.connect();
|
||||
}
|
||||
this.channel = (ChannelSftp) this.jschSession.openChannel("sftp");
|
||||
if (this.channel != null && !this.channel.isConnected()) {
|
||||
this.channel.connect(this.channelConnectTimeout);
|
||||
if (!this.sftpClient.isOpen()) {
|
||||
Duration initializationTimeout =
|
||||
SftpModuleProperties.SFTP_CHANNEL_OPEN_TIMEOUT.getRequired(this.sftpClient.getSession());
|
||||
this.sftpClient.getClientChannel().open().verify(initializationTimeout);
|
||||
}
|
||||
}
|
||||
catch (JSchException e) {
|
||||
this.close();
|
||||
throw new IllegalStateException("failed to connect", e);
|
||||
catch (IOException ex) {
|
||||
close();
|
||||
throw new UncheckedIOException("failed to connect an SFTP client", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelSftp getClientInstance() {
|
||||
return this.channel;
|
||||
public SftpClient getClientInstance() {
|
||||
return this.sftpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHostPort() {
|
||||
return this.jschSession.getHost() + ':' + this.jschSession.getPort();
|
||||
return this.sftpClient.getSession().getConnectAddress().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -320,10 +210,10 @@ public class SftpSession implements Session<LsEntry> {
|
||||
|
||||
private boolean doTest() {
|
||||
try {
|
||||
this.channel.lstat(this.channel.getHome());
|
||||
this.sftpClient.canonicalPath("");
|
||||
return true;
|
||||
}
|
||||
catch (@SuppressWarnings("unused") Exception e) {
|
||||
catch (@SuppressWarnings("unused") Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.support;
|
||||
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
|
||||
/**
|
||||
* Simple runtime exception to wrap an SftpException.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 4.3
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class GeneralSftpException extends NestedRuntimeException {
|
||||
|
||||
public GeneralSftpException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Provides general support classes for sftp.
|
||||
*/
|
||||
package org.springframework.integration.sftp.support;
|
||||
@@ -1,30 +0,0 @@
|
||||
JSch 0.0.* was released under the GNU LGPL license. Later, we have switched
|
||||
over to a BSD-style license.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
Copyright (c) 2002,2003,2004,2005,2006,2007,2008 Atsuhiko Yamanaka, JCraft,Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-2021 the original author or authors.
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,7 @@ import java.util.Collections;
|
||||
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
|
||||
import org.apache.sshd.server.SshServer;
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
@@ -32,8 +33,6 @@ import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.sftp.server.ApacheMinaSftpEventListener;
|
||||
import org.springframework.integration.sftp.session.DefaultSftpSessionFactory;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* Provides an embedded SFTP Server for test cases.
|
||||
*
|
||||
@@ -77,14 +76,16 @@ public class SftpTestSupport extends RemoteFileTestSupport {
|
||||
port = server.getPort();
|
||||
}
|
||||
|
||||
public static SessionFactory<LsEntry> sessionFactory() {
|
||||
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true);
|
||||
public static SessionFactory<SftpClient.DirEntry> sessionFactory() {
|
||||
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(false);
|
||||
factory.setHost("localhost");
|
||||
factory.setPort(port);
|
||||
factory.setUser("foo");
|
||||
factory.setPassword("foo");
|
||||
factory.setAllowUnknownKeys(true);
|
||||
return new CachingSessionFactory<>(factory);
|
||||
CachingSessionFactory<SftpClient.DirEntry> cachingSessionFactory = new CachingSessionFactory<>(factory);
|
||||
cachingSessionFactory.setTestSession(true);
|
||||
return cachingSessionFactory;
|
||||
}
|
||||
|
||||
public static ApacheMinaSftpEventListener eventListener() {
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
<beans:property name="password" value="hello"/>
|
||||
<beans:property name="port" value="2222"/>
|
||||
<beans:property name="user" value="oleg"/>
|
||||
<beans:property name="sessionConfig" ref="sessionConfig"/>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="csf" class="org.springframework.integration.file.remote.session.CachingSessionFactory">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,8 +18,6 @@ package org.springframework.integration.sftp.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -38,22 +36,23 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
@DirtiesContext
|
||||
public class InboundChannelAdapterParserCachingTests {
|
||||
|
||||
@Autowired private Object cachingAdapter;
|
||||
@Autowired
|
||||
private Object cachingAdapter;
|
||||
|
||||
@Autowired private Object nonCachingAdapter;
|
||||
@Autowired
|
||||
private Object nonCachingAdapter;
|
||||
|
||||
@Test
|
||||
public void cachingAdapter() {
|
||||
Object sessionFactory = TestUtils.getPropertyValue(cachingAdapter, "source.synchronizer.remoteFileTemplate.sessionFactory");
|
||||
Object sessionFactory =
|
||||
TestUtils.getPropertyValue(cachingAdapter, "source.synchronizer.remoteFileTemplate.sessionFactory");
|
||||
assertThat(sessionFactory.getClass()).isEqualTo(CachingSessionFactory.class);
|
||||
Properties sessionConfig = TestUtils.getPropertyValue(sessionFactory, "sessionFactory.sessionConfig", Properties.class);
|
||||
assertThat(sessionConfig).isNotNull();
|
||||
assertThat(sessionConfig.getProperty("StrictHostKeyChecking")).isEqualTo("no");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonCachingAdapter() {
|
||||
Object sessionFactory = TestUtils.getPropertyValue(nonCachingAdapter, "source.synchronizer.remoteFileTemplate.sessionFactory");
|
||||
Object sessionFactory =
|
||||
TestUtils.getPropertyValue(nonCachingAdapter, "source.synchronizer.remoteFileTemplate.sessionFactory");
|
||||
assertThat(sessionFactory.getClass()).isEqualTo(DefaultSftpSessionFactory.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans
|
||||
xmlns="http://www.springframework.org/schema/integration"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:sftp="http://www.springframework.org/schema/integration/sftp"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
|
||||
|
||||
|
||||
|
||||
http://www.springframework.org/schema/integration/sftp https://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd">
|
||||
|
||||
<channel id="requestChannel"/>
|
||||
|
||||
<beans:bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.SimpleSftpSessionFactory">
|
||||
<beans:property name="host" value="localhost"/>
|
||||
<beans:property name="knownHostsResource" value="local, foo.com, bar.foo"/>
|
||||
<beans:property name="privateKey" value="classpath:org/springframework/integration/sftp/config/sftpTest"/>
|
||||
<beans:property name="privateKeyPassphrase" value="ghj"/>
|
||||
<beans:property name="password" value="hello"/>
|
||||
<beans:property name="port" value="2222"/>
|
||||
<beans:property name="user" value="oleg"/>
|
||||
</beans:bean>
|
||||
|
||||
<sftp:inbound-channel-adapter id="sftpAdapterNoAutoCreate"
|
||||
channel="requestChannel"
|
||||
session-factory="sftpSessionFactory"
|
||||
filter="filter"
|
||||
filename-pattern="."
|
||||
auto-startup="false"
|
||||
remote-directory="/foo"
|
||||
local-directory="file:local-test-dir"
|
||||
auto-create-local-directory="false"
|
||||
auto-delete-remote-files-on-sync="false">
|
||||
<poller fixed-rate="1000"/>
|
||||
</sftp:inbound-channel-adapter>
|
||||
|
||||
<beans:bean id="filter" class="org.springframework.integration.sftp.filters.SftpPatternMatchingFileListFilter">
|
||||
<beans:constructor-arg value="."/>
|
||||
</beans:bean>
|
||||
|
||||
</beans:beans>
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,7 +29,6 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
@@ -126,16 +125,6 @@ public class InboundChannelAdapterParserTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
//exactly one of 'filename-pattern' or 'filter' is allowed on SFTP inbound adapter
|
||||
public void testFailWithFilePatternAndFilter() {
|
||||
assertThat(!new File("target/bar").exists()).isTrue();
|
||||
assertThatExceptionOfType(BeanDefinitionStoreException.class)
|
||||
.isThrownBy(() ->
|
||||
new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context-fail.xml",
|
||||
getClass()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalDirAutoCreated() {
|
||||
assertThat(new File("foo").exists()).isFalse();
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans
|
||||
xmlns="http://www.springframework.org/schema/integration"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:sftp="http://www.springframework.org/schema/integration/sftp"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/sftp https://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd">
|
||||
|
||||
<channel id="inboundFilesChannel"/>
|
||||
|
||||
<beans:bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
|
||||
<beans:property name="host" value="localhost"/>
|
||||
<beans:property name="knownHostsResource"
|
||||
value="#{ new org.springframework.core.io.ByteArrayResource('local, foo.com, bar.foo'.bytes)}"/>
|
||||
<beans:property name="privateKey" value="classpath:org/springframework/integration/sftp/config/sftpTest"/>
|
||||
<beans:property name="privateKeyPassphrase" value="ghj"/>
|
||||
<beans:property name="password" value="hello"/>
|
||||
<beans:property name="port" value="2222"/>
|
||||
<beans:property name="user" value="oleg"/>
|
||||
</beans:bean>
|
||||
|
||||
<sftp:inbound-channel-adapter id="sftpAdapter"
|
||||
session-factory="sftpSessionFactory"
|
||||
auto-startup="false"
|
||||
remote-directory="/hello"
|
||||
local-directory="file:local-test-dir"
|
||||
channel="inboundFilesChannel"
|
||||
filename-pattern=".*?jpg"
|
||||
auto-create-local-directory="true"
|
||||
delete-remote-files="true">
|
||||
<poller fixed-rate="1000" />
|
||||
</sftp:inbound-channel-adapter>
|
||||
|
||||
</beans:beans>
|
||||
@@ -1,34 +0,0 @@
|
||||
<?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/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/sftp https://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd">
|
||||
|
||||
<bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.SimpleSftpSessionFactory">
|
||||
<property name="host" value="localhost"/>
|
||||
<property name="knownHostsResource" value="local, foo.com, bar.foo"/>
|
||||
<property name="privateKey" value="classpath:org/springframework/integration/sftp/config/sftpTest"/>
|
||||
<property name="privateKeyPassphrase" value="ghj"/>
|
||||
<property name="password" value="hello"/>
|
||||
<property name="port" value="2222"/>
|
||||
<property name="user" value="oleg"/>
|
||||
</bean>
|
||||
|
||||
<int:channel id="inputChannel"/>
|
||||
|
||||
<int-sftp:outbound-channel-adapter id="sftpOutboundAdapterWithExpression"
|
||||
session-factory="sftpSessionFactory"
|
||||
channel="inputChannel"
|
||||
charset="UTF-8"
|
||||
remote-filename-generator="fileNameGenerator"
|
||||
remote-directory-expression="'foo' + '/' + 'bar'"
|
||||
remote-filename-generator-expression="payload.getName() + '-foo'"/>
|
||||
|
||||
|
||||
<bean id="fileNameGenerator" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.springframework.integration.file.FileNameGenerator"/>
|
||||
</bean>
|
||||
</beans>
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -157,15 +157,6 @@ public class OutboundChannelAdapterParserTests {
|
||||
.withMessageContaining("Only one of 'remote-directory'");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailWithFileExpressionAndFileGenerator() {
|
||||
assertThatExceptionOfType(BeanDefinitionStoreException.class)
|
||||
.isThrownBy(() ->
|
||||
new ClassPathXmlApplicationContext(
|
||||
"OutboundChannelAdapterParserTests-context-fail-fileFileGen.xml",
|
||||
getClass()));
|
||||
}
|
||||
|
||||
public static class FooAdvice extends AbstractRequestHandlerAdvice {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousy
|
||||
* @author Gunnar Hillert
|
||||
*
|
||||
*/
|
||||
public class SftpInboundOutboundSanitySample {
|
||||
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testInbound() throws Exception {
|
||||
File fileA = new File("local-test-dir/a.test");
|
||||
if (fileA.exists()) {
|
||||
fileA.delete();
|
||||
}
|
||||
File fileB = new File("local-test-dir/b.test");
|
||||
if (fileB.exists()) {
|
||||
fileB.delete();
|
||||
}
|
||||
fileA = new File("remote-target-dir/a.test-foo");
|
||||
if (fileA.exists()) {
|
||||
fileA.delete();
|
||||
}
|
||||
fileB = new File("remote-target-dir/b.test-foo");
|
||||
if (fileB.exists()) {
|
||||
fileB.delete();
|
||||
}
|
||||
|
||||
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
|
||||
"SftpInboundReceiveSample-ignored.xml", this.getClass());
|
||||
Thread.sleep(5000);
|
||||
fileA = new File("local-test-dir/a.test");
|
||||
fileB = new File("local-test-dir/b.test");
|
||||
assertThat(fileA.exists()).isTrue();
|
||||
assertThat(fileB.exists()).isTrue();
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testOutbound() throws Exception {
|
||||
ClassPathXmlApplicationContext ac =
|
||||
new ClassPathXmlApplicationContext("SftpOutboundTransferSample-ignored.xml", this.getClass());
|
||||
File fileA = new File("local-test-dir/a.test");
|
||||
File fileB = new File("local-test-dir/b.test");
|
||||
MessageChannel ftpChannel = ac.getBean("ftpChannel", MessageChannel.class);
|
||||
ftpChannel.send(new GenericMessage<File>(fileA));
|
||||
ftpChannel.send(new GenericMessage<File>(fileB));
|
||||
Thread.sleep(6000);
|
||||
fileA = new File("remote-target-dir/a.test-foo");
|
||||
fileB = new File("remote-target-dir/b.test-foo");
|
||||
assertThat(fileA.exists()).isTrue();
|
||||
assertThat(fileB.exists()).isTrue();
|
||||
ac.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?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"
|
||||
xmlns:task="http://www.springframework.org/schema/task"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/task https://www.springframework.org/schema/task/spring-task.xsd
|
||||
http://www.springframework.org/schema/integration/sftp https://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd">
|
||||
|
||||
<bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
|
||||
<property name="host" value="localhost"/>
|
||||
<property name="privateKey" value="classpath:org/springframework/integration/sftp/config/sftp_rsa"/>
|
||||
<property name="privateKeyPassphrase" value="springintegration"/>
|
||||
<property name="port" value="22"/>
|
||||
<property name="user" value="ozhurakousky"/>
|
||||
</bean>
|
||||
|
||||
<int-sftp:inbound-channel-adapter id="sftpInbondAdapter"
|
||||
channel="receiveChannel"
|
||||
session-factory="sftpSessionFactory"
|
||||
local-directory="file:local-test-dir"
|
||||
remote-directory="/Users/ozhurakousky/workspace-sts-2.3.3.M2/si/spring-integration/spring-integration-sftp/remote-test-dir"
|
||||
auto-startup="true"
|
||||
temporary-file-suffix=".foo"
|
||||
delete-remote-files="false"
|
||||
filename-regex=".*\.test$">
|
||||
<int:poller fixed-rate="3000" max-messages-per-poll="1" />
|
||||
</int-sftp:inbound-channel-adapter>
|
||||
|
||||
<int:channel id="receiveChannel">
|
||||
<int:queue/>
|
||||
</int:channel>
|
||||
|
||||
<task:executor id="executor" pool-size="15"/>
|
||||
|
||||
</beans>
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gunnar Hillert
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class SftpMessageHistoryTests {
|
||||
|
||||
@Test
|
||||
public void testMessageHistoryCompliance() {
|
||||
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("MessageHistory-context.xml",
|
||||
SftpMessageHistoryTests.class);
|
||||
SourcePollingChannelAdapter spca = ac.getBean("sftpAdapter", SourcePollingChannelAdapter.class);
|
||||
assertThat(spca.getComponentName()).isEqualTo("sftpAdapter");
|
||||
assertThat(spca.getComponentType()).isEqualTo("sftp:inbound-channel-adapter");
|
||||
ac.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?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/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/sftp https://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd">
|
||||
|
||||
<bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
|
||||
<property name="host" value="localhost"/>
|
||||
<!-- <property name="privateKey" value="classpath:org/springframework/integration/sftp/config/sftp_rsa"/> -->
|
||||
<!-- <property name="privateKeyPassphrase" value="springintegration"/> -->
|
||||
<property name="port" value="22"/>
|
||||
<property name="user" value="ozhurakousky"/>
|
||||
<property name="password" value="password"/>
|
||||
</bean>
|
||||
|
||||
<int:channel id="ftpChannel"/>
|
||||
|
||||
<int-sftp:outbound-channel-adapter id="sftpOutboundAdapter"
|
||||
session-factory="sftpSessionFactory"
|
||||
channel="ftpChannel"
|
||||
charset="UTF-8"
|
||||
temporary-file-suffix=".foo"
|
||||
remote-filename-generator-expression="payload.getName() + '-foo'"
|
||||
auto-create-directory="true"
|
||||
remote-directory="spring-integration-sftp/remote-target-dir/bar/baz"/>
|
||||
|
||||
</beans>
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.config;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
*/
|
||||
public class SftpOutboundTransferSample {
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testOutbound() throws Exception {
|
||||
ClassPathXmlApplicationContext ac =
|
||||
new ClassPathXmlApplicationContext("SftpOutboundTransferSample-ignored.xml", SftpOutboundTransferSample.class);
|
||||
ac.start();
|
||||
File file = new File("/Users/ozhurakousky/workspace-sts-2.3.3.M2/si/spring-integration/spring-integration-sftp/local-test-dir/foo.txt");
|
||||
if (file.exists()) {
|
||||
Message<File> message = MessageBuilder.withPayload(file).build();
|
||||
MessageChannel inputChannel = ac.getBean("inputChannel", MessageChannel.class);
|
||||
inputChannel.send(message);
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
ac.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,10 +20,14 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.common.SftpHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
@@ -50,8 +54,6 @@ import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
@@ -68,7 +70,6 @@ public class SftpTests extends SftpTestSupport {
|
||||
private IntegrationFlowContext flowContext;
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testSftpInboundFlow() {
|
||||
QueueChannel out = new QueueChannel();
|
||||
IntegrationFlow flow = IntegrationFlow
|
||||
@@ -78,7 +79,7 @@ public class SftpTests extends SftpTestSupport {
|
||||
.regexFilter(".*\\.txt$")
|
||||
.localFilenameExpression("#this.toUpperCase() + '.a'")
|
||||
.localDirectory(getTargetLocalDirectory())
|
||||
.remoteComparator(Comparator.naturalOrder()),
|
||||
.remoteComparator(Comparator.comparing(SftpClient.DirEntry::getFilename)),
|
||||
e -> e.id("sftpInboundAdapter").poller(Pollers.fixedDelay(100)))
|
||||
.channel(out)
|
||||
.get();
|
||||
@@ -122,7 +123,7 @@ public class SftpTests extends SftpTestSupport {
|
||||
assertThat(message).isNotNull();
|
||||
assertThat(message.getPayload()).isInstanceOf(InputStream.class);
|
||||
assertThat(message.getHeaders().get(FileHeaders.REMOTE_FILE)).isIn(" sftpSource1.txt", "sftpSource2.txt");
|
||||
assertThat(message.getHeaders().get(FileHeaders.REMOTE_HOST_PORT, String.class)).contains("localhost:");
|
||||
assertThat(message.getHeaders().get(FileHeaders.REMOTE_HOST_PORT, String.class)).contains("localhost");
|
||||
((InputStream) message.getPayload()).close();
|
||||
new IntegrationMessageHeaderAccessor(message).getCloseableResource().close();
|
||||
|
||||
@@ -141,11 +142,11 @@ public class SftpTests extends SftpTestSupport {
|
||||
.setHeader(FileHeaders.FILENAME, fileName)
|
||||
.build());
|
||||
|
||||
RemoteFileTemplate<ChannelSftp.LsEntry> template = new RemoteFileTemplate<>(sessionFactory());
|
||||
ChannelSftp.LsEntry[] files = template.execute(session ->
|
||||
session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
RemoteFileTemplate<SftpClient.DirEntry> template = new RemoteFileTemplate<>(sessionFactory());
|
||||
SftpClient.DirEntry[] files =
|
||||
template.execute(session -> session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
assertThat(files.length).isEqualTo(1);
|
||||
assertThat(files[0].getAttrs().getSize()).isEqualTo(3);
|
||||
assertThat(files[0].getAttributes().getSize()).isEqualTo(3);
|
||||
|
||||
registration.destroy();
|
||||
}
|
||||
@@ -163,10 +164,10 @@ public class SftpTests extends SftpTestSupport {
|
||||
.setHeader(FileHeaders.FILENAME, fileName)
|
||||
.build());
|
||||
|
||||
ChannelSftp.LsEntry[] files = sftpTemplate.execute(session ->
|
||||
session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
SftpClient.DirEntry[] files =
|
||||
sftpTemplate.execute(session -> session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
assertThat(files.length).isEqualTo(1);
|
||||
assertThat(files[0].getAttrs().getSize()).isEqualTo(3);
|
||||
assertThat(files[0].getAttributes().getSize()).isEqualTo(3);
|
||||
|
||||
registration.destroy();
|
||||
}
|
||||
@@ -187,10 +188,10 @@ public class SftpTests extends SftpTestSupport {
|
||||
.setHeader(FileHeaders.FILENAME, fileName)
|
||||
.build());
|
||||
|
||||
ChannelSftp.LsEntry[] files = sftpTemplate.execute(session ->
|
||||
session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
SftpClient.DirEntry[] files =
|
||||
sftpTemplate.execute(session -> session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
assertThat(files.length).isEqualTo(1);
|
||||
assertThat(files[0].getAttrs().getSize()).isEqualTo(6);
|
||||
assertThat(files[0].getAttributes().getSize()).isEqualTo(6);
|
||||
|
||||
registration.destroy();
|
||||
}
|
||||
@@ -210,16 +211,17 @@ public class SftpTests extends SftpTestSupport {
|
||||
.setHeader(FileHeaders.FILENAME, fileName)
|
||||
.build());
|
||||
|
||||
RemoteFileTemplate<ChannelSftp.LsEntry> template = new RemoteFileTemplate<>(sessionFactory());
|
||||
ChannelSftp.LsEntry[] files = template.execute(session ->
|
||||
session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
RemoteFileTemplate<SftpClient.DirEntry> template = new RemoteFileTemplate<>(sessionFactory());
|
||||
SftpClient.DirEntry[] files =
|
||||
template.execute(session -> session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
assertThat(files.length).isEqualTo(1);
|
||||
assertThat(files[0].getAttrs().getSize()).isEqualTo(3);
|
||||
String[] permissions = files[0].getAttrs().getPermissionsString().substring(1).replaceAll("--", "-").split("-");
|
||||
assertThat(permissions[0]).isEqualTo("rw");
|
||||
assertThat(permissions[1]).isEqualTo("r");
|
||||
assertThat(permissions[2]).isEqualTo("r");
|
||||
|
||||
assertThat(files[0].getAttributes().getSize()).isEqualTo(3);
|
||||
int permissionFlags = files[0].getAttributes().getPermissions();
|
||||
Set<PosixFilePermission> posixFilePermissions = SftpHelper.permissionsToAttributes(permissionFlags);
|
||||
assertThat(posixFilePermissions)
|
||||
.contains(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE,
|
||||
PosixFilePermission.GROUP_READ, PosixFilePermission.OTHERS_READ)
|
||||
.doesNotContain(PosixFilePermission.GROUP_WRITE, PosixFilePermission.OTHERS_WRITE);
|
||||
registration.destroy();
|
||||
}
|
||||
|
||||
@@ -264,9 +266,9 @@ public class SftpTests extends SftpTestSupport {
|
||||
Message<?> receive = out.receive(10_000);
|
||||
assertThat(receive).isNotNull();
|
||||
Object payload = receive.getPayload();
|
||||
assertThat(payload).isInstanceOf(ChannelSftp.LsEntry[].class);
|
||||
assertThat(payload).isInstanceOf(SftpClient.DirEntry[].class);
|
||||
|
||||
assertThat(((ChannelSftp.LsEntry[]) payload).length > 0).isTrue();
|
||||
assertThat(((SftpClient.DirEntry[]) payload).length > 0).isTrue();
|
||||
|
||||
registration.destroy();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2020 the original author or authors.
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -32,10 +33,10 @@ import org.springframework.integration.sftp.session.SftpRemoteFileTemplate;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*
|
||||
*/
|
||||
@@ -50,9 +51,9 @@ public class SftpFileListFilterTests extends SftpTestSupport {
|
||||
public void testMarkerFile() throws Exception {
|
||||
SftpSystemMarkerFilePresentFileListFilter filter = new SftpSystemMarkerFilePresentFileListFilter(
|
||||
new SftpSimplePatternFileListFilter("*.txt"));
|
||||
LsEntry[] files = template.list("sftpSource");
|
||||
SftpClient.DirEntry[] files = template.list("sftpSource");
|
||||
assertThat(files.length).isGreaterThan(0);
|
||||
List<LsEntry> filtered = filter.filterFiles(files);
|
||||
List<SftpClient.DirEntry> filtered = filter.filterFiles(files);
|
||||
assertThat(filtered.size()).isEqualTo(0);
|
||||
File remoteDir = getSourceRemoteDirectory();
|
||||
File marker = new File(remoteDir, "sftpSource2.txt.complete");
|
||||
@@ -68,12 +69,12 @@ public class SftpFileListFilterTests extends SftpTestSupport {
|
||||
public static class Config {
|
||||
|
||||
@Bean
|
||||
public SessionFactory<LsEntry> sftpSessionFactory() {
|
||||
public SessionFactory<SftpClient.DirEntry> sftpSessionFactory() {
|
||||
return SftpFileListFilterTests.sessionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SftpRemoteFileTemplate remoteFileTempalte() {
|
||||
public SftpRemoteFileTemplate remoteFileTemplate() {
|
||||
return new SftpRemoteFileTemplate(sftpSessionFactory());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2020 the original author or authors.
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,23 +17,22 @@
|
||||
package org.springframework.integration.sftp.filters;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.metadata.SimpleMetadataStore;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author David Liu
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 4.0.4
|
||||
*
|
||||
*/
|
||||
@@ -43,18 +42,15 @@ public class SftpPersistentAcceptOnceFileListFilterTests {
|
||||
public void testRollback() throws Exception {
|
||||
SftpPersistentAcceptOnceFileListFilter filter = new SftpPersistentAcceptOnceFileListFilter(
|
||||
new SimpleMetadataStore(), "rollback:");
|
||||
ChannelSftp channel = new ChannelSftp();
|
||||
SftpATTRS attrs = mock(SftpATTRS.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Constructor<LsEntry> ctor = (Constructor<LsEntry>) LsEntry.class.getDeclaredConstructors()[0];
|
||||
ctor.setAccessible(true);
|
||||
LsEntry sftpFile1 = ctor.newInstance(channel, "foo", "foo", attrs);
|
||||
LsEntry sftpFile2 = ctor.newInstance(channel, "bar", "bar", attrs);
|
||||
LsEntry ftpFile3 = ctor.newInstance(channel, "baz", "baz", attrs);
|
||||
LsEntry[] files = new LsEntry[] {sftpFile1, sftpFile2, ftpFile3};
|
||||
List<LsEntry> passed = filter.filterFiles(files);
|
||||
SftpClient.Attributes attrs = new SftpClient.Attributes();
|
||||
attrs.setModifyTime(FileTime.from(Instant.now()));
|
||||
SftpClient.DirEntry sftpFile1 = new SftpClient.DirEntry("foo", "foo", attrs);
|
||||
SftpClient.DirEntry sftpFile2 = new SftpClient.DirEntry("bar", "bar", attrs);
|
||||
SftpClient.DirEntry sftpFile3 = new SftpClient.DirEntry("baz", "baz", attrs);
|
||||
SftpClient.DirEntry[] files = new SftpClient.DirEntry[]{ sftpFile1, sftpFile2, sftpFile3 };
|
||||
List<SftpClient.DirEntry> passed = filter.filterFiles(files);
|
||||
assertThat(Arrays.equals(files, passed.toArray())).isTrue();
|
||||
List<LsEntry> now = filter.filterFiles(files);
|
||||
List<SftpClient.DirEntry> now = filter.filterFiles(files);
|
||||
assertThat(now.size()).isEqualTo(0);
|
||||
filter.rollback(passed.get(1), passed);
|
||||
now = filter.filterFiles(files);
|
||||
@@ -70,15 +66,12 @@ public class SftpPersistentAcceptOnceFileListFilterTests {
|
||||
public void testKeyUsingFileName() throws Exception {
|
||||
SftpPersistentAcceptOnceFileListFilter filter = new SftpPersistentAcceptOnceFileListFilter(
|
||||
new SimpleMetadataStore(), "rollback:");
|
||||
ChannelSftp channel = new ChannelSftp();
|
||||
SftpATTRS attrs = mock(SftpATTRS.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Constructor<LsEntry> ctor = (Constructor<LsEntry>) LsEntry.class.getDeclaredConstructors()[0];
|
||||
ctor.setAccessible(true);
|
||||
LsEntry sftpFile1 = ctor.newInstance(channel, "foo", "same", attrs);
|
||||
LsEntry sftpFile2 = ctor.newInstance(channel, "bar", "same", attrs);
|
||||
LsEntry[] files = new LsEntry[] {sftpFile1, sftpFile2};
|
||||
List<LsEntry> now = filter.filterFiles(files);
|
||||
SftpClient.Attributes attrs = new SftpClient.Attributes();
|
||||
attrs.setModifyTime(FileTime.from(Instant.now()));
|
||||
SftpClient.DirEntry sftpFile1 = new SftpClient.DirEntry("foo", "same", attrs);
|
||||
SftpClient.DirEntry sftpFile2 = new SftpClient.DirEntry("bar", "same", attrs);
|
||||
SftpClient.DirEntry[] files = new SftpClient.DirEntry[]{ sftpFile1, sftpFile2 };
|
||||
List<SftpClient.DirEntry> now = filter.filterFiles(files);
|
||||
assertThat(now.size()).isEqualTo(2);
|
||||
assertThat(now.get(0).getFilename()).isEqualTo("foo");
|
||||
assertThat(now.get(1).getFilename()).isEqualTo("bar");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2020 the original author or authors.
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,6 +23,7 @@ import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -34,11 +35,10 @@ import org.springframework.integration.sftp.SftpTestSupport;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 4.1.7
|
||||
*
|
||||
*/
|
||||
@@ -85,12 +85,13 @@ public class RollbackLocalFilterTests extends SftpTestSupport {
|
||||
this.file = in;
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Config {
|
||||
|
||||
@Bean
|
||||
public SessionFactory<LsEntry> sftpSessionFactory() {
|
||||
public SessionFactory<SftpClient.DirEntry> sftpSessionFactory() {
|
||||
return RollbackLocalFilterTests.sessionFactory();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,7 +17,7 @@
|
||||
package org.springframework.integration.sftp.inbound;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.willReturn;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -27,13 +27,15 @@ import static org.mockito.Mockito.when;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -53,10 +55,6 @@ import org.springframework.integration.sftp.session.SftpTestSessionFactory;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gunnar Hillert
|
||||
@@ -68,14 +66,9 @@ import com.jcraft.jsch.SftpATTRS;
|
||||
*/
|
||||
public class SftpInboundRemoteFileSystemSynchronizerTests {
|
||||
|
||||
private static final com.jcraft.jsch.Session jschSession = mock(com.jcraft.jsch.Session.class);
|
||||
|
||||
@BeforeEach
|
||||
@AfterEach
|
||||
public void cleanup() {
|
||||
willReturn("::1")
|
||||
.given(jschSession)
|
||||
.getHost();
|
||||
File file = new File("test");
|
||||
if (file.exists()) {
|
||||
String[] files = file.list();
|
||||
@@ -104,12 +97,11 @@ public class SftpInboundRemoteFileSystemSynchronizerTests {
|
||||
PropertiesPersistingMetadataStore store = spy(new PropertiesPersistingMetadataStore());
|
||||
store.setBaseDirectory("test");
|
||||
store.afterPropertiesSet();
|
||||
SftpPersistentAcceptOnceFileListFilter persistFilter =
|
||||
new SftpPersistentAcceptOnceFileListFilter(store, "foo");
|
||||
List<FileListFilter<LsEntry>> filters = new ArrayList<>();
|
||||
SftpPersistentAcceptOnceFileListFilter persistFilter = new SftpPersistentAcceptOnceFileListFilter(store, "foo");
|
||||
List<FileListFilter<SftpClient.DirEntry>> filters = new ArrayList<>();
|
||||
filters.add(persistFilter);
|
||||
filters.add(patternFilter);
|
||||
CompositeFileListFilter<LsEntry> filter = new CompositeFileListFilter<>(filters);
|
||||
CompositeFileListFilter<SftpClient.DirEntry> filter = new CompositeFileListFilter<>(filters);
|
||||
synchronizer.setFilter(filter);
|
||||
synchronizer.setBeanFactory(mock(BeanFactory.class));
|
||||
synchronizer.afterPropertiesSet();
|
||||
@@ -140,7 +132,7 @@ public class SftpInboundRemoteFileSystemSynchronizerTests {
|
||||
TestUtils.getPropertyValue(synchronizer, "remoteFileMetadataStore.metadata", Map.class);
|
||||
|
||||
String next = remoteFileMetadataStore.values().iterator().next();
|
||||
assertThat(URI.create(next).getHost()).isEqualTo("[::1]");
|
||||
assertThat(URI.create(next).getHost()).isEqualTo("mock.sftp.host");
|
||||
|
||||
Message<File> btestFile = ms.receive();
|
||||
assertThat(btestFile).isNotNull();
|
||||
@@ -172,43 +164,40 @@ public class SftpInboundRemoteFileSystemSynchronizerTests {
|
||||
|
||||
public static class TestSftpSessionFactory extends DefaultSftpSessionFactory {
|
||||
|
||||
private final Vector<LsEntry> sftpEntries = new Vector<>();
|
||||
private List<SftpClient.DirEntry> sftpEntries;
|
||||
|
||||
private void init() {
|
||||
String[] files = new File("remote-test-dir").list();
|
||||
for (String fileName : files) {
|
||||
LsEntry lsEntry = mock(LsEntry.class);
|
||||
SftpATTRS attributes = mock(SftpATTRS.class);
|
||||
when(lsEntry.getAttrs()).thenReturn(attributes);
|
||||
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.DATE, 1);
|
||||
when(lsEntry.getAttrs().getMTime())
|
||||
.thenReturn(Long.valueOf(calendar.getTimeInMillis() / 1000).intValue());
|
||||
when(lsEntry.getFilename()).thenReturn(fileName);
|
||||
when(lsEntry.getLongname()).thenReturn(fileName);
|
||||
sftpEntries.add(lsEntry);
|
||||
}
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.DATE, 1);
|
||||
this.sftpEntries =
|
||||
Arrays.stream(files)
|
||||
.map((file) -> {
|
||||
SftpClient.Attributes attributes = spy(new SftpClient.Attributes());
|
||||
attributes.setModifyTime(FileTime.fromMillis(calendar.getTimeInMillis()));
|
||||
given(attributes.isRegularFile()).willReturn(true);
|
||||
return new SftpClient.DirEntry(file, file, attributes);
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SftpSession getSession() {
|
||||
if (this.sftpEntries.size() == 0) {
|
||||
this.init();
|
||||
if (this.sftpEntries == null) {
|
||||
init();
|
||||
}
|
||||
|
||||
try {
|
||||
ChannelSftp channel = mock(ChannelSftp.class);
|
||||
SftpClient sftpClient = mock(SftpClient.class);
|
||||
|
||||
String[] files = new File("remote-test-dir").list();
|
||||
for (String fileName : files) {
|
||||
when(channel.get("remote-test-dir/" + fileName))
|
||||
when(sftpClient.read("remote-test-dir/" + fileName))
|
||||
.thenReturn(new FileInputStream("remote-test-dir/" + fileName));
|
||||
}
|
||||
when(channel.ls("remote-test-dir")).thenReturn(sftpEntries);
|
||||
when(sftpClient.readDir("remote-test-dir")).thenReturn(this.sftpEntries);
|
||||
|
||||
when(jschSession.openChannel("sftp")).thenReturn(channel);
|
||||
return SftpTestSessionFactory.createSftpSession(jschSession);
|
||||
return SftpTestSessionFactory.createSftpSession(sftpClient);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create mock sftp session", e);
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.Comparator;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -53,8 +54,6 @@ import org.springframework.scheduling.support.PeriodicTrigger;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
@@ -120,7 +119,7 @@ public class SftpStreamingMessageSourceTests extends SftpTestSupport {
|
||||
received = (Message<byte[]>) this.data.receive(10000);
|
||||
assertThat(received).isNotNull();
|
||||
assertThat(received.getHeaders().get(FileHeaders.REMOTE_FILE_INFO)).isInstanceOf(SftpFileInfo.class);
|
||||
assertThat(received.getHeaders().get(FileHeaders.REMOTE_HOST_PORT, String.class)).contains("localhost:");
|
||||
assertThat(received.getHeaders().get(FileHeaders.REMOTE_HOST_PORT, String.class)).contains("localhost");
|
||||
this.adapter.stop();
|
||||
}
|
||||
|
||||
@@ -172,7 +171,7 @@ public class SftpStreamingMessageSourceTests extends SftpTestSupport {
|
||||
private SftpStreamingMessageSource buildSource() {
|
||||
SftpStreamingMessageSource messageSource =
|
||||
new SftpStreamingMessageSource(this.config.template(),
|
||||
Comparator.comparing(LsEntry::getFilename));
|
||||
Comparator.comparing(SftpClient.DirEntry::getFilename));
|
||||
messageSource.setRemoteDirectory("sftpSource/");
|
||||
messageSource.setBeanFactory(this.context);
|
||||
return messageSource;
|
||||
@@ -205,7 +204,7 @@ public class SftpStreamingMessageSourceTests extends SftpTestSupport {
|
||||
@InboundChannelAdapter(channel = "stream", autoStartup = "false")
|
||||
public MessageSource<InputStream> sftpMessageSource() {
|
||||
SftpStreamingMessageSource messageSource = new SftpStreamingMessageSource(template(),
|
||||
Comparator.comparing(LsEntry::getFilename));
|
||||
Comparator.comparing(SftpClient.DirEntry::getFilename));
|
||||
messageSource.setFilter(
|
||||
new SftpPersistentAcceptOnceFileListFilter(
|
||||
new SimpleMetadataStore(metadataMap()), "testStreaming"));
|
||||
@@ -225,7 +224,7 @@ public class SftpStreamingMessageSourceTests extends SftpTestSupport {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SessionFactory<LsEntry> ftpSessionFactory() {
|
||||
public SessionFactory<SftpClient.DirEntry> ftpSessionFactory() {
|
||||
return SftpStreamingMessageSourceTests.sessionFactory();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,41 +22,42 @@ import static org.mockito.AdditionalMatchers.and;
|
||||
import static org.mockito.AdditionalMatchers.not;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.willAnswer;
|
||||
import static org.mockito.BDDMockito.willReturn;
|
||||
import static org.mockito.BDDMockito.willThrow;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.apache.sshd.common.SshConstants;
|
||||
import org.apache.sshd.common.SshException;
|
||||
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
|
||||
import org.apache.sshd.server.SshServer;
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.common.SftpConstants;
|
||||
import org.apache.sshd.sftp.common.SftpException;
|
||||
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.file.DefaultFileNameGenerator;
|
||||
import org.springframework.integration.file.remote.FileInfo;
|
||||
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
|
||||
import org.springframework.integration.file.remote.session.CachingSessionFactory;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.sftp.session.DefaultSftpSessionFactory;
|
||||
@@ -70,13 +71,6 @@ import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.JSch;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
import com.jcraft.jsch.SftpException;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
@@ -85,19 +79,18 @@ import com.jcraft.jsch.SftpException;
|
||||
*/
|
||||
public class SftpOutboundTests {
|
||||
|
||||
private static final com.jcraft.jsch.Session jschSession = mock(com.jcraft.jsch.Session.class);
|
||||
|
||||
@Test
|
||||
public void testHandleFileMessage() throws Exception {
|
||||
File targetDir = new File("remote-target-dir");
|
||||
assertThat(targetDir.exists()).as("target directory does not exist: " + targetDir.getName()).isTrue();
|
||||
|
||||
SessionFactory<LsEntry> sessionFactory = new TestSftpSessionFactory();
|
||||
FileTransferringMessageHandler<LsEntry> handler = new FileTransferringMessageHandler<>(sessionFactory);
|
||||
SessionFactory<SftpClient.DirEntry> sessionFactory = new TestSftpSessionFactory();
|
||||
FileTransferringMessageHandler<SftpClient.DirEntry> handler =
|
||||
new FileTransferringMessageHandler<>(sessionFactory);
|
||||
handler.setRemoteDirectoryExpression(new LiteralExpression(targetDir.getName()));
|
||||
DefaultFileNameGenerator fGenerator = new DefaultFileNameGenerator();
|
||||
fGenerator.setBeanFactory(mock(BeanFactory.class));
|
||||
fGenerator.setExpression("payload + '.test'");
|
||||
fGenerator.setExpression("payload.name + '.test'");
|
||||
handler.setFileNameGenerator(fGenerator);
|
||||
handler.setBeanFactory(mock(BeanFactory.class));
|
||||
handler.afterPropertiesSet();
|
||||
@@ -118,8 +111,9 @@ public class SftpOutboundTests {
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
SessionFactory<LsEntry> sessionFactory = new TestSftpSessionFactory();
|
||||
FileTransferringMessageHandler<LsEntry> handler = new FileTransferringMessageHandler<>(sessionFactory);
|
||||
SessionFactory<SftpClient.DirEntry> sessionFactory = new TestSftpSessionFactory();
|
||||
FileTransferringMessageHandler<SftpClient.DirEntry> handler =
|
||||
new FileTransferringMessageHandler<>(sessionFactory);
|
||||
DefaultFileNameGenerator fGenerator = new DefaultFileNameGenerator();
|
||||
fGenerator.setBeanFactory(mock(BeanFactory.class));
|
||||
fGenerator.setExpression("'foo.txt'");
|
||||
@@ -128,7 +122,7 @@ public class SftpOutboundTests {
|
||||
handler.setBeanFactory(mock(BeanFactory.class));
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
handler.handleMessage(new GenericMessage<String>("String data"));
|
||||
handler.handleMessage(new GenericMessage<>("String data"));
|
||||
assertThat(new File("remote-target-dir", "foo.txt").exists()).isTrue();
|
||||
byte[] inFile = FileCopyUtils.copyToByteArray(file);
|
||||
assertThat(new String(inFile)).isEqualTo("String data");
|
||||
@@ -141,8 +135,9 @@ public class SftpOutboundTests {
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
SessionFactory<LsEntry> sessionFactory = new TestSftpSessionFactory();
|
||||
FileTransferringMessageHandler<LsEntry> handler = new FileTransferringMessageHandler<>(sessionFactory);
|
||||
SessionFactory<SftpClient.DirEntry> sessionFactory = new TestSftpSessionFactory();
|
||||
FileTransferringMessageHandler<SftpClient.DirEntry> handler =
|
||||
new FileTransferringMessageHandler<>(sessionFactory);
|
||||
DefaultFileNameGenerator fGenerator = new DefaultFileNameGenerator();
|
||||
fGenerator.setBeanFactory(mock(BeanFactory.class));
|
||||
fGenerator.setExpression("'foo.txt'");
|
||||
@@ -174,7 +169,7 @@ public class SftpOutboundTests {
|
||||
|
||||
MessageChannel channel = context.getBean("outboundChannelAdapterInsideChain", MessageChannel.class);
|
||||
|
||||
channel.send(new GenericMessage<File>(srcFile));
|
||||
channel.send(new GenericMessage<>(srcFile));
|
||||
assertThat(destFile.exists()).as("destination file was not created").isTrue();
|
||||
context.close();
|
||||
}
|
||||
@@ -203,15 +198,15 @@ public class SftpOutboundTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test //INT-2954
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testMkDir() throws Exception {
|
||||
@SuppressWarnings("unchecked")
|
||||
Session<LsEntry> session = mock(Session.class);
|
||||
Session<SftpClient.DirEntry> session = mock(Session.class);
|
||||
when(session.exists(anyString())).thenReturn(Boolean.FALSE);
|
||||
@SuppressWarnings("unchecked")
|
||||
SessionFactory<LsEntry> sessionFactory = mock(SessionFactory.class);
|
||||
SessionFactory<SftpClient.DirEntry> sessionFactory = mock(SessionFactory.class);
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
FileTransferringMessageHandler<LsEntry> handler = new FileTransferringMessageHandler<>(sessionFactory);
|
||||
FileTransferringMessageHandler<SftpClient.DirEntry> handler =
|
||||
new FileTransferringMessageHandler<>(sessionFactory);
|
||||
handler.setAutoCreateDirectory(true);
|
||||
handler.setRemoteDirectoryExpression(new LiteralExpression("/foo/bar/baz"));
|
||||
handler.setBeanFactory(mock(BeanFactory.class));
|
||||
@@ -228,186 +223,56 @@ public class SftpOutboundTests {
|
||||
assertThat(madeDirs.get(2)).isEqualTo("/foo/bar/baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharedSession() throws Exception {
|
||||
JSch jsch = spy(new JSch());
|
||||
Constructor<com.jcraft.jsch.Session> ctor = com.jcraft.jsch.Session.class.getDeclaredConstructor(JSch.class,
|
||||
String.class, String.class, int.class);
|
||||
ctor.setAccessible(true);
|
||||
com.jcraft.jsch.Session jschSession1 = spy(ctor.newInstance(jsch, "foo", "host", 22));
|
||||
com.jcraft.jsch.Session jschSession2 = spy(ctor.newInstance(jsch, "foo", "host", 22));
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
public void testSharedSession(boolean sharedSession) throws IOException {
|
||||
try (SshServer server = SshServer.setUpDefaultServer()) {
|
||||
server.setPasswordAuthenticator((arg0, arg1, arg2) -> true);
|
||||
server.setPort(0);
|
||||
server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("hostkey.ser").toPath()));
|
||||
server.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
|
||||
final String pathname = System.getProperty("java.io.tmpdir") + File.separator + "sftptest" + File.separator;
|
||||
new File(pathname).mkdirs();
|
||||
server.setFileSystemFactory(new VirtualFileSystemFactory(Paths.get(pathname)));
|
||||
server.start();
|
||||
|
||||
willAnswer(invocation -> {
|
||||
new DirectFieldAccessor(jschSession1).setPropertyValue("isConnected", true);
|
||||
return null;
|
||||
})
|
||||
.given(jschSession1)
|
||||
.connect();
|
||||
DefaultSftpSessionFactory f = new DefaultSftpSessionFactory(sharedSession);
|
||||
f.setHost("localhost");
|
||||
f.setPort(server.getPort());
|
||||
f.setUser("user");
|
||||
f.setPassword("pass");
|
||||
f.setAllowUnknownKeys(true);
|
||||
|
||||
willAnswer(invocation -> {
|
||||
new DirectFieldAccessor(jschSession2).setPropertyValue("isConnected", true);
|
||||
return null;
|
||||
})
|
||||
.given(jschSession2)
|
||||
.connect();
|
||||
|
||||
when(jsch.getSession("foo", "host", 22)).thenReturn(jschSession1, jschSession2);
|
||||
final ChannelSftp channel1 = spy(new ChannelSftp());
|
||||
doReturn("channel1").when(channel1).toString();
|
||||
final ChannelSftp channel2 = spy(new ChannelSftp());
|
||||
doReturn("channel2").when(channel2).toString();
|
||||
new DirectFieldAccessor(channel1).setPropertyValue("session", jschSession1);
|
||||
new DirectFieldAccessor(channel2).setPropertyValue("session", jschSession1);
|
||||
// Can't use when(session.open()) with a spy
|
||||
final AtomicInteger n = new AtomicInteger();
|
||||
doAnswer(invocation -> n.getAndIncrement() == 0 ? channel1 : channel2).when(jschSession1).openChannel("sftp");
|
||||
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(jsch, true);
|
||||
factory.setHost("host");
|
||||
factory.setUser("foo");
|
||||
factory.setPassword("bar");
|
||||
noopConnect(channel1);
|
||||
noopConnect(channel2);
|
||||
Session<LsEntry> s1 = factory.getSession();
|
||||
Session<LsEntry> s2 = factory.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(s2, "jschSession"))
|
||||
.isSameAs(TestUtils.getPropertyValue(s1, "jschSession"));
|
||||
assertThat(TestUtils.getPropertyValue(s1, "channel")).isSameAs(channel1);
|
||||
assertThat(TestUtils.getPropertyValue(s2, "channel")).isSameAs(channel2);
|
||||
Session<SftpClient.DirEntry> s1 = f.getSession();
|
||||
Session<SftpClient.DirEntry> s2 = f.getSession();
|
||||
if (sharedSession) {
|
||||
assertThat(TestUtils.getPropertyValue(s2, "sftpClient"))
|
||||
.isSameAs(TestUtils.getPropertyValue(s1, "sftpClient"));
|
||||
}
|
||||
else {
|
||||
assertThat(TestUtils.getPropertyValue(s2, "sftpClient"))
|
||||
.isNotSameAs(TestUtils.getPropertyValue(s1, "sftpClient"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotSharedSession() throws Exception {
|
||||
JSch jsch = spy(new JSch());
|
||||
Constructor<com.jcraft.jsch.Session> ctor =
|
||||
com.jcraft.jsch.Session.class.getDeclaredConstructor(JSch.class, String.class, String.class,
|
||||
int.class);
|
||||
ctor.setAccessible(true);
|
||||
com.jcraft.jsch.Session jschSession1 = spy(ctor.newInstance(jsch, "foo", "host", 22));
|
||||
com.jcraft.jsch.Session jschSession2 = spy(ctor.newInstance(jsch, "foo", "host", 22));
|
||||
new DirectFieldAccessor(jschSession1).setPropertyValue("isConnected", true);
|
||||
new DirectFieldAccessor(jschSession2).setPropertyValue("isConnected", true);
|
||||
when(jsch.getSession("foo", "host", 22)).thenReturn(jschSession1, jschSession2);
|
||||
ChannelSftp channel1 = spy(new ChannelSftp());
|
||||
ChannelSftp channel2 = spy(new ChannelSftp());
|
||||
new DirectFieldAccessor(channel1).setPropertyValue("session", jschSession1);
|
||||
new DirectFieldAccessor(channel2).setPropertyValue("session", jschSession1);
|
||||
doReturn(channel1).when(jschSession1).openChannel("sftp");
|
||||
doReturn(channel2).when(jschSession2).openChannel("sftp");
|
||||
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(jsch, false);
|
||||
factory.setHost("host");
|
||||
factory.setUser("foo");
|
||||
factory.setPassword("bar");
|
||||
noopConnect(channel1);
|
||||
noopConnect(channel2);
|
||||
Session<LsEntry> s1 = factory.getSession();
|
||||
Session<LsEntry> s2 = factory.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(s2, "jschSession"))
|
||||
.isNotSameAs(TestUtils.getPropertyValue(s1, "jschSession"));
|
||||
assertThat(TestUtils.getPropertyValue(s1, "channel")).isSameAs(channel1);
|
||||
assertThat(TestUtils.getPropertyValue(s2, "channel")).isSameAs(channel2);
|
||||
}
|
||||
public void testExists() throws IOException {
|
||||
SftpClient sftpClient = mock(SftpClient.class);
|
||||
|
||||
@Test
|
||||
public void testSharedSessionCachedReset() throws Exception {
|
||||
JSch jsch = spy(new JSch());
|
||||
Constructor<com.jcraft.jsch.Session> ctor =
|
||||
com.jcraft.jsch.Session.class.getDeclaredConstructor(JSch.class, String.class, String.class,
|
||||
int.class);
|
||||
ctor.setAccessible(true);
|
||||
com.jcraft.jsch.Session jschSession1 = spy(ctor.newInstance(jsch, "foo", "host", 22));
|
||||
com.jcraft.jsch.Session jschSession2 = spy(ctor.newInstance(jsch, "foo", "host", 22));
|
||||
|
||||
willAnswer(invocation -> {
|
||||
new DirectFieldAccessor(jschSession1).setPropertyValue("isConnected", true);
|
||||
return null;
|
||||
})
|
||||
.given(jschSession1)
|
||||
.connect();
|
||||
|
||||
willAnswer(invocation -> {
|
||||
new DirectFieldAccessor(jschSession2).setPropertyValue("isConnected", true);
|
||||
return null;
|
||||
})
|
||||
.given(jschSession2)
|
||||
.connect();
|
||||
|
||||
when(jsch.getSession("foo", "host", 22)).thenReturn(jschSession1, jschSession2);
|
||||
final ChannelSftp channel1 = spy(new ChannelSftp());
|
||||
doReturn("channel1").when(channel1).toString();
|
||||
final ChannelSftp channel2 = spy(new ChannelSftp());
|
||||
doReturn("channel2").when(channel2).toString();
|
||||
final ChannelSftp channel3 = spy(new ChannelSftp());
|
||||
doReturn("channel3").when(channel3).toString();
|
||||
final ChannelSftp channel4 = spy(new ChannelSftp());
|
||||
doReturn("channel4").when(channel4).toString();
|
||||
new DirectFieldAccessor(channel1).setPropertyValue("session", jschSession1);
|
||||
new DirectFieldAccessor(channel2).setPropertyValue("session", jschSession1);
|
||||
// Can't use when(session.open()) with a spy
|
||||
final AtomicInteger n = new AtomicInteger();
|
||||
doAnswer(invocation -> n.getAndIncrement() == 0 ? channel1 : channel2).when(jschSession1).openChannel("sftp");
|
||||
doAnswer(invocation -> n.getAndIncrement() < 3 ? channel3 : channel4).when(jschSession2).openChannel("sftp");
|
||||
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(jsch, true);
|
||||
factory.setHost("host");
|
||||
factory.setUser("foo");
|
||||
factory.setPassword("bar");
|
||||
CachingSessionFactory<LsEntry> cachedFactory = new CachingSessionFactory<LsEntry>(factory);
|
||||
noopConnect(channel1);
|
||||
noopConnect(channel2);
|
||||
noopConnect(channel3);
|
||||
noopConnect(channel4);
|
||||
Session<LsEntry> s1 = cachedFactory.getSession();
|
||||
Session<LsEntry> s2 = cachedFactory.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(s2, "targetSession.jschSession")).isSameAs(jschSession1);
|
||||
assertThat(TestUtils.getPropertyValue(s1, "targetSession.channel")).isSameAs(channel1);
|
||||
assertThat(TestUtils.getPropertyValue(s2, "targetSession.channel")).isSameAs(channel2);
|
||||
assertThat(TestUtils.getPropertyValue(s2, "targetSession.jschSession"))
|
||||
.isSameAs(TestUtils.getPropertyValue(s1, "targetSession.jschSession"));
|
||||
s1.close();
|
||||
Session<LsEntry> s3 = cachedFactory.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(s3, "targetSession"))
|
||||
.isSameAs(TestUtils.getPropertyValue(s1, "targetSession"));
|
||||
assertThat(TestUtils.getPropertyValue(s3, "targetSession.channel")).isSameAs(channel1);
|
||||
s3.close();
|
||||
cachedFactory.resetCache();
|
||||
verify(jschSession1, never()).disconnect();
|
||||
s3 = cachedFactory.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(s3, "targetSession.jschSession")).isSameAs(jschSession2);
|
||||
assertThat(TestUtils.getPropertyValue(s3, "targetSession"))
|
||||
.isNotSameAs(TestUtils.getPropertyValue(s1, "targetSession"));
|
||||
assertThat(TestUtils.getPropertyValue(s3, "targetSession.channel")).isSameAs(channel3);
|
||||
s2.close();
|
||||
verify(jschSession1).disconnect();
|
||||
s2 = cachedFactory.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(s2, "targetSession.jschSession")).isSameAs(jschSession2);
|
||||
assertThat(TestUtils.getPropertyValue(s2, "targetSession"))
|
||||
.isNotSameAs(TestUtils.getPropertyValue(s3, "targetSession"));
|
||||
assertThat(TestUtils.getPropertyValue(s2, "targetSession.channel")).isSameAs(channel4);
|
||||
s2.close();
|
||||
s3.close();
|
||||
verify(jschSession2, never()).disconnect();
|
||||
cachedFactory.resetCache();
|
||||
verify(jschSession2).disconnect();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExists() throws SftpException, IOException {
|
||||
ChannelSftp channelSftp = mock(ChannelSftp.class);
|
||||
|
||||
willReturn(mock(SftpATTRS.class))
|
||||
.given(channelSftp)
|
||||
willReturn(new SftpClient.Attributes())
|
||||
.given(sftpClient)
|
||||
.lstat(eq("exist"));
|
||||
|
||||
willThrow(new SftpException(ChannelSftp.SSH_FX_NO_SUCH_FILE, "Path does not exist."))
|
||||
.given(channelSftp)
|
||||
willThrow(new SftpException(SftpConstants.SSH_FX_NO_SUCH_FILE, "notExist"))
|
||||
.given(sftpClient)
|
||||
.lstat(eq("notExist"));
|
||||
|
||||
willThrow(new SftpException(ChannelSftp.SSH_FX_CONNECTION_LOST, "Connection lost."))
|
||||
.given(channelSftp)
|
||||
willThrow(new SshException(SshConstants.SSH_OPEN_CONNECT_FAILED, "Connection lost."))
|
||||
.given(sftpClient)
|
||||
.lstat(and(not(eq("exist")), not(eq("notExist"))));
|
||||
|
||||
SftpSession sftpSession = new SftpSession(mock(com.jcraft.jsch.Session.class));
|
||||
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(sftpSession);
|
||||
fieldAccessor.setPropertyValue("channel", channelSftp);
|
||||
SftpSession sftpSession = new SftpSession(sftpClient);
|
||||
|
||||
assertThat(sftpSession.exists("exist")).isTrue();
|
||||
|
||||
@@ -417,23 +282,18 @@ public class SftpOutboundTests {
|
||||
isThrownBy(() -> sftpSession.exists("foo"));
|
||||
}
|
||||
|
||||
private void noopConnect(ChannelSftp channel1) throws JSchException {
|
||||
doNothing().when(channel1).connect(5000);
|
||||
}
|
||||
|
||||
public static class TestSftpSessionFactory extends DefaultSftpSessionFactory {
|
||||
|
||||
@Override
|
||||
public SftpSession getSession() {
|
||||
try {
|
||||
ChannelSftp channel = mock(ChannelSftp.class);
|
||||
SftpClient sftpClient = mock(SftpClient.class);
|
||||
|
||||
doAnswer(invocation -> {
|
||||
File file = new File((String) invocation.getArgument(1));
|
||||
File file = new File((String) invocation.getArgument(0));
|
||||
assertThat(file.getName()).endsWith(".writing");
|
||||
FileCopyUtils.copy((InputStream) invocation.getArgument(0), new FileOutputStream(file));
|
||||
return null;
|
||||
}).when(channel).put(Mockito.any(InputStream.class), Mockito.anyString());
|
||||
return new FileOutputStream(file);
|
||||
}).when(sftpClient).write(Mockito.anyString());
|
||||
|
||||
doAnswer(invocation -> {
|
||||
File file = new File((String) invocation.getArgument(0));
|
||||
@@ -441,21 +301,16 @@ public class SftpOutboundTests {
|
||||
File renameToFile = new File((String) invocation.getArgument(1));
|
||||
file.renameTo(renameToFile);
|
||||
return null;
|
||||
}).when(channel).rename(Mockito.anyString(), Mockito.anyString());
|
||||
}).when(sftpClient).rename(Mockito.anyString(), Mockito.anyString(), eq(SftpClient.CopyMode.Overwrite));
|
||||
|
||||
String[] files = new File("remote-test-dir").list();
|
||||
Vector<LsEntry> sftpEntries = new Vector<>();
|
||||
for (String fileName : files) {
|
||||
LsEntry lsEntry = mock(LsEntry.class);
|
||||
SftpATTRS attributes = mock(SftpATTRS.class);
|
||||
when(lsEntry.getAttrs()).thenReturn(attributes);
|
||||
when(lsEntry.getFilename()).thenReturn(fileName);
|
||||
sftpEntries.add(lsEntry);
|
||||
}
|
||||
when(channel.ls("remote-test-dir/")).thenReturn(sftpEntries);
|
||||
List<SftpClient.DirEntry> dirEntries =
|
||||
Arrays.stream(files)
|
||||
.map((file) -> new SftpClient.DirEntry(file, file, new SftpClient.Attributes()))
|
||||
.toList();
|
||||
when(sftpClient.readDir("remote-test-dir")).thenReturn(dirEntries);
|
||||
|
||||
when(jschSession.openChannel("sftp")).thenReturn(channel);
|
||||
return SftpTestSessionFactory.createSftpSession(jschSession);
|
||||
return SftpTestSessionFactory.createSftpSession(sftpClient);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create mock sftp session", e);
|
||||
|
||||
@@ -19,6 +19,10 @@ package org.springframework.integration.sftp.outbound;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@@ -40,6 +44,7 @@ import java.util.regex.Matcher;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -78,9 +83,6 @@ import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
@@ -128,7 +130,7 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
private DirectChannel inboundMPutRecursiveFiltered;
|
||||
|
||||
@Autowired
|
||||
private SessionFactory<LsEntry> sessionFactory;
|
||||
private SessionFactory<SftpClient.DirEntry> sessionFactory;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel appending;
|
||||
@@ -180,8 +182,6 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
assertThat(localFile.getPath().replaceAll(Matcher.quoteReplacement(File.separator), "/"))
|
||||
.contains(dir.toUpperCase());
|
||||
Session<?> session2 = this.sessionFactory.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(session2, "targetSession.jschSession"))
|
||||
.isSameAs(TestUtils.getPropertyValue(session, "targetSession.jschSession"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -284,10 +284,10 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
assertThat(files.stream()
|
||||
.map(fi -> fi.getFilename())
|
||||
.collect(Collectors.toList())).contains(
|
||||
" sftpSource1.txt",
|
||||
"sftpSource2.txt",
|
||||
"subSftpSource",
|
||||
"subSftpSource/subSftpSource1.txt");
|
||||
" sftpSource1.txt",
|
||||
"sftpSource2.txt",
|
||||
"subSftpSource",
|
||||
"subSftpSource/subSftpSource1.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -302,14 +302,14 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
assertThat(files.stream()
|
||||
.map(fi -> fi.getFilename())
|
||||
.collect(Collectors.toList())).contains(
|
||||
" sftpSource1.txt",
|
||||
"sftpSource2.txt",
|
||||
"subSftpSource",
|
||||
"subSftpSource/subSftpSource1.txt",
|
||||
".",
|
||||
"..",
|
||||
"subSftpSource/.",
|
||||
"subSftpSource/..");
|
||||
" sftpSource1.txt",
|
||||
"sftpSource2.txt",
|
||||
"subSftpSource",
|
||||
"subSftpSource/subSftpSource1.txt",
|
||||
".",
|
||||
"..",
|
||||
"subSftpSource/.",
|
||||
"subSftpSource/..");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -324,9 +324,9 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
assertThat(files.stream()
|
||||
.map(fi -> fi.getFilename())
|
||||
.collect(Collectors.toList())).contains(
|
||||
" sftpSource1.txt",
|
||||
"sftpSource2.txt",
|
||||
"subSftpSource/subSftpSource1.txt");
|
||||
" sftpSource1.txt",
|
||||
"sftpSource2.txt",
|
||||
"subSftpSource/subSftpSource1.txt");
|
||||
File newDeepFile = new File(this.sourceRemoteDirectory + "/subSftpSource/subSftpSource2.txt");
|
||||
OutputStream fos = new FileOutputStream(newDeepFile);
|
||||
fos.write("test".getBytes());
|
||||
@@ -474,8 +474,9 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
Session<?> session = sessionFactory.getSession();
|
||||
session.close();
|
||||
session = TestUtils.getPropertyValue(session, "targetSession", Session.class);
|
||||
ChannelSftp channel = spy(TestUtils.getPropertyValue(session, "channel", ChannelSftp.class));
|
||||
new DirectFieldAccessor(session).setPropertyValue("channel", channel);
|
||||
SftpClient sftpClient = spy(TestUtils.getPropertyValue(session, "sftpClient", SftpClient.class));
|
||||
doNothing().when(sftpClient).setStat(anyString(), any(SftpClient.Attributes.class));
|
||||
new DirectFieldAccessor(session).setPropertyValue("sftpClient", sftpClient);
|
||||
|
||||
String dir = "sftpSource/";
|
||||
this.inboundMGetRecursive.send(new GenericMessage<Object>(dir + "*"));
|
||||
@@ -492,8 +493,8 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
.isIn("sftpTarget/localSource1.txt", "sftpTarget/localSource2.txt");
|
||||
assertThat(out.getPayload().get(1))
|
||||
.isIn("sftpTarget/localSource1.txt", "sftpTarget/localSource2.txt");
|
||||
verify(channel).chmod(0600, "sftpTarget/localSource1.txt");
|
||||
verify(channel).chmod(0600, "sftpTarget/localSource2.txt");
|
||||
verify(sftpClient).setStat(eq("sftpTarget/localSource1.txt"), any(SftpClient.Attributes.class));
|
||||
verify(sftpClient).setStat(eq("sftpTarget/localSource2.txt"), any(SftpClient.Attributes.class));
|
||||
resetSessionCache();
|
||||
assertThat(this.config.latch.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
assertThat(this.config.events).hasSize(6);
|
||||
@@ -676,9 +677,10 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
}
|
||||
|
||||
private void assertLength6(SftpRemoteFileTemplate template) {
|
||||
LsEntry[] files = template.execute(session -> session.list("sftpTarget/appending.txt"));
|
||||
assertThat(files.length).isEqualTo(1);
|
||||
assertThat(files[0].getAttrs().getSize()).isEqualTo(6);
|
||||
SftpClient.DirEntry[] files = template.execute(session -> session.list("sftpTarget"));
|
||||
assertThat(files.length).isEqualTo(3);
|
||||
assertThat(files[2].getFilename()).isEqualTo("appending.txt");
|
||||
assertThat(files[2].getAttributes().getSize()).isEqualTo(6);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -689,7 +691,7 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
sessionFactory.setUser("foo");
|
||||
sessionFactory.setPassword("foo");
|
||||
sessionFactory.setAllowUnknownKeys(true);
|
||||
Session<LsEntry> session = sessionFactory.getSession();
|
||||
Session<SftpClient.DirEntry> session = sessionFactory.getSession();
|
||||
|
||||
assertThat(session.exists("sftpSource")).isTrue();
|
||||
assertThat(session.exists("notExist")).isFalse();
|
||||
@@ -699,15 +701,15 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
assertThatExceptionOfType(UncheckedIOException.class)
|
||||
.isThrownBy(() -> session.exists("any"))
|
||||
.withRootCauseInstanceOf(IOException.class)
|
||||
.withStackTraceContaining("Pipe closed");
|
||||
.withStackTraceContaining("lstat(any) client is closed");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final class TestMessageSessionCallback
|
||||
implements MessageSessionCallback<LsEntry, Object> {
|
||||
implements MessageSessionCallback<SftpClient.DirEntry, Object> {
|
||||
|
||||
@Override
|
||||
public Object doInSession(Session<ChannelSftp.LsEntry> session, Message<?> requestMessage) {
|
||||
public Object doInSession(Session<SftpClient.DirEntry> session, Message<?> requestMessage) {
|
||||
return ((String) requestMessage.getPayload()).toUpperCase();
|
||||
}
|
||||
|
||||
@@ -722,13 +724,13 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
private volatile CountDownLatch latch;
|
||||
|
||||
@Bean
|
||||
public SessionFactory<LsEntry> sftpSessionFactory(ApplicationContext context) {
|
||||
public SessionFactory<SftpClient.DirEntry> sftpSessionFactory(ApplicationContext context) {
|
||||
SftpServerOutboundTests.eventListener().setApplicationEventPublisher(context);
|
||||
return SftpServerOutboundTests.sessionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SftpRemoteFileTemplate template(SessionFactory<LsEntry> sf) {
|
||||
public SftpRemoteFileTemplate template(SessionFactory<SftpClient.DirEntry> sf) {
|
||||
return new SftpRemoteFileTemplate(sf);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
|
||||
import com.jcraft.jsch.Proxy;
|
||||
import com.jcraft.jsch.ProxyHTTP;
|
||||
import com.jcraft.jsch.ProxySOCKS4;
|
||||
import com.jcraft.jsch.ProxySOCKS5;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 4.3
|
||||
*
|
||||
*/
|
||||
public class ProxyTests {
|
||||
|
||||
@Test
|
||||
@Disabled // TODO Use SftpTestSupport
|
||||
/*
|
||||
* Needs host and account
|
||||
*/
|
||||
public void testSimpleConnect() {
|
||||
DefaultSftpSessionFactory sf = new DefaultSftpSessionFactory();
|
||||
sf.setHost("10.0.0.3");
|
||||
sf.setPort(22);
|
||||
sf.setUser("ftptest");
|
||||
sf.setPassword("ftptest");
|
||||
sf.setAllowUnknownKeys(true);
|
||||
sf.getSession().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
/*
|
||||
* Needs host and account and...
|
||||
* $ ssh -D 1080 -f -N gpr@10.0.0.3
|
||||
*/
|
||||
public void testProxyConnect() throws Exception {
|
||||
DefaultSftpSessionFactory sf = new DefaultSftpSessionFactory();
|
||||
JschProxyFactoryBean proxyFactoryBean = new JschProxyFactoryBean(JschProxyFactoryBean.Type.SOCKS5, "localhost",
|
||||
1080, "ftptest", "ftptest");
|
||||
proxyFactoryBean.afterPropertiesSet();
|
||||
sf.setHost("10.0.0.3");
|
||||
sf.setPort(22);
|
||||
sf.setUser("ftptest");
|
||||
sf.setPassword("ftptest");
|
||||
sf.setProxy(proxyFactoryBean.getObject());
|
||||
sf.setAllowUnknownKeys(true);
|
||||
sf.getSession().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFactoryBean() throws Exception {
|
||||
JschProxyFactoryBean proxyFactoryBean = new JschProxyFactoryBean(JschProxyFactoryBean.Type.SOCKS5, "localhost",
|
||||
1080, "ftptest", "pass");
|
||||
proxyFactoryBean.afterPropertiesSet();
|
||||
Proxy proxy = proxyFactoryBean.getObject();
|
||||
assertProxy(proxy, ProxySOCKS5.class);
|
||||
|
||||
proxyFactoryBean = new JschProxyFactoryBean(JschProxyFactoryBean.Type.SOCKS4, "localhost",
|
||||
1080, "ftptest", "pass");
|
||||
proxyFactoryBean.afterPropertiesSet();
|
||||
proxy = proxyFactoryBean.getObject();
|
||||
assertProxy(proxy, ProxySOCKS4.class);
|
||||
|
||||
proxyFactoryBean = new JschProxyFactoryBean(JschProxyFactoryBean.Type.HTTP, "localhost",
|
||||
1080, "ftptest", "pass");
|
||||
proxyFactoryBean.afterPropertiesSet();
|
||||
proxy = proxyFactoryBean.getObject();
|
||||
assertProxy(proxy, ProxyHTTP.class);
|
||||
}
|
||||
|
||||
private void assertProxy(Proxy proxy, Class<? extends Proxy> clazz) {
|
||||
assertThat(proxy).isInstanceOf(clazz);
|
||||
assertThat(TestUtils.getPropertyValue(proxy, "user")).isEqualTo("ftptest");
|
||||
assertThat(TestUtils.getPropertyValue(proxy, "passwd")).isEqualTo("pass");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.apache.sshd.client.ClientFactoryManager;
|
||||
import org.apache.sshd.client.config.hosts.HostPatternsHolder;
|
||||
import org.apache.sshd.client.config.hosts.KnownHostEntry;
|
||||
import org.apache.sshd.client.session.ClientSession;
|
||||
import org.apache.sshd.common.Factory;
|
||||
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
|
||||
import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
|
||||
import org.apache.sshd.common.random.JceRandomFactory;
|
||||
import org.apache.sshd.common.util.GenericUtils;
|
||||
import org.apache.sshd.common.util.net.SshdSocketAddress;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIf;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
@EnabledIf("isDefaultKnownHostsFilePresent")
|
||||
public class ResourceKnownHostsServerKeyVerifierTests {
|
||||
|
||||
private static final Map<SshdSocketAddress, PublicKey> HOST_KEYS = new TreeMap<>(SshdSocketAddress.BY_HOST_AND_PORT);
|
||||
|
||||
@BeforeAll
|
||||
static void loadHostKeys() throws GeneralSecurityException, IOException {
|
||||
Map<SshdSocketAddress, KnownHostEntry> hostsEntries = loadEntries(KnownHostEntry.getDefaultKnownHostsFile());
|
||||
for (Map.Entry<SshdSocketAddress, KnownHostEntry> ke : hostsEntries.entrySet()) {
|
||||
SshdSocketAddress hostIdentity = ke.getKey();
|
||||
KnownHostEntry entry = ke.getValue();
|
||||
AuthorizedKeyEntry authEntry = entry.getKeyEntry();
|
||||
PublicKey key = authEntry.resolvePublicKey(null, Collections.emptyMap(), PublicKeyEntryResolver.FAILING);
|
||||
HOST_KEYS.put(hostIdentity, key);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
void testServerKeys() {
|
||||
ResourceKnownHostsServerKeyVerifier verifier
|
||||
= new ResourceKnownHostsServerKeyVerifier(
|
||||
new FileSystemResource(KnownHostEntry.getDefaultKnownHostsFile()));
|
||||
|
||||
ClientFactoryManager manager = Mockito.mock(ClientFactoryManager.class);
|
||||
Mockito.when(manager.getRandomFactory()).thenReturn((Factory) JceRandomFactory.INSTANCE);
|
||||
|
||||
HOST_KEYS.forEach((key, value) -> {
|
||||
ClientSession session = Mockito.mock(ClientSession.class);
|
||||
Mockito.when(session.getFactoryManager()).thenReturn(manager);
|
||||
|
||||
Mockito.when(session.getConnectAddress()).thenReturn(key);
|
||||
assertThat(verifier.verifyServerKey(session, key, value)).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
private static Map<SshdSocketAddress, KnownHostEntry> loadEntries(Path file) throws IOException {
|
||||
Collection<KnownHostEntry> entries = KnownHostEntry.readKnownHostEntries(file);
|
||||
if (GenericUtils.isEmpty(entries)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<SshdSocketAddress, KnownHostEntry> hostsMap = new TreeMap<>(SshdSocketAddress.BY_HOST_AND_PORT);
|
||||
for (KnownHostEntry entry : entries) {
|
||||
String line = entry.getConfigLine();
|
||||
// extract hosts
|
||||
int pos = line.indexOf(' ');
|
||||
String patterns = line.substring(0, pos);
|
||||
if (entry.getHashedEntry() != null) {
|
||||
hostsMap.put(new SshdSocketAddress("localhost", 0), entry);
|
||||
}
|
||||
else {
|
||||
String[] addrs = GenericUtils.split(patterns, ',');
|
||||
for (String a : addrs) {
|
||||
int port = 0;
|
||||
if (a.charAt(0) == HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM) {
|
||||
pos = a.indexOf(HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM, 1);
|
||||
|
||||
port = Integer.parseInt(a.substring(pos + 2));
|
||||
a = a.substring(1, pos);
|
||||
}
|
||||
hostsMap.put(new SshdSocketAddress(a, port), entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hostsMap;
|
||||
}
|
||||
|
||||
static boolean isDefaultKnownHostsFilePresent() {
|
||||
return KnownHostEntry.getDefaultKnownHostsFile().toFile().exists();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2020 the original author or authors.
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,10 +20,11 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
@@ -44,11 +45,6 @@ import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
import com.jcraft.jsch.SftpException;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
@@ -61,7 +57,7 @@ import com.jcraft.jsch.SftpException;
|
||||
public class SftpRemoteFileTemplateTests extends SftpTestSupport {
|
||||
|
||||
@Autowired
|
||||
private CachingSessionFactory<LsEntry> sessionFactory;
|
||||
private CachingSessionFactory<SftpClient.DirEntry> sessionFactory;
|
||||
|
||||
@Test
|
||||
public void testINT3412AppendStatRmdir() {
|
||||
@@ -82,24 +78,24 @@ public class SftpRemoteFileTemplateTests extends SftpTestSupport {
|
||||
template.append(new GenericMessage<>("foo"));
|
||||
template.append(new GenericMessage<>("bar"));
|
||||
assertThat(template.exists("foo/foobar.txt")).isTrue();
|
||||
template.executeWithClient((ClientCallbackWithoutResult<ChannelSftp>) client -> {
|
||||
template.executeWithClient((ClientCallbackWithoutResult<SftpClient>) client -> {
|
||||
try {
|
||||
SftpATTRS file = client.lstat("foo/foobar.txt");
|
||||
SftpClient.Attributes file = client.lstat("foo/foobar.txt");
|
||||
assertThat(file.getSize()).isEqualTo(6);
|
||||
}
|
||||
catch (SftpException e) {
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
template.execute((SessionCallbackWithoutResult<LsEntry>) session -> {
|
||||
LsEntry[] files = session.list("foo/");
|
||||
template.execute((SessionCallbackWithoutResult<SftpClient.DirEntry>) session -> {
|
||||
SftpClient.DirEntry[] files = session.list("foo/");
|
||||
assertThat(files.length).isEqualTo(4);
|
||||
assertThat(session.remove("foo/foobar.txt")).isTrue();
|
||||
assertThat(session.rmdir("foo/bar/")).isTrue();
|
||||
files = session.list("foo/");
|
||||
assertThat(files.length).isEqualTo(2);
|
||||
List<LsEntry> list = Arrays.asList(files);
|
||||
assertThat(list.stream().map(l -> l.getFilename()).collect(Collectors.toList())).contains(".", "..");
|
||||
List<String> fileNames = Arrays.stream(files).map(SftpClient.DirEntry::getFilename).toList();
|
||||
assertThat(fileNames).contains(".", "..");
|
||||
assertThat(session.rmdir("foo/")).isTrue();
|
||||
});
|
||||
assertThat(template.exists("foo")).isFalse();
|
||||
@@ -107,8 +103,8 @@ public class SftpRemoteFileTemplateTests extends SftpTestSupport {
|
||||
|
||||
@Test
|
||||
public void testNoDeadLockOnSend() {
|
||||
CachingSessionFactory<LsEntry> cachingSessionFactory = new CachingSessionFactory<>(sessionFactory(), 1);
|
||||
SftpRemoteFileTemplate template = new SftpRemoteFileTemplate(cachingSessionFactory);
|
||||
CachingSessionFactory<SftpClient.DirEntry> sessionFactory = new CachingSessionFactory<>(sessionFactory(), 1);
|
||||
SftpRemoteFileTemplate template = new SftpRemoteFileTemplate(sessionFactory);
|
||||
template.setRemoteDirectoryExpression(new LiteralExpression(""));
|
||||
template.setBeanFactory(mock(BeanFactory.class));
|
||||
template.setUseTemporaryFileName(false);
|
||||
@@ -125,14 +121,14 @@ public class SftpRemoteFileTemplateTests extends SftpTestSupport {
|
||||
.withCauseInstanceOf(MessagingException.class)
|
||||
.withStackTraceContaining("he destination file already exists at 'test.file'.");
|
||||
|
||||
cachingSessionFactory.destroy();
|
||||
sessionFactory.destroy();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class Config {
|
||||
|
||||
@Bean
|
||||
public SessionFactory<LsEntry> ftpSessionFactory() {
|
||||
public SessionFactory<SftpClient.DirEntry> ftpSessionFactory() {
|
||||
return SftpRemoteFileTemplateTests.sessionFactory();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2021 the original author or authors.
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,6 +35,7 @@ import java.util.Collections;
|
||||
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
|
||||
import org.apache.sshd.server.SshServer;
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -44,8 +45,6 @@ import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* *
|
||||
* @author Gary Russell
|
||||
@@ -59,8 +58,7 @@ public class SftpServerTests {
|
||||
|
||||
@Test
|
||||
public void testUcPw() throws Exception {
|
||||
SshServer server = SshServer.setUpDefaultServer();
|
||||
try {
|
||||
try (SshServer server = SshServer.setUpDefaultServer()) {
|
||||
server.setPasswordAuthenticator((arg0, arg1, arg2) -> true);
|
||||
server.setPort(0);
|
||||
server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("hostkey.ser").toPath()));
|
||||
@@ -76,12 +74,9 @@ public class SftpServerTests {
|
||||
f.setUser("user");
|
||||
f.setPassword("pass");
|
||||
f.setAllowUnknownKeys(true);
|
||||
Session<LsEntry> session = f.getSession();
|
||||
Session<SftpClient.DirEntry> session = f.getSession();
|
||||
doTest(server, session);
|
||||
}
|
||||
finally {
|
||||
server.stop(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -94,11 +89,9 @@ public class SftpServerTests {
|
||||
testKeyExchange("id_rsa_pp.pub", "id_rsa_pp", "secret");
|
||||
}
|
||||
|
||||
private void testKeyExchange(String pubKey, String privKey, String passphrase)
|
||||
throws Exception {
|
||||
SshServer server = SshServer.setUpDefaultServer();
|
||||
private void testKeyExchange(String pubKey, String privKey, String passphrase) throws Exception {
|
||||
final PublicKey allowedKey = decodePublicKey(pubKey);
|
||||
try {
|
||||
try (SshServer server = SshServer.setUpDefaultServer()) {
|
||||
server.setPublickeyAuthenticator((username, key, session) -> key.equals(allowedKey));
|
||||
server.setPort(0);
|
||||
server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("hostkey.ser").toPath()));
|
||||
@@ -116,12 +109,9 @@ public class SftpServerTests {
|
||||
InputStream stream = new ClassPathResource(privKey).getInputStream();
|
||||
f.setPrivateKey(new ByteArrayResource(FileCopyUtils.copyToByteArray(stream)));
|
||||
f.setPrivateKeyPassphrase(passphrase);
|
||||
Session<LsEntry> session = f.getSession();
|
||||
Session<SftpClient.DirEntry> session = f.getSession();
|
||||
doTest(server, session);
|
||||
}
|
||||
finally {
|
||||
server.stop(true);
|
||||
}
|
||||
}
|
||||
|
||||
private PublicKey decodePublicKey(String key) throws Exception {
|
||||
@@ -155,12 +145,15 @@ public class SftpServerTests {
|
||||
return new BigInteger(bytes);
|
||||
}
|
||||
|
||||
protected void doTest(SshServer server, Session<LsEntry> session) throws IOException {
|
||||
protected void doTest(SshServer server, Session<SftpClient.DirEntry> session) throws IOException {
|
||||
assertThat(server.getActiveSessions().size()).isEqualTo(1);
|
||||
LsEntry[] list = session.list(".");
|
||||
if (list.length > 0) {
|
||||
session.remove("*");
|
||||
SftpClient.DirEntry[] list = session.list(".");
|
||||
for (SftpClient.DirEntry entry : list) {
|
||||
if (entry.getAttributes().isRegularFile()) {
|
||||
session.remove(entry.getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
session.write(new ByteArrayInputStream("foo".getBytes()), "bar");
|
||||
list = session.list(".");
|
||||
assertThat(list[1].getFilename()).isEqualTo("bar");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2021 the original author or authors.
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,28 +18,15 @@ package org.springframework.integration.sftp.session;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.apache.sshd.common.SshException;
|
||||
import org.apache.sshd.server.SshServer;
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.UserInfo;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
@@ -54,8 +41,7 @@ public class SftpSessionFactoryTests {
|
||||
*/
|
||||
@Test
|
||||
public void testConnectFailSocketOpen() throws Exception {
|
||||
SshServer server = SshServer.setUpDefaultServer();
|
||||
try {
|
||||
try (SshServer server = SshServer.setUpDefaultServer()) {
|
||||
server.setPasswordAuthenticator((arg0, arg1, arg2) -> true);
|
||||
server.setPort(0);
|
||||
server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("hostkey.ser").toPath()));
|
||||
@@ -69,24 +55,22 @@ public class SftpSessionFactoryTests {
|
||||
int n = 0;
|
||||
while (true) {
|
||||
try {
|
||||
f.getSession();
|
||||
f.getSession().connect();
|
||||
fail("Expected Exception");
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (e instanceof IllegalStateException && "failed to create SFTP Session".equals(e.getMessage())) {
|
||||
if (e.getCause() instanceof IllegalStateException) {
|
||||
if (e.getCause().getCause() instanceof JSchException) {
|
||||
if (e.getCause().getCause().getCause() instanceof ConnectException) {
|
||||
assertThat(n++ < 100).as("Server failed to start in 10 seconds").isTrue();
|
||||
Thread.sleep(100);
|
||||
continue;
|
||||
}
|
||||
if (e.getCause().getCause() instanceof ConnectException) {
|
||||
assertThat(n++ < 100).as("Server failed to start in 10 seconds").isTrue();
|
||||
Thread.sleep(100);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertThat(e).isInstanceOf(IllegalStateException.class);
|
||||
assertThat(e.getCause()).isInstanceOf(IllegalStateException.class);
|
||||
assertThat(e.getCause().getMessage()).isEqualTo("failed to connect");
|
||||
assertThat(e.getCause()).isInstanceOf(SshException.class);
|
||||
assertThat(e.getCause().getMessage()).isEqualTo("Server key did not validate");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -98,130 +82,6 @@ public class SftpSessionFactoryTests {
|
||||
|
||||
assertThat(server.getActiveSessions().size()).isEqualTo(0);
|
||||
}
|
||||
finally {
|
||||
server.stop(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswordPassPhraseViaUserInfo() {
|
||||
DefaultSftpSessionFactory f = new DefaultSftpSessionFactory();
|
||||
f.setUser("user");
|
||||
f.setAllowUnknownKeys(true);
|
||||
UserInfo ui = mock(UserInfo.class);
|
||||
when(ui.getPassword()).thenReturn("pass");
|
||||
when(ui.getPassphrase()).thenReturn("pp").thenReturn(null);
|
||||
f.setUserInfo(ui);
|
||||
UserInfo userInfo = TestUtils.getPropertyValue(f, "userInfoWrapper", UserInfo.class);
|
||||
assertThat(userInfo.getPassword()).isEqualTo("pass");
|
||||
f.setPassword("foo");
|
||||
try {
|
||||
userInfo.getPassword();
|
||||
fail("expected Exception");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
assertThat(e.getMessage()).startsWith("When a 'UserInfo' is provided, 'password' is not allowed");
|
||||
}
|
||||
assertThat(userInfo.getPassphrase()).isEqualTo("pp");
|
||||
f.setPrivateKeyPassphrase("bar");
|
||||
try {
|
||||
userInfo.getPassphrase();
|
||||
fail("expected Exception");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
assertThat(e
|
||||
.getMessage()).startsWith("When a 'UserInfo' is provided, 'privateKeyPassphrase' is not allowed");
|
||||
}
|
||||
f.setUserInfo(null);
|
||||
assertThat(userInfo.getPassword()).isEqualTo("foo");
|
||||
assertThat(userInfo.getPassphrase()).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultUserInfoFalse() throws Exception {
|
||||
SshServer server = SshServer.setUpDefaultServer();
|
||||
try {
|
||||
DefaultSftpSessionFactory f = createServerAndClient(server);
|
||||
expectReject(f);
|
||||
}
|
||||
finally {
|
||||
server.stop(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultUserInfoTrue() throws Exception {
|
||||
SshServer server = SshServer.setUpDefaultServer();
|
||||
try {
|
||||
DefaultSftpSessionFactory f = createServerAndClient(server);
|
||||
f.setChannelConnectTimeout(Duration.ofSeconds(6));
|
||||
f.setAllowUnknownKeys(true);
|
||||
SftpSession session = f.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(session, "channelConnectTimeout", Integer.class)).isEqualTo(6_000);
|
||||
session.close();
|
||||
}
|
||||
finally {
|
||||
server.stop(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomUserInfoFalse() throws Exception {
|
||||
SshServer server = SshServer.setUpDefaultServer();
|
||||
try {
|
||||
DefaultSftpSessionFactory f = createServerAndClient(server);
|
||||
UserInfo userInfo = mock(UserInfo.class);
|
||||
when(userInfo.promptYesNo(anyString())).thenReturn(false);
|
||||
f.setUserInfo(userInfo);
|
||||
expectReject(f);
|
||||
}
|
||||
finally {
|
||||
server.stop(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void expectReject(DefaultSftpSessionFactory f) {
|
||||
try {
|
||||
f.getSession().close();
|
||||
fail("Expected Exception");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e).isInstanceOf(IllegalStateException.class);
|
||||
assertThat(e.getCause()).isInstanceOf(IllegalStateException.class);
|
||||
assertThat(e.getCause().getCause()).isInstanceOf(JSchException.class);
|
||||
assertThat(e.getCause().getCause().getMessage()).contains("reject HostKey");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomUserInfoTrue() throws Exception {
|
||||
SshServer server = SshServer.setUpDefaultServer();
|
||||
try {
|
||||
DefaultSftpSessionFactory f = createServerAndClient(server);
|
||||
UserInfo userInfo = mock(UserInfo.class);
|
||||
when(userInfo.promptYesNo(anyString())).thenReturn(true);
|
||||
f.setUserInfo(userInfo);
|
||||
f.getSession().close();
|
||||
}
|
||||
finally {
|
||||
server.stop(true);
|
||||
}
|
||||
}
|
||||
|
||||
private DefaultSftpSessionFactory createServerAndClient(SshServer server) throws IOException {
|
||||
server.setPublickeyAuthenticator((username, key, session) -> true);
|
||||
server.setPort(0);
|
||||
server.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
|
||||
server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("hostkey.ser").toPath()));
|
||||
server.start();
|
||||
|
||||
DefaultSftpSessionFactory f = new DefaultSftpSessionFactory();
|
||||
f.setHost("localhost");
|
||||
f.setPort(server.getPort());
|
||||
f.setUser("user");
|
||||
Resource privateKey = new ClassPathResource("id_rsa");
|
||||
f.setPrivateKey(privateKey);
|
||||
return f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,11 @@
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
|
||||
import static org.mockito.BDDMockito.willReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
@@ -28,9 +33,12 @@ public class SftpTestSessionFactory {
|
||||
super();
|
||||
}
|
||||
|
||||
public static SftpSession createSftpSession(com.jcraft.jsch.Session jschSession) {
|
||||
SftpSession sftpSession = new SftpSession(jschSession);
|
||||
sftpSession.connect();
|
||||
public static SftpSession createSftpSession(SftpClient sftpClient) {
|
||||
SftpSession sftpSession = spy(new SftpSession(sftpClient));
|
||||
willReturn("mock.sftp.host:22")
|
||||
.given(sftpSession)
|
||||
.getHostPort();
|
||||
return sftpSession;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Logger name="com.jcraft.jsch" level="warn"/>
|
||||
<Logger name="org.apache.sshd" level="error"/>
|
||||
<Logger name="org.springframework.integration" level="warn"/>
|
||||
<Logger name="org.springframework.integration.sftp" level="warn"/>
|
||||
<Root level="warn">
|
||||
|
||||
@@ -232,7 +232,7 @@ This section describes general changes to the Spring Integration SFTP functional
|
||||
====== Factory Bean
|
||||
|
||||
We added a new factory bean to simplify the configuration of Jsch proxies for SFTP.
|
||||
See <<./sftp.adoc#sftp-proxy-factory-bean,Proxy Factory Bean>> for more information.
|
||||
See `JschProxyFactoryBean` for more information.
|
||||
|
||||
====== `chmod` Changes
|
||||
|
||||
|
||||
@@ -10,6 +10,12 @@ The SFTP protocol requires a secure channel, such as SSH, and visibility to a cl
|
||||
Spring Integration supports sending and receiving files over SFTP by providing three client side endpoints: inbound channel adapter, outbound channel adapter, and outbound gateway.
|
||||
It also provides convenient namespace configuration to define these client components.
|
||||
|
||||
NOTE: Starting with version 6.0, an outdated JCraft JSch client has been replaced with modern https://mina.apache.org/sshd-project/index.html[Apache MINA SSHD] framework.
|
||||
This caused a lot of breaking changes in the framework components.
|
||||
However, in most cases, such a migration is hidden behind Spring Integration API.
|
||||
The most drastic changed has happened with a `DefaultSftpSessionFactory` which is based now on the `org.apache.sshd.client.SshClient` and exposes some if its configuration properties.
|
||||
|
||||
|
||||
You need to include this dependency into your project:
|
||||
|
||||
====
|
||||
@@ -64,16 +70,16 @@ You can configure the SFTP session factory with a regular bean definition, as th
|
||||
====
|
||||
|
||||
Every time an adapter requests a session object from its `SessionFactory`, a new SFTP session is created.
|
||||
Under the covers, the SFTP Session Factory relies on the http://www.jcraft.com/jsch[JSch] library to provide the SFTP capabilities.
|
||||
Under the covers, the SFTP Session Factory relies on the https://mina.apache.org/sshd-project/index.html[Apache MINA SSHD] library to provide the SFTP capabilities.
|
||||
|
||||
However, Spring Integration also supports the caching of SFTP sessions.
|
||||
See <<sftp-session-caching>> for more information.
|
||||
|
||||
[IMPORTANT]
|
||||
=====
|
||||
JSch supports multiple channels (operations) over a connection to the server.
|
||||
The `SshClient` supports multiple channels (operations) over a connection to the server.
|
||||
By default, the Spring Integration session factory uses a separate physical connection for each channel.
|
||||
Since Spring Integration 3.0, you can configure the session factory (using a boolean constructor arg - default `false`) to use a single connection to the server and create multiple `JSch` channels on that single connection.
|
||||
Since Spring Integration 3.0, you can configure the session factory (using a boolean constructor arg - default `false`) to use a single connection to the server and create multiple `SftpClient` instances on that single connection.
|
||||
|
||||
When using this feature, you must wrap the session factory in a caching session factory, as <<sftp-session-caching,described later>>, so that the connection is not physically closed when an operation completes.
|
||||
|
||||
@@ -82,9 +88,6 @@ If the cache is reset, the session is disconnected only when the last channel is
|
||||
The connection is refreshed if it is found to be disconnected when a new operation obtains a session.
|
||||
=====
|
||||
|
||||
NOTE: If you experience connectivity problems and would like to trace session creation and see which sessions are polled, you may enable tracing by setting the logger to `TRACE` level (for example, `log4j.category.org.springframework.integration.sftp=TRACE`).
|
||||
See <<sftp-jsch-logging>>.
|
||||
|
||||
Now all you need to do is inject this SFTP session factory into your adapters.
|
||||
|
||||
NOTE: A more practical way to provide values for the SFTP session factory is to use Spring's https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-placeholderconfigurer[property placeholder support].
|
||||
@@ -94,35 +97,32 @@ NOTE: A more practical way to provide values for the SFTP session factory is to
|
||||
|
||||
The following list describes all the properties that are exposed by the https://docs.spring.io/spring-integration/api/org/springframework/integration/sftp/session/DefaultSftpSessionFactory.html[`DefaultSftpSessionFactory`].
|
||||
|
||||
`isSharedSession` (constructor argument)::When `true`, a single connection is used, and `JSch Channels` are multiplexed.
|
||||
`isSharedSession` (constructor argument)::When `true`, a single `SftpClient` is used for all the requested `SftpSession` instances.
|
||||
It defaults to `false`.
|
||||
|
||||
`clientVersion`::Lets you set the client version property.
|
||||
It's default depends on the underlying JSch version but it will look like: _SSH-2.0-JSCH-0.1.45_
|
||||
`sftpVersionSelector`::An `SftpVersionSelector` instance for SFTP protocol selection.
|
||||
The default one is `SftpVersionSelector.CURRENT`.
|
||||
|
||||
`enableDaemonThread`::If `true`, all threads are daemon threads.
|
||||
If set to `false`, normal non-daemon threads are used instead.
|
||||
This property is set on the underlying https://epaul.github.io/jsch-documentation/javadoc/com/jcraft/jsch/Session.html[session].
|
||||
There, this property defaults to `false`.
|
||||
|
||||
`host`::The URL of the host to which you want to connect.
|
||||
`host`::The URL of the host to which to connect.
|
||||
Required.
|
||||
|
||||
`hostKeyAlias`::Sets the host key alias, which is used when comparing the host key to the known hosts list.
|
||||
|
||||
`knownHostsResource`::Specifies the file resource that used for a host key repository.
|
||||
The file has the same format as OpenSSH's `known_hosts` file and is required and must be pre-populated if `allowUnknownKeys` is false.
|
||||
|
||||
`password`::The password to authenticate against the remote host.
|
||||
If a password is not provided, then the `privateKey` property is required.
|
||||
It is not allowed if you set `userInfo`.
|
||||
The password is obtained from that object.
|
||||
`hostConfig`::An `org.apache.sshd.client.config.hosts.HostConfigEntry` instance as an alternative for the user/host/port options.
|
||||
Can be configured with a proxy jump property.
|
||||
|
||||
`port`::The port over which the SFTP connection shall be established.
|
||||
If not specified, this value defaults to `22`.
|
||||
If specified, this properties must be a positive number.
|
||||
|
||||
`privateKey`::Lets you set a https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/io/Resource.html[resource] that represents the location of the private key used for authenticating against the remote host.
|
||||
`user`::The remote user to use.
|
||||
Required.
|
||||
|
||||
`knownHostsResource`::An `org.springframework.core.io.Resource` that used for a host key repository.
|
||||
The content of the resource has to be the same format as OpenSSH `known_hosts` file and is required and must be pre-populated if `allowUnknownKeys` is false.
|
||||
|
||||
`password`::The password to authenticate against the remote host.
|
||||
If a password is not provided, then the `privateKey` property is required.
|
||||
|
||||
`privateKey`::An `org.springframework.core.io.Resource` that represents the location of the private key used for authenticating against the remote host.
|
||||
If the `privateKey` is not provided, then the `password` property is required.
|
||||
|
||||
`privateKeyPassphrase`::The password for the private key.
|
||||
@@ -130,66 +130,15 @@ If you set `userInfo`, `privateKeyPassphrase` is not allowed .
|
||||
The passphrase is obtained from that object.
|
||||
Optional.
|
||||
|
||||
`proxy`::Allows for specifying a JSch-based https://epaul.github.com/jsch-documentation/javadoc/com/jcraft/jsch/Proxy.html[proxy].
|
||||
If set, the proxy object is used to create the connection to the remote host through the proxy.
|
||||
See <<sftp-proxy-factory-bean>> for a convenient way to configure the proxy.
|
||||
|
||||
`serverAliveCountMax`::Specifies the number of server-alive messages, which are sent without any reply from the server before disconnecting.
|
||||
If not set, this property defaults to `1`.
|
||||
|
||||
`serverAliveInterval`::Sets the timeout interval (in milliseconds) before a server-alive message is sent, in case no message is received from the server.
|
||||
|
||||
`sessionConfig`::By using `Properties`, you can set additional configuration setting on the underlying JSch Session.
|
||||
|
||||
`socketFactory`::Lets you pass in a https://epaul.github.com/jsch-documentation/javadoc/com/jcraft/jsch/SocketFactory.html[`SocketFactory`].
|
||||
The socket factory is used to create a socket to the target host.
|
||||
When a proxy is used, the socket factory is passed to the proxy.
|
||||
By default, plain TCP sockets are used.
|
||||
|
||||
`timeout`::The timeout property is used as the socket timeout parameter, as well as the default connection timeout.
|
||||
Defaults to `0`, which means, that no timeout will occur.
|
||||
|
||||
`user`::The remote user to use.
|
||||
Required.
|
||||
|
||||
[[sftp-unk-keys]]
|
||||
`allowUnknownKeys`::Set to `true` to allow connections to hosts with unknown (or changed) keys.
|
||||
Its default is 'false'.
|
||||
It is applied only if no `userInfo` is provided.
|
||||
If `false`, a pre-populated `knownHosts` file is required.
|
||||
|
||||
`userInfo`::Set a custom `UserInfo` to be used during authentication.
|
||||
In particular, `promptYesNo()` is invoked when an unknown (or changed) host key is received.
|
||||
See also <<sftp-unk-keys,`allowUnknownKeys`>>.
|
||||
When you provide a `UserInfo`, the `password` and private key `passphrase` are obtained from it, and you cannot set discrete `password` and `privateKeyPassphrase` properties.
|
||||
|
||||
[[sftp-proxy-factory-bean]]
|
||||
=== Proxy Factory Bean
|
||||
|
||||
`Jsch` provides a mechanism to connect to the server over an HTTP or SOCKS proxy.
|
||||
To use this feature, configure the `Proxy` and provide a reference to the `DefaultSftpSessionFactory`, as discussed earlier.
|
||||
Three implementations are provided by `Jsch`: `HTTP`, `SOCKS4`, and `SOCKS5`.
|
||||
Spring Integration 4.3 introduced a `FactoryBean`, easing configuration of these proxies by allowing property injection, as the following example shows:
|
||||
|
||||
====
|
||||
[source, xml]
|
||||
----
|
||||
<bean id="proxySocks5" class="org.springframework.integration.sftp.session.JschProxyFactoryBean">
|
||||
<constructor-arg value="SOCKS5" />
|
||||
<constructor-arg value="${sftp.proxy.address}" />
|
||||
<constructor-arg value="${sftp.proxy.port}" />
|
||||
<constructor-arg value="${sftp.proxy.user}" />
|
||||
<constructor-arg value="${sftp.proxy.pw}" />
|
||||
</bean>
|
||||
|
||||
<bean id="sessionFactory"
|
||||
class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory" >
|
||||
...
|
||||
<property name="proxy" ref="proxySocks5" />
|
||||
...
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
`userInteraction`::A custom `org.apache.sshd.client.auth.keyboard.UserInteraction` to be used during authentication.
|
||||
|
||||
[[sftp-dsf]]
|
||||
=== Delegating Session Factory
|
||||
@@ -269,7 +218,7 @@ When using `isSharedSession=true`, the channel is closed and the shared session
|
||||
New requests for sessions establish new sessions as necessary.
|
||||
|
||||
Starting with version 5.1, the `CachingSessionFactory` has a new property `testSession`.
|
||||
When true, the session will be tested by performing a `stat(getHome())` command to ensure it is still active; if not, it will be removed from the cache; a new session is created if no active sessions are in the cache.
|
||||
When true, the session will be tested by performing a `REALPATH` command for an empty path to ensure it is still active; if not, it will be removed from the cache; a new session is created if no active sessions are in the cache.
|
||||
|
||||
[[sftp-rft]]
|
||||
=== Using `RemoteFileTemplate`
|
||||
@@ -485,7 +434,7 @@ public class SftpJavaApplication {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SessionFactory<LsEntry> sftpSessionFactory() {
|
||||
public SessionFactory<SftpClient.DirEntry> sftpSessionFactory() {
|
||||
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true);
|
||||
factory.setHost("localhost");
|
||||
factory.setPort(port);
|
||||
@@ -493,7 +442,7 @@ public class SftpJavaApplication {
|
||||
factory.setPassword("foo");
|
||||
factory.setAllowUnknownKeys(true);
|
||||
factory.setTestSession(true);
|
||||
return new CachingSessionFactory<LsEntry>(factory);
|
||||
return new CachingSessionFactory<>(factory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -624,14 +573,10 @@ See <<sftp-max-fetch>> for more information.
|
||||
The adapter puts the remote directory and the file name in headers (`FileHeaders.REMOTE_DIRECTORY` and `FileHeaders.REMOTE_FILE`, respectively).
|
||||
Starting with version 5.0, the `FileHeaders.REMOTE_FILE_INFO` header provides additional remote file information (in JSON).
|
||||
If you set the `fileInfoJson` property on the `SftpStreamingMessageSource` to `false`, the header contains an `SftpFileInfo` object.
|
||||
You can access the `LsEntry` object provided by the underlying Jsch library by using the `SftpFileInfo.getFileInfo()` method.
|
||||
You can access the `SftpClient.DirEntry` object provided by the underlying `SftpClient` by using the `SftpFileInfo.getFileInfo()` method.
|
||||
The `fileInfoJson` property is not available when you use XML configuration, but you can set it by injecting the `SftpStreamingMessageSource` into one of your configuration classes.
|
||||
See also <<sftp-remote-file-info>>.
|
||||
|
||||
Starting with version 5.1, the generic type of the `comparator` is `LsEntry`.
|
||||
Previously, it was `AbstractFileInfo<LsEntry>`.
|
||||
This is because the sort is now performed earlier in the processing, before filtering and applying `maxFetch`.
|
||||
|
||||
[[sftp-streaming-java-config]]
|
||||
==== Configuring with Java Configuration
|
||||
|
||||
@@ -689,7 +634,7 @@ public class SftpJavaApplication {
|
||||
----
|
||||
====
|
||||
|
||||
Notice that, in this example, the message handler downstream of the transformer has an advice that removes the remote file after processing.
|
||||
Notice that, in this example, the message handler downstream of the transformer has an `advice` that removes the remote file after processing.
|
||||
|
||||
[[sftp-rotating-server-advice]]
|
||||
=== Inbound Channel Adapters: Polling Multiple Servers and Directories
|
||||
@@ -800,7 +745,7 @@ Another use for `max-fetch-size` is when you want to stop fetching remote files
|
||||
Setting the `maxFetchSize` property on the `MessageSource` (programmatically, via JMX, or via a <<./control-bus.adoc#control-bus, control bus>>) effectively stops the adapter from fetching more files but lets the poller continue to emit messages for files that have previously been fetched.
|
||||
If the poller is active when the property is changed, the change takes effect on the next poll.
|
||||
|
||||
Starting with version 5.1, the synchronizer can be provided with a `Comparator<LsEntry>`.
|
||||
Starting with version 5.1, the synchronizer can be provided with a `Comparator<?>`.
|
||||
This is useful when restricting the number of files fetched with `maxFetchSize`.
|
||||
|
||||
[[sftp-outbound]]
|
||||
@@ -896,7 +841,7 @@ public class SftpJavaApplication {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SessionFactory<LsEntry> sftpSessionFactory() {
|
||||
public SessionFactory<SftpClient.DirEntry> sftpSessionFactory() {
|
||||
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true);
|
||||
factory.setHost("localhost");
|
||||
factory.setPort(port);
|
||||
@@ -904,7 +849,7 @@ public class SftpJavaApplication {
|
||||
factory.setPassword("foo");
|
||||
factory.setAllowUnknownKeys(true);
|
||||
factory.setTestSession(true);
|
||||
return new CachingSessionFactory<LsEntry>(factory);
|
||||
return new CachingSessionFactory<SftpClient.DirEntry>(factory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -1090,7 +1035,7 @@ The message payload resulting from an `mget` operation is a `List<File>` object
|
||||
IMPORTANT: Starting with version 5.0, if the `FileExistsMode` is `IGNORE`, the payload of the output message no longer contain files that were not fetched due to the file already existing.
|
||||
Previously, the array contained all files, including those that already existed.
|
||||
|
||||
The expression you use determine the remote path should produce a result that ends with `*` for example `myfiles/*` fetches the complete tree under `myfiles`.
|
||||
The expression you use determine the remote path should produce a result that ends with `\*` for example `myfiles/*` fetches the complete tree under `myfiles`.
|
||||
|
||||
Starting with version 5.0, you can use a recursive `MGET`, combined with the `FileExistsMode.REPLACE_IF_MODIFIED` mode, to periodically synchronize an entire remote directory tree locally.
|
||||
This mode sets the local file's last modified timestamp to the remote file's timestamp, regardless of the `-P` (preserve timestamp) option.
|
||||
@@ -1223,7 +1168,7 @@ It is particularly useful for mget (for example: `local-directory-expression="'/
|
||||
This attribute is mutually exclusive with the `local-directory` attribute.
|
||||
|
||||
For all commands, the 'expression' property of the gateway holds the path on which the command acts.
|
||||
For the `mget` command, the expression might evaluate to `*`, meaning to retrieve all files, `somedirectory/*`, and other values that end with `*`.
|
||||
For the `mget` command, the expression might evaluate to `\*`, meaning to retrieve all files, `somedirectory/*`, and other values that end with `*`.
|
||||
|
||||
The following example shows a gateway configured for an `ls` command:
|
||||
|
||||
@@ -1293,14 +1238,14 @@ public class SftpJavaApplication {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SessionFactory<LsEntry> sftpSessionFactory() {
|
||||
public SessionFactory<SftpClient.DirEntry> sftpSessionFactory() {
|
||||
DefaultSftpSessionFactory sf = new DefaultSftpSessionFactory();
|
||||
sf.setHost("localhost");
|
||||
sf.setPort(port);
|
||||
sf.setUsername("foo");
|
||||
sf.setPassword("foo");
|
||||
factory.setTestSession(true);
|
||||
return new CachingSessionFactory<LsEntry>(sf);
|
||||
return new CachingSessionFactory<>(sf);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -1356,26 +1301,10 @@ root/
|
||||
If the exception occurs on `file3.txt`, the `PartialSuccessException` thrown by the gateway has `derivedInput` of `file1.txt`, `subdir`, and `zoo.txt` and `partialResults` of `file1.txt`.
|
||||
Its `cause` is another `PartialSuccessException` with `derivedInput` of `file2.txt` and `file3.txt` and `partialResults` of `file2.txt`.
|
||||
|
||||
[[sftp-jsch-logging]]
|
||||
=== SFTP/JSCH Logging
|
||||
|
||||
Since we use JSch libraries to provide SFTP support, you may at times require more information from the JSch API itself, especially if something is not working properly (such as authentication exceptions).
|
||||
Unfortunately JSch does not use `commons-logging` but instead relies on custom implementations of their `com.jcraft.jsch.Logger` interface.
|
||||
As of Spring Integration 2.0.1, we have implemented this interface.
|
||||
So now, to enable JSch logging, you can configure your logger the way you usually do.
|
||||
For example, the following example is valid configuration of a logger that uses Log4J:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
log4j.category.com.jcraft.jsch=DEBUG
|
||||
----
|
||||
====
|
||||
|
||||
[[sftp-session-callback]]
|
||||
=== MessageSessionCallback
|
||||
|
||||
Starting with Spring Integration version 4.2, you can use a `MessageSessionCallback<F, T>` implementation with the `<int-sftp:outbound-gateway/>` (`SftpOutboundGateway`) to perform any operation on the `Session<LsEntry>` with the `requestMessage` context.
|
||||
Starting with Spring Integration version 4.2, you can use a `MessageSessionCallback<F, T>` implementation with the `<int-sftp:outbound-gateway/>` (`SftpOutboundGateway`) to perform any operation on the `Session<SftpClient.DirEntry>` with the `requestMessage` context.
|
||||
You can use it for any non-standard or low-level SFTP operation (or several), such as allowing access from an integration flow definition, or functional interface (lambda) implementation injection.
|
||||
The following example uses a lambda:
|
||||
|
||||
@@ -1384,7 +1313,7 @@ The following example uses a lambda:
|
||||
----
|
||||
@Bean
|
||||
@ServiceActivator(inputChannel = "sftpChannel")
|
||||
public MessageHandler sftpOutboundGateway(SessionFactory<ChannelSftp.LsEntry> sessionFactory) {
|
||||
public MessageHandler sftpOutboundGateway(SessionFactory<SftpClient.DirEntry> sessionFactory) {
|
||||
return new SftpOutboundGateway(sessionFactory,
|
||||
(session, requestMessage) -> session.list(requestMessage.getPayload()));
|
||||
}
|
||||
|
||||
@@ -50,6 +50,12 @@ See <<./jdbc.adoc#postgresql-push,PostgreSQL: Receiving Push Notifications>> for
|
||||
The AMQP module has been enhanced to provide support for inbound and outbound channel adapters using RabbitMQ Stream Queues.
|
||||
See <<./amqp.adoc#rmq-streams,RabbitMQ Stream Queue Support>> for more information.
|
||||
|
||||
[[x6.0-sftp]]
|
||||
==== Apache MINA SFTP
|
||||
|
||||
The SFTP modules has been fully reworked from outdated JCraft JSch library to more robust and modern `org.apache.sshd:sshd-sftp` module of the Apache MINA project.
|
||||
|
||||
See <<./sftp.adoc#sftp,SFTP Adapters>> for more information.
|
||||
[[x6.0-general]]
|
||||
=== General Changes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user