GH-3247: Fix SftpSession.exists for error code (#3248)

* GH-3247: Fix `SftpSession.exists` for error code

Fixes https://github.com/spring-projects/spring-integration/issues/3247

When there is no path on the SFTP server, a `ChannelSftp.SSH_FX_NO_SUCH_FILE`
error is returned in the thrown `SftpException`.

* Fix `SftpSession.exists()` to check for the `SSH_FX_NO_SUCH_FILE` to
return `false` and re-throw an exception otherwise
* Add mock test for `SftpSession.exists()`
* Add `org.mockito.AdditionalMatchers` to `checkstyle.xml` exclusions

**Cherry-pick to 5.2.x & 5.1.x**

* * Add exists tests against Mina embedded server
This commit is contained in:
Artem Bilan
2020-04-14 15:26:14 -04:00
committed by Gary Russell
parent 2d00bfc301
commit 78becd5391
3 changed files with 89 additions and 18 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@@ -19,6 +19,7 @@ package org.springframework.integration.sftp.session;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
@@ -108,16 +109,15 @@ public class SftpSession implements Session<LsEntry> {
@Override
public String[] listNames(String path) throws IOException {
LsEntry[] entries = this.list(path);
List<String> names = new ArrayList<String>();
for (int i = 0; i < entries.length; i++) {
String fileName = entries[i].getFilename();
SftpATTRS attrs = entries[i].getAttrs();
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);
}
}
String[] fileNames = new String[names.size()];
return names.toArray(fileNames);
return names.toArray(new String[0]);
}
@@ -251,10 +251,15 @@ public class SftpSession implements Session<LsEntry> {
this.channel.lstat(path);
return true;
}
catch (SftpException e) {
// ignore
catch (SftpException ex) {
if (ex.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
return false;
}
else {
throw new UncheckedIOException("Cannot check 'lstat' for path " + path,
new IOException(ex));
}
}
return false;
}
void connect() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* 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.
@@ -17,11 +17,16 @@
package org.springframework.integration.sftp.outbound;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
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.doReturn;
import static org.mockito.Mockito.mock;
@@ -32,7 +37,9 @@ 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.util.ArrayList;
import java.util.Arrays;
@@ -40,7 +47,7 @@ import java.util.List;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.DirectFieldAccessor;
@@ -69,6 +76,7 @@ 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
@@ -78,7 +86,7 @@ import com.jcraft.jsch.SftpATTRS;
*/
public class SftpOutboundTests {
private static com.jcraft.jsch.Session jschSession = mock(com.jcraft.jsch.Session.class);
private static final com.jcraft.jsch.Session jschSession = mock(com.jcraft.jsch.Session.class);
@Test
public void testHandleFileMessage() throws Exception {
@@ -86,7 +94,7 @@ public class SftpOutboundTests {
assertTrue("target directory does not exist: " + targetDir.getName(), targetDir.exists());
SessionFactory<LsEntry> sessionFactory = new TestSftpSessionFactory();
FileTransferringMessageHandler<LsEntry> handler = new FileTransferringMessageHandler<LsEntry>(sessionFactory);
FileTransferringMessageHandler<LsEntry> handler = new FileTransferringMessageHandler<>(sessionFactory);
handler.setRemoteDirectoryExpression(new LiteralExpression(targetDir.getName()));
DefaultFileNameGenerator fGenerator = new DefaultFileNameGenerator();
fGenerator.setBeanFactory(mock(BeanFactory.class));
@@ -135,7 +143,7 @@ public class SftpOutboundTests {
file.delete();
}
SessionFactory<LsEntry> sessionFactory = new TestSftpSessionFactory();
FileTransferringMessageHandler<LsEntry> handler = new FileTransferringMessageHandler<LsEntry>(sessionFactory);
FileTransferringMessageHandler<LsEntry> handler = new FileTransferringMessageHandler<>(sessionFactory);
DefaultFileNameGenerator fGenerator = new DefaultFileNameGenerator();
fGenerator.setBeanFactory(mock(BeanFactory.class));
fGenerator.setExpression("'foo.txt'");
@@ -173,7 +181,7 @@ public class SftpOutboundTests {
}
@Test //INT-2275
public void testFtpOutboundGatewayInsideChain() throws Exception {
public void testFtpOutboundGatewayInsideChain() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"SftpOutboundInsideChainTests-context.xml", getClass());
@@ -204,12 +212,12 @@ public class SftpOutboundTests {
@SuppressWarnings("unchecked")
SessionFactory<LsEntry> sessionFactory = mock(SessionFactory.class);
when(sessionFactory.getSession()).thenReturn(session);
FileTransferringMessageHandler<LsEntry> handler = new FileTransferringMessageHandler<LsEntry>(sessionFactory);
FileTransferringMessageHandler<LsEntry> handler = new FileTransferringMessageHandler<>(sessionFactory);
handler.setAutoCreateDirectory(true);
handler.setRemoteDirectoryExpression(new LiteralExpression("/foo/bar/baz"));
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
final List<String> madeDirs = new ArrayList<String>();
final List<String> madeDirs = new ArrayList<>();
doAnswer(invocation -> {
madeDirs.add(invocation.getArgument(0));
return null;
@@ -374,6 +382,38 @@ public class SftpOutboundTests {
verify(jschSession2).disconnect();
}
@Test
public void testExists() throws SftpException, IOException {
ChannelSftp channelSftp = mock(ChannelSftp.class);
willReturn(mock(SftpATTRS.class))
.given(channelSftp)
.lstat(eq("exist"));
willThrow(new SftpException(ChannelSftp.SSH_FX_NO_SUCH_FILE, "Path does not exist."))
.given(channelSftp)
.lstat(eq("notExist"));
willThrow(new SftpException(ChannelSftp.SSH_FX_CONNECTION_LOST, "Connection lost."))
.given(channelSftp)
.lstat(eq("foo"));
SftpSession sftpSession = new SftpSession(mock(com.jcraft.jsch.Session.class));
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(sftpSession);
fieldAccessor.setPropertyValue("channel", channelSftp);
assertTrue(sftpSession.exists("exist"));
assertFalse(sftpSession.exists("notExist"));
try {
sftpSession.exists("foo");
fail("Expected exception");
}
catch (UncheckedIOException e) {
}
}
private void noopConnect(ChannelSftp channel1) throws JSchException {
doAnswer(invocation -> null).when(channel1).connect();
}

View File

@@ -21,6 +21,7 @@ import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
@@ -35,6 +36,7 @@ import java.io.File;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
@@ -56,6 +58,7 @@ import org.springframework.integration.file.remote.MessageSessionCallback;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.sftp.SftpTestSupport;
import org.springframework.integration.sftp.session.DefaultSftpSessionFactory;
import org.springframework.integration.sftp.session.SftpRemoteFileTemplate;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
@@ -497,6 +500,29 @@ public class SftpServerOutboundTests extends SftpTestSupport {
assertEquals(6, files[0].getAttrs().getSize());
}
@Test
public void testSessionExists() throws IOException {
DefaultSftpSessionFactory sessionFactory = new DefaultSftpSessionFactory();
sessionFactory.setHost("localhost");
sessionFactory.setPort(port);
sessionFactory.setUser("foo");
sessionFactory.setPassword("foo");
sessionFactory.setAllowUnknownKeys(true);
Session<LsEntry> session = sessionFactory.getSession();
assertTrue(session.exists("sftpSource"));
assertFalse(session.exists("notExist"));
session.close();
try {
session.exists("any");
fail("expected exception");
}
catch (UncheckedIOException e) {
}
}
@SuppressWarnings("unused")
private static final class TestMessageSessionCallback
implements MessageSessionCallback<LsEntry, Object> {