INT-1668 added logic to FTP/SFTP to ensure that the files are being uploaded with .writing extension and then renamed. Added rename(..) method to Session strategy. Ensured that file to be uploaded is not copied locally. Tested upload/download with both SFTP and FTP of a very large files. Added more mock tests
This commit is contained in:
@@ -56,7 +56,7 @@ import java.nio.charset.Charset;
|
||||
*/
|
||||
public class FileWritingMessageHandler extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
private static final String TEMPORARY_FILE_SUFFIX =".writing";
|
||||
public static final String TEMPORARY_FILE_SUFFIX =".writing";
|
||||
|
||||
|
||||
private final Log logger = LogFactory.getLog(this.getClass());
|
||||
|
||||
@@ -19,9 +19,7 @@ package org.springframework.integration.file.remote.handler;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.springframework.expression.Expression;
|
||||
@@ -29,6 +27,7 @@ import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageDeliveryException;
|
||||
import org.springframework.integration.file.DefaultFileNameGenerator;
|
||||
import org.springframework.integration.file.FileNameGenerator;
|
||||
import org.springframework.integration.file.FileWritingMessageHandler;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.handler.AbstractMessageHandler;
|
||||
@@ -48,9 +47,6 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class FileTransferringMessageHandler extends AbstractMessageHandler {
|
||||
|
||||
private static final String TEMPORARY_FILE_SUFFIX = ".writing";
|
||||
|
||||
|
||||
private final SessionFactory sessionFactory;
|
||||
|
||||
private volatile ExpressionEvaluatingMessageProcessor<String> directoryExpressionProcessor;
|
||||
@@ -60,6 +56,8 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
|
||||
private volatile File temporaryDirectory = new File(System.getProperty("java.io.tmpdir"));
|
||||
|
||||
private volatile String charset = Charset.defaultCharset().name();
|
||||
|
||||
private volatile boolean deleteOnExit;
|
||||
|
||||
|
||||
public FileTransferringMessageHandler(SessionFactory sessionFactory) {
|
||||
@@ -94,10 +92,10 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
|
||||
File file = this.redeemForStorableFile(message);
|
||||
if (file != null && file.exists()) {
|
||||
Session session = this.sessionFactory.getSession();
|
||||
boolean sentSuccesfully = false;
|
||||
try {
|
||||
String targetDirectory = this.directoryExpressionProcessor.processMessage(message);
|
||||
sentSuccesfully = this.sendFileToRemoteDirectory(file, targetDirectory, session);
|
||||
String fileName = this.fileNameGenerator.generateFileName(message);
|
||||
this.sendFileToRemoteDirectory(file, targetDirectory, fileName, session);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
throw new MessageDeliveryException(message,
|
||||
@@ -112,59 +110,51 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
|
||||
"Error handling message for file [" + file + "]", e);
|
||||
}
|
||||
finally {
|
||||
if (file.exists()) {
|
||||
try {
|
||||
file.delete();
|
||||
}
|
||||
catch (Throwable th) {
|
||||
// ignore
|
||||
if (deleteOnExit){
|
||||
if (file.exists()) {
|
||||
try {
|
||||
file.delete();
|
||||
}
|
||||
catch (Throwable th) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
if (session != null) {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
if (!sentSuccesfully) {
|
||||
throw new MessageDeliveryException(message, "Failed to transfer file '" + file + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private File handleFileMessage(File sourceFile, File tempFile, File resultFile) throws IOException {
|
||||
FileCopyUtils.copy(sourceFile, tempFile);
|
||||
tempFile.renameTo(resultFile);
|
||||
return resultFile;
|
||||
}
|
||||
|
||||
private File handleByteArrayMessage(byte[] bytes, File tempFile, File resultFile) throws IOException {
|
||||
FileCopyUtils.copy(bytes, tempFile);
|
||||
tempFile.renameTo(resultFile);
|
||||
return resultFile;
|
||||
}
|
||||
|
||||
private File handleStringMessage(String content, File tempFile, File resultFile, String charset) throws IOException {
|
||||
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(tempFile), charset);
|
||||
FileCopyUtils.copy(content, writer);
|
||||
tempFile.renameTo(resultFile);
|
||||
return resultFile;
|
||||
}
|
||||
|
||||
private File redeemForStorableFile(Message<?> message) throws MessageDeliveryException {
|
||||
try {
|
||||
Object payload = message.getPayload();
|
||||
String generateFileName = this.fileNameGenerator.generateFileName(message);
|
||||
File tempFile = new File(this.temporaryDirectory, generateFileName + TEMPORARY_FILE_SUFFIX);
|
||||
File resultFile = new File(this.temporaryDirectory, generateFileName);
|
||||
|
||||
File sendableFile = null;
|
||||
if (payload instanceof String) {
|
||||
sendableFile = this.handleStringMessage((String) payload, tempFile, resultFile, this.charset);
|
||||
|
||||
if (payload instanceof File){
|
||||
sendableFile = (File) payload;
|
||||
deleteOnExit = false;
|
||||
}
|
||||
else if (payload instanceof File) {
|
||||
sendableFile = this.handleFileMessage((File) payload, tempFile, resultFile);
|
||||
else if (payload instanceof byte[] || payload instanceof String) {
|
||||
String tempFileName = this.fileNameGenerator.generateFileName(message) + ".tmp";
|
||||
sendableFile = new File(this.temporaryDirectory, tempFileName); // will only create temp file for String/byte[]
|
||||
deleteOnExit = true;
|
||||
byte[] bytes = null;
|
||||
if (payload instanceof String){
|
||||
bytes = ((String)payload).getBytes(charset);
|
||||
}
|
||||
else {
|
||||
bytes = (byte[]) payload;
|
||||
}
|
||||
FileCopyUtils.copy(bytes, sendableFile);
|
||||
}
|
||||
else if (payload instanceof byte[]) {
|
||||
sendableFile = this.handleByteArrayMessage((byte[]) payload, tempFile, resultFile);
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported payload type. The only supported payloads are " +
|
||||
"java.io.File, java.lang.String and byte[]");
|
||||
}
|
||||
|
||||
return sendableFile;
|
||||
}
|
||||
catch (Exception e) {
|
||||
@@ -172,15 +162,19 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean sendFileToRemoteDirectory(File file, String remoteDirectory, Session session) throws FileNotFoundException, IOException {
|
||||
private void sendFileToRemoteDirectory(File file, String remoteDirectory, String pathTo, Session session)
|
||||
throws FileNotFoundException, IOException {
|
||||
|
||||
FileInputStream fileInputStream = new FileInputStream(file);
|
||||
if (!StringUtils.endsWithIgnoreCase(remoteDirectory, File.separator)) {
|
||||
remoteDirectory += File.separatorChar;
|
||||
remoteDirectory += File.separatorChar;
|
||||
}
|
||||
String remoteFilePath = remoteDirectory + file.getName();
|
||||
String remoteFilePath = remoteDirectory + file.getName() + FileWritingMessageHandler.TEMPORARY_FILE_SUFFIX;
|
||||
// write remote file first with .writing extension
|
||||
session.write(fileInputStream, remoteFilePath);
|
||||
fileInputStream.close();
|
||||
return true;
|
||||
// then rename it to its final name
|
||||
session.rename(remoteFilePath, pathTo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -136,6 +136,10 @@ public class CachingSessionFactory implements SessionFactory, DisposableBean {
|
||||
public boolean isOpen() {
|
||||
return this.targetSession.isOpen();
|
||||
}
|
||||
|
||||
public void rename(String pathFrom, String pathTo) throws IOException {
|
||||
this.targetSession.rename(pathFrom, pathTo);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,8 +38,11 @@ public interface Session {
|
||||
void read(String source, OutputStream outputStream) throws IOException;
|
||||
|
||||
void write(InputStream inputStream, String destination) throws IOException;
|
||||
|
||||
void rename(String pathFrom, String pathTo) throws IOException;
|
||||
|
||||
void close();
|
||||
|
||||
boolean isOpen();
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.commons.net.ftp.FTPClient;
|
||||
import org.apache.commons.net.ftp.FTPFile;
|
||||
|
||||
import org.springframework.integration.file.FileWritingMessageHandler;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -78,7 +79,8 @@ class FtpSession implements Session {
|
||||
Assert.hasText(path, "path must not be null");
|
||||
boolean completed = client.storeFile(path, inputStream);
|
||||
if (!completed){
|
||||
throw new IOException("Failed to copy '" + path + "'. Server replied with: " + client.getReplyString());
|
||||
throw new IOException("Failed to write to '" + (path+FileWritingMessageHandler.TEMPORARY_FILE_SUFFIX)
|
||||
+ "'. Server replied with: " + client.getReplyString());
|
||||
}
|
||||
logger.info("File have been successfully transfered to: " + path);
|
||||
}
|
||||
@@ -102,4 +104,13 @@ class FtpSession implements Session {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void rename(String pathFrom, String pathTo) throws IOException{
|
||||
boolean completed = client.rename(pathFrom, pathTo);
|
||||
if (!completed){
|
||||
throw new IOException("Failed to rename '" + pathFrom +
|
||||
"' to " + pathTo + "'. Server replied with: " + client.getReplyString());
|
||||
}
|
||||
logger.info("File have been successfully renamed from: " + pathFrom + " to " + pathTo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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-ftp="http://www.springframework.org/schema/integration/ftp"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.0.xsd
|
||||
http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp-2.0.xsd">
|
||||
|
||||
<int:channel id="ftpOutbound"/>
|
||||
|
||||
<bean id="ftpSessionFactory" class="org.springframework.integration.ftp.session.DefaultFtpsSessionFactory">
|
||||
<property name="host" value="localhost"/>
|
||||
<property name="port" value="22"/>
|
||||
<property name="username" value="oleg"/>
|
||||
<property name="password" value="password"/>
|
||||
<property name="clientMode" value="0"/>
|
||||
<property name="fileType" value="2"/>
|
||||
</bean>
|
||||
|
||||
<int-ftp:outbound-channel-adapter id="ftpOutboundAdapter"
|
||||
session-factory="ftpSessionFactory"
|
||||
remote-directory="foo/bar"
|
||||
channel="ftpOutbound"
|
||||
remote-filename-generator="fileNameGenerator"/>
|
||||
|
||||
<bean id="fileNameGenerator" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.springframework.integration.file.FileNameGenerator"/>
|
||||
</bean>
|
||||
</beans>
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2010 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
|
||||
*
|
||||
* http://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.ftp;
|
||||
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.endpoint.EventDrivenConsumer;
|
||||
import org.springframework.integration.file.FileNameGenerator;
|
||||
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
public class FtpParserOutboundTests {
|
||||
|
||||
@Test
|
||||
public void testFtpOutboundWithFileGenerator() throws Exception{
|
||||
ClassPathXmlApplicationContext context =
|
||||
new ClassPathXmlApplicationContext("FtpParserOutboundTests-context.xml", this.getClass());
|
||||
FileNameGenerator fileNameGenerator = context.getBean("fileNameGenerator", FileNameGenerator.class);
|
||||
assertNotNull(fileNameGenerator);
|
||||
when(fileNameGenerator.generateFileName(Mockito.any(Message.class))).thenReturn("oleg-ftp-test.txt");
|
||||
EventDrivenConsumer fileOutboundEndpoint = context.getBean("ftpOutboundAdapter", EventDrivenConsumer.class);
|
||||
FileTransferringMessageHandler handler = (FileTransferringMessageHandler) TestUtils.getPropertyValue(fileOutboundEndpoint, "handler");
|
||||
Message<String> message = new GenericMessage<String>("ftp file generator test");
|
||||
try {
|
||||
handler.handleMessage(message);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
verify(fileNameGenerator, times(1)).generateFileName(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,9 +8,10 @@
|
||||
http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp-2.0.xsd">
|
||||
|
||||
<bean id="ftpClientFactory" class="org.springframework.integration.ftp.session.DefaultFtpSessionFactory">
|
||||
<property name="host" value="localhost"/>
|
||||
<property name="host" value="192.168.1.155"/>
|
||||
<property name="username" value="oleg"/>
|
||||
<property name="password" value="xxxx"/>
|
||||
<property name="password" value="password"/>
|
||||
<property name="bufferSize" value="1000000"/>
|
||||
</bean>
|
||||
|
||||
<int-ftp:inbound-channel-adapter id="ftpInbound"
|
||||
@@ -18,10 +19,10 @@
|
||||
session-factory="ftpClientFactory"
|
||||
auto-create-local-directory="true"
|
||||
delete-remote-files="false"
|
||||
filename-regex=".*\.test$"
|
||||
remote-directory="/Users/ozhurakousky/workspace-sts-2.3.3.M2/si/spring-integration/spring-integration-ftp/remote-test-dir"
|
||||
filename-regex=".*\.gz$"
|
||||
remote-directory="/home/ozhurakousky/Downloads"
|
||||
local-directory="file:local-test-dir">
|
||||
<int:poller fixed-rate="1000"/>
|
||||
<int:poller fixed-rate="5000"/>
|
||||
</int-ftp:inbound-channel-adapter>
|
||||
|
||||
<int:channel id="ftpChannel">
|
||||
|
||||
@@ -8,15 +8,16 @@
|
||||
http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp-2.0.xsd">
|
||||
|
||||
<bean id="ftpSessionFactory" class="org.springframework.integration.ftp.session.DefaultFtpSessionFactory">
|
||||
<property name="host" value="localhost"/>
|
||||
<property name="host" value="192.168.1.155"/>
|
||||
<property name="username" value="oleg"/>
|
||||
<property name="password" value="xxxx"/>
|
||||
<property name="password" value="password"/>
|
||||
<property name="bufferSize" value="1000000"/>
|
||||
</bean>
|
||||
|
||||
<int:channel id="ftpChannel"/>
|
||||
|
||||
<int-ftp:outbound-channel-adapter
|
||||
session-factory="ftpSessionFactory"
|
||||
remote-directory="/Users/ozhurakousky/workspace-sts-2.3.3.M2/si/spring-integration/spring-integration-ftp/remote-target-dir"
|
||||
remote-directory="/home/ozhurakousky"
|
||||
channel="ftpChannel"/>
|
||||
</beans>
|
||||
|
||||
@@ -133,6 +133,14 @@ public class FtpSendingMessageHandlerTest {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
when(ftpClient.rename(Mockito.anyString(), Mockito.anyString())).thenAnswer(new Answer<Boolean>() {
|
||||
public Boolean answer(InvocationOnMock invocation)
|
||||
throws Throwable {
|
||||
File file = new File((String) invocation.getArguments()[0]);
|
||||
file.renameTo(new File(file.getParent(), (String) invocation.getArguments()[1]));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return ftpClient;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create mock client", e);
|
||||
|
||||
@@ -92,7 +92,7 @@ class SftpSession implements Session {
|
||||
FileCopyUtils.copy(is, os);
|
||||
}
|
||||
catch (SftpException e) {
|
||||
throw new IOException("failed to copy file", e);
|
||||
throw new IOException("failed to read file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class SftpSession implements Session {
|
||||
this.channel.put(inputStream, destination);
|
||||
}
|
||||
catch (SftpException e) {
|
||||
throw new IOException("failed to copy file", e);
|
||||
throw new IOException("failed to write file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,4 +131,12 @@ class SftpSession implements Session {
|
||||
return this.jschSession.isConnected();
|
||||
}
|
||||
|
||||
public void rename(String pathFrom, String pathTo) throws IOException {
|
||||
try {
|
||||
this.channel.rename(pathFrom, pathTo);
|
||||
} catch (SftpException e) {
|
||||
throw new IOException("failed to rename from " + pathFrom + " to " + pathTo, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
|
||||
<bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
|
||||
<property name="host" value="localhost"/>
|
||||
<property name="host" value="192.168.1.155"/>
|
||||
<property name="privateKey" value="classpath:org/springframework/integration/sftp/config/sftp_rsa"/>
|
||||
<property name="privateKeyPassphrase" value="springintegration"/>
|
||||
<property name="port" value="22"/>
|
||||
@@ -21,12 +21,12 @@
|
||||
<int-sftp:inbound-channel-adapter id="sftpInbondAdapter"
|
||||
channel="receiveChannel"
|
||||
session-factory="sftpSessionFactory"
|
||||
local-directory="/Users/ozhurakousky/workspace-sts-2.3.3.M2/si/spring-integration/spring-integration-sftp/local-test-dir"
|
||||
remote-directory="/Users/ozhurakousky/workspace-sts-2.3.3.M2/si/spring-integration/spring-integration-sftp/remote-test-dir"
|
||||
local-directory="file:local-test-dir"
|
||||
remote-directory="/home/ozhurakousky/Downloads"
|
||||
auto-startup="true"
|
||||
delete-remote-files="false"
|
||||
filename-regex=".*\.test$">
|
||||
<int:poller fixed-rate="1000" max-messages-per-poll="10" task-executor="executor"/>
|
||||
filename-regex=".*\.gz$">
|
||||
<int:poller fixed-rate="3000" max-messages-per-poll="1" />
|
||||
</int-sftp:inbound-channel-adapter>
|
||||
|
||||
<int:channel id="receiveChannel">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
|
||||
<bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
|
||||
<property name="host" value="localhost"/>
|
||||
<property name="host" value="192.168.1.155"/>
|
||||
<property name="privateKey" value="classpath:org/springframework/integration/sftp/config/sftp_rsa"/>
|
||||
<property name="privateKeyPassphrase" value="springintegration"/>
|
||||
<property name="port" value="22"/>
|
||||
@@ -23,6 +23,6 @@
|
||||
channel="ftpChannel"
|
||||
charset="UTF-8"
|
||||
remote-filename-generator-expression="payload.getName() + '-foo'"
|
||||
remote-directory="/Users/ozhurakousky/workspace-sts-2.3.3.M2/si/spring-integration/spring-integration-sftp/remote-target-dir"/>
|
||||
remote-directory="/home/ozhurakousky"/>
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -16,67 +16,127 @@
|
||||
|
||||
package org.springframework.integration.sftp.outbound;
|
||||
|
||||
import static org.mockito.Mockito.atLeast;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.file.DefaultFileNameGenerator;
|
||||
import org.springframework.integration.file.FileWritingMessageHandler;
|
||||
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.integration.sftp.session.DefaultSftpSessionFactory;
|
||||
import org.springframework.integration.sftp.session.SftpTestSessionFactory;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
// there are few validations in this tests, but it is mainly to increase code coverage during CI
|
||||
public class SftpSendingMessageHandlerTests {
|
||||
|
||||
private static com.jcraft.jsch.Session jschSession = mock(com.jcraft.jsch.Session.class);
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@Test
|
||||
public void testHandleFileNameMessage() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
Session session = mock(Session.class);
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
public void testHandleFileMessage() throws Exception {
|
||||
File file = new File("remote-target-dir", "template.mf.test");
|
||||
if (file.exists()){
|
||||
file.delete();
|
||||
}
|
||||
SessionFactory sessionFactory = new TestSftpSessionFactory();
|
||||
FileTransferringMessageHandler handler = new FileTransferringMessageHandler(sessionFactory);
|
||||
handler.setRemoteDirectoryExpression(new SpelExpressionParser().parseExpression("'foo.txt'"));
|
||||
handler.handleMessage(new GenericMessage("hello"));
|
||||
verify(sessionFactory, times(1)).getSession();
|
||||
DefaultFileNameGenerator fGenerator = new DefaultFileNameGenerator();
|
||||
fGenerator.setExpression("payload + '.test'");
|
||||
handler.setFileNameGenerator(fGenerator);
|
||||
handler.setRemoteDirectoryExpression(new LiteralExpression("remote-target-dir"));
|
||||
|
||||
handler.handleMessage(new GenericMessage<File>(new File("template.mf")));
|
||||
assertTrue(new File("remote-target-dir", "template.mf.test").exists());
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@Test
|
||||
public void testHandleFileAsByte() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
Session session = mock(Session.class);
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
public void testHandleStringMessage() throws Exception {
|
||||
File file = new File("remote-target-dir", "foo.txt");
|
||||
if (file.exists()){
|
||||
file.delete();
|
||||
}
|
||||
SessionFactory sessionFactory = new TestSftpSessionFactory();
|
||||
FileTransferringMessageHandler handler = new FileTransferringMessageHandler(sessionFactory);
|
||||
handler.setRemoteDirectoryExpression(new SpelExpressionParser().parseExpression("'foo.txt'"));
|
||||
DefaultFileNameGenerator fGenerator = new DefaultFileNameGenerator();
|
||||
fGenerator.setExpression("'foo.txt'");
|
||||
handler.setFileNameGenerator(fGenerator);
|
||||
handler.setRemoteDirectoryExpression(new LiteralExpression("remote-target-dir"));
|
||||
|
||||
handler.handleMessage(new GenericMessage("hello".getBytes()));
|
||||
verify(sessionFactory, times(1)).getSession();
|
||||
handler.handleMessage(new GenericMessage("hello"));
|
||||
assertTrue(new File("remote-target-dir", "foo.txt").exists());
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@Test
|
||||
public void testHandleFileMessage() throws Exception {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
Session session = mock(Session.class);
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
public void testHandleBytesMessage() throws Exception {
|
||||
File file = new File("remote-target-dir", "foo.txt");
|
||||
if (file.exists()){
|
||||
file.delete();
|
||||
}
|
||||
SessionFactory sessionFactory = new TestSftpSessionFactory();
|
||||
FileTransferringMessageHandler handler = new FileTransferringMessageHandler(sessionFactory);
|
||||
handler.setRemoteDirectoryExpression(new SpelExpressionParser().parseExpression("'foo.txt'"));
|
||||
DefaultFileNameGenerator fGenerator = new DefaultFileNameGenerator();
|
||||
fGenerator.setExpression("'foo.txt'");
|
||||
handler.setFileNameGenerator(fGenerator);
|
||||
handler.setRemoteDirectoryExpression(new LiteralExpression("remote-target-dir"));
|
||||
|
||||
handler.handleMessage(new GenericMessage("hello".getBytes()));
|
||||
assertTrue(new File("remote-target-dir", "foo.txt").exists());
|
||||
}
|
||||
|
||||
public static class TestSftpSessionFactory extends DefaultSftpSessionFactory {
|
||||
|
||||
File file = File.createTempFile("foo", ".txt");
|
||||
handler.handleMessage(new GenericMessage(file));
|
||||
verify(sessionFactory, atLeast(1)).getSession();
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Session getSession() {
|
||||
try {
|
||||
ChannelSftp channel = mock(ChannelSftp.class);
|
||||
|
||||
doAnswer(new Answer() {
|
||||
public Object answer(InvocationOnMock invocation)
|
||||
throws Throwable {
|
||||
File file = new File((String)invocation.getArguments()[1]);
|
||||
assertTrue(file.getName().endsWith(FileWritingMessageHandler.TEMPORARY_FILE_SUFFIX));
|
||||
FileCopyUtils.copy((InputStream)invocation.getArguments()[0], new FileOutputStream(file));
|
||||
return null;
|
||||
}
|
||||
|
||||
}).when(channel).put(Mockito.any(InputStream.class), Mockito.anyString());
|
||||
|
||||
doAnswer(new Answer() {
|
||||
public Object answer(InvocationOnMock invocation)
|
||||
throws Throwable {
|
||||
File file = new File((String) invocation.getArguments()[0]);
|
||||
assertTrue(file.getName().endsWith(FileWritingMessageHandler.TEMPORARY_FILE_SUFFIX));
|
||||
file.renameTo(new File(file.getParent(), (String) invocation.getArguments()[1]));
|
||||
return null;
|
||||
}
|
||||
|
||||
}).when(channel).rename(Mockito.anyString(), Mockito.anyString());
|
||||
when(jschSession.openChannel("sftp")).thenReturn(channel);
|
||||
return SftpTestSessionFactory.createSftpSession(jschSession);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create mock sftp session", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n
|
||||
|
||||
log4j.category.com.jcraft.jsch=DEBUG
|
||||
log4j.category.org.springframework.integration=WARN
|
||||
log4j.category.org.springframework.integration=DEBUG
|
||||
log4j.category.org.springframework.integration.sftp=DEBUG
|
||||
|
||||
Reference in New Issue
Block a user