diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java index d1a5d110e2..4ddb465355 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java @@ -68,6 +68,7 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { this.sessionFactory = sessionFactory; } + public void setAutoCreateDirectory(boolean autoCreateDirectory) { this.autoCreateDirectory = autoCreateDirectory; } @@ -78,11 +79,12 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { } public void setRemoteDirectoryExpression(Expression remoteDirectoryExpression) { + Assert.notNull(remoteDirectoryExpression, "remoteDirectoryExpression must not be null"); this.directoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor(remoteDirectoryExpression, String.class); } protected String getTemporaryFileSuffix() { - return temporaryFileSuffix; + return this.temporaryFileSuffix; } public void setTemporaryDirectory(File temporaryDirectory) { @@ -136,7 +138,7 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { try { file.delete(); } - catch (Throwable th) { + catch (Throwable t) { // ignore } } @@ -160,7 +162,7 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { sendableFile = new File(this.temporaryDirectory, tempFileName); // will only create temp file for String/byte[] byte[] bytes = null; if (payload instanceof String) { - bytes = ((String) payload).getBytes(charset); + bytes = ((String) payload).getBytes(this.charset); } else { bytes = (byte[]) payload; @@ -169,7 +171,7 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { } else { throw new IllegalArgumentException("Unsupported payload type. The only supported payloads are " + - "java.io.File, java.lang.String and byte[]"); + "java.io.File, java.lang.String, and byte[]"); } return sendableFile; } @@ -185,17 +187,15 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { if (!StringUtils.hasText(remoteDirectory)) { remoteDirectory = ""; } - else if (!remoteDirectory.endsWith(remoteFileSeparator)) { - remoteDirectory += remoteFileSeparator; + else if (!remoteDirectory.endsWith(this.remoteFileSeparator)) { + remoteDirectory += this.remoteFileSeparator; } String remoteFilePath = remoteDirectory + fileName; // write remote file first with .writing extension String tempFilePath = remoteFilePath + this.temporaryFileSuffix; - - if (this.autoCreateDirectory){ - this.ensureDirectoryExists(session, remoteDirectory, remoteDirectory); + if (this.autoCreateDirectory) { + session.mkdir(remoteDirectory); } - try { session.write(fileInputStream, tempFilePath); // then rename it to its final name @@ -209,35 +209,4 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { } } - private void ensureDirectoryExists(Session session, String remoteDirectory, String originalRemoteDirectory){ - try { - session.list(remoteDirectory); - } catch (IOException e) { - if (logger.isDebugEnabled()){ - logger.debug("Directory '" + remoteDirectory + "' does not exist. Will attempt to auto-create it"); - } - int nextSeparatorIndex = remoteDirectory.lastIndexOf(this.remoteFileSeparator); - if (nextSeparatorIndex <= 0){ - throw new MessagingException("Failed to auto-create directory '" + originalRemoteDirectory + "'"); - } - else { - remoteDirectory = remoteDirectory.substring(0, nextSeparatorIndex); - this.ensureDirectoryExists(session, remoteDirectory, originalRemoteDirectory); - } - } - String missingDirectoryPath = originalRemoteDirectory.substring(remoteDirectory.length()); - String[] directories = StringUtils.tokenizeToStringArray(missingDirectoryPath, this.remoteFileSeparator); - String directory = remoteDirectory + this.remoteFileSeparator; - for (String directorySegment : directories) { - directory += directorySegment+this.remoteFileSeparator; - if (logger.isDebugEnabled()){ - logger.debug("Creating '" + directory + "'"); - } - try { - session.mkdir(directory); - } catch (Exception e) { - throw new MessagingException("Failed to auto-create directory '" + directory + "'"); - } - } - } } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/Session.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/Session.java index 09408ddc01..be3413b4ab 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/Session.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/Session.java @@ -46,5 +46,4 @@ public interface Session { void close(); boolean isOpen(); - } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java index f38bec6393..2481f7973b 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java @@ -270,7 +270,8 @@ public class RemoteFileOutboundGatewayTests { } public boolean isOpen() { return open; - } }); + } + }); @SuppressWarnings("unchecked") Message out = (Message) gw.handleRequestMessage(new GenericMessage("f1")); File outFile = new File(this.tmpDir + "/f1"); @@ -323,7 +324,8 @@ public class RemoteFileOutboundGatewayTests { } public boolean isOpen() { return open; - } }); + } + }); @SuppressWarnings("unchecked") Message out = (Message) gw.handleRequestMessage(new GenericMessage("x/f1")); File outFile = new File(this.tmpDir + "/f1"); @@ -374,7 +376,8 @@ public class RemoteFileOutboundGatewayTests { } public boolean isOpen() { return open; - } }); + } + }); gw.handleRequestMessage(new GenericMessage("f1")); File out = new File(this.tmpDir + "/x/f1"); assertTrue(out.exists()); diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/session/FtpSession.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/session/FtpSession.java index 5ddd3f2c4a..4e66cacddf 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/session/FtpSession.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/session/FtpSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -51,7 +51,7 @@ class FtpSession implements Session { public boolean remove(String path) throws IOException { Assert.hasText(path, "path must not be null"); boolean completed = this.client.deleteFile(path); - if (!completed){ + if (!completed) { throw new IOException("Failed to delete '" + path + "'. Server replied with: " + client.getReplyString()); } return completed; @@ -63,25 +63,28 @@ class FtpSession implements Session { return this.client.listFiles(path); } - public void read(String path, OutputStream fos) throws IOException{ + public void read(String path, OutputStream fos) throws IOException { Assert.hasText(path, "path must not be null"); Assert.notNull(fos, "outputStream must not be null"); boolean completed = this.client.retrieveFile(path, fos); - if (!completed){ - throw new IOException("Failed to copy '" + path + "'. Server replied with: " + client.getReplyString()); + if (!completed) { + throw new IOException("Failed to copy '" + path + + "'. Server replied with: " + this.client.getReplyString()); } logger.info("File have been successfully transfered to: " + path); } - public void write(InputStream inputStream, String path) throws IOException{ + public void write(InputStream inputStream, String path) throws IOException { Assert.notNull(inputStream, "inputStream must not be null"); Assert.hasText(path, "path must not be null"); - boolean completed = client.storeFile(path, inputStream); - if (!completed){ + boolean completed = this.client.storeFile(path, inputStream); + if (!completed) { throw new IOException("Failed to write to '" + path - + "'. Server replied with: " + client.getReplyString()); + + "'. Server replied with: " + this.client.getReplyString()); + } + if (logger.isInfoEnabled()) { + logger.info("File has been successfully transfered to: " + path); } - logger.info("File have been successfully transfered to: " + path); } public void close() { @@ -97,21 +100,24 @@ class FtpSession implements Session { public boolean isOpen() { try { - client.noop(); - } catch (Exception e) { + this.client.noop(); + } + catch (Exception e) { return false; } return true; } public void rename(String pathFrom, String pathTo) throws IOException{ - client.deleteFile(pathTo); - boolean completed = client.rename(pathFrom, pathTo); - if (!completed){ + this.client.deleteFile(pathTo); + boolean completed = this.client.rename(pathFrom, pathTo); + if (!completed) { throw new IOException("Failed to rename '" + pathFrom + - "' to " + pathTo + "'. Server replied with: " + client.getReplyString()); + "' to " + pathTo + "'. Server replied with: " + this.client.getReplyString()); + } + if (logger.isInfoEnabled()) { + logger.info("File has been successfully renamed from: " + pathFrom + " to " + pathTo); } - logger.info("File have been successfully renamed from: " + pathFrom + " to " + pathTo); } public void mkdir(String directory) throws IOException { @@ -124,4 +130,5 @@ class FtpSession implements Session { } } } + } diff --git a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd index e6672bf35f..3ce1800dfe 100644 --- a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd +++ b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.1.xsd @@ -37,6 +37,13 @@ + + + + Specify whether to automatically create the remote target directory if it doesn't exist. + + + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterSample-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterSample-context.xml index c1bfab5da7..42d402a9dc 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterSample-context.xml +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterSample-context.xml @@ -8,8 +8,8 @@ http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp.xsd"> - - + + @@ -17,7 +17,8 @@ diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/session/SessionFactoryTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/session/SessionFactoryTests.java index a940fcacae..2de9f0ede5 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/session/SessionFactoryTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/session/SessionFactoryTests.java @@ -15,13 +15,13 @@ */ package org.springframework.integration.ftp.session; -import static junit.framework.Assert.fail; - import java.lang.reflect.Field; import org.apache.commons.net.ftp.FTPClient; import org.junit.Test; +import static junit.framework.Assert.fail; + /** * @author Oleg Zhurakousky * @@ -47,7 +47,6 @@ public class SessionFactoryTests { fail(); } } - } - + } } } diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpSession.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpSession.java index fce80a3af9..2808b06a6c 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpSession.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -25,9 +25,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.NestedIOException; +import org.springframework.integration.MessagingException; import org.springframework.integration.file.remote.session.Session; import org.springframework.util.Assert; import org.springframework.util.FileCopyUtils; +import org.springframework.util.StringUtils; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.ChannelSftp.LsEntry; @@ -44,12 +46,13 @@ import com.jcraft.jsch.SftpException; * @since 2.0 */ class SftpSession implements Session { + private final Log logger = LogFactory.getLog(this.getClass()); - - private volatile ChannelSftp channel; - + private final com.jcraft.jsch.Session jschSession; + private volatile ChannelSftp channel; + public SftpSession(com.jcraft.jsch.Session jschSession) { Assert.notNull(jschSession, "jschSession must not be null"); @@ -57,7 +60,7 @@ class SftpSession implements Session { } - public boolean remove(String path) throws IOException{ + public boolean remove(String path) throws IOException { Assert.state(this.channel != null, "session is not connected"); try { this.channel.rm(path); @@ -89,9 +92,8 @@ class SftpSession implements Session { return new LsEntry[0]; } - public void read(String source, OutputStream os) throws IOException{ + 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); @@ -101,7 +103,7 @@ class SftpSession implements Session { } } - public void write(InputStream inputStream, String destination) throws IOException{ + public void write(InputStream inputStream, String destination) throws IOException { Assert.state(this.channel != null, "session is not connected"); try { this.channel.put(inputStream, destination); @@ -117,21 +119,6 @@ class SftpSession implements Session { } } - 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(); - } - } - catch (JSchException e) { - throw new IllegalStateException("failed to connect", e); - } - } - public boolean isOpen() { return this.jschSession.isConnected(); } @@ -147,7 +134,7 @@ class SftpSession implements Session { } try { this.remove(pathTo); - if (logger.isDebugEnabled()){ + if (logger.isDebugEnabled()) { logger.debug("Delete file: " + pathTo + " succeeded. Will attempt rename again"); } } @@ -160,19 +147,85 @@ class SftpSession implements Session { } catch (SftpException sftpex2) { throw new NestedIOException("failed to rename from " + pathFrom + " to " + pathTo, sftpex2); - } + } } - if (logger.isDebugEnabled()){ + if (logger.isDebugEnabled()) { logger.debug("File: " + pathFrom + " was successfully renamed to " + pathTo); - } + } } - public void mkdir(String directory) throws IOException { - try { - this.channel.mkdir(directory); - } catch (SftpException e) { - throw new NestedIOException("failed to create remote directory '" + directory + "'.", e); + public void mkdir(String remoteDirectory) throws IOException { + try { + this.mkdirRecursively(remoteDirectory, remoteDirectory); } + catch (SftpException e) { + throw new NestedIOException("failed to create remote directory '" + remoteDirectory + "'.", e); + } + } + + 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(); + } + } + catch (JSchException e) { + throw new IllegalStateException("failed to connect", e); + } + } + + /** + * Since the underlying SFTP API does not give us a clean method to create directories recursively, + * we need to create them one at the time starting from the path that we know actually exists. + * To determine the existing path we need to iterate through each delimited segment starting from + * the full directory path moving backward until we find it. Once found we need to start creating + * individual directories for each segment; so in this method on the initial call the two parameters + * will be the same, but for each recursive call the 'currentPath' is the directory with one less + * segment from the previous 'currentPath'. For example, if you had '/foo/bar/baz', in the next + * iteration it would be '/foo/bar/', and then just '/foo' and so on. + */ + private void mkdirRecursively(String currentPath, String fullPath) throws SftpException { + String remoteFileSeparator = "/"; + if (this.exists(currentPath)) { + String missingDirectoryPath = fullPath.substring(currentPath.length()); + String[] directories = StringUtils.tokenizeToStringArray(missingDirectoryPath, remoteFileSeparator); + String directory = currentPath + remoteFileSeparator; + for (String directorySegment : directories) { + directory += directorySegment + remoteFileSeparator; + if (logger.isDebugEnabled()){ + logger.debug("Creating '" + directory + "'"); + } + this.channel.mkdir(directory); + } + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Directory '" + currentPath + "' does not exist. Will attempt to auto-create it"); + } + int nextSeparatorIndex = currentPath.lastIndexOf(remoteFileSeparator); + if (nextSeparatorIndex <= 0) { + throw new MessagingException("Failed to auto-create directory '" + fullPath + "'"); + } + else { + currentPath = currentPath.substring(0, nextSeparatorIndex); + this.mkdirRecursively(currentPath, fullPath); + } + } + } + + private boolean exists(String path) { + try { + this.channel.lstat(path); + return true; + } + catch (SftpException e) { + // ignore + } + return false; } } diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpInboundOutboundSanitySample.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpInboundOutboundSanitySample.java index c01be69628..95b84dc188 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpInboundOutboundSanitySample.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpInboundOutboundSanitySample.java @@ -15,18 +15,17 @@ */ package org.springframework.integration.sftp.config; -import static junit.framework.Assert.assertTrue; - import java.io.File; import org.junit.Ignore; import org.junit.Test; - import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.integration.MessageChannel; import org.springframework.integration.message.GenericMessage; +import static junit.framework.Assert.assertTrue; + /** * @author Oleg Zhurakousy * @@ -72,11 +71,10 @@ public class SftpInboundOutboundSanitySample { MessageChannel ftpChannel = ac.getBean("ftpChannel", MessageChannel.class); ftpChannel.send(new GenericMessage(fileA)); ftpChannel.send(new GenericMessage(fileB)); - Thread.sleep(3000); + Thread.sleep(6000); fileA = new File("remote-target-dir/a.test-foo"); fileB = new File("remote-target-dir/b.test-foo"); assertTrue(fileA.exists()); assertTrue(fileB.exists()); } - } diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundTransferSample-ignored.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundTransferSample-ignored.xml index aa070179d1..de5d04234e 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundTransferSample-ignored.xml +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundTransferSample-ignored.xml @@ -24,6 +24,6 @@ temporary-file-suffix=".foo" remote-filename-generator-expression="payload.getName() + '-foo'" auto-create-directory="true" - remote-directory="/Users/ozhurakousky/workspace-sts-2.3.3.M2/si/spring-integration/spring-integration-sftp/remote-target-dir/foo/bar/baz"/> + remote-directory="spring-integration-sftp/remote-target-dir/bar/baz"/> diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/session/SftpTestSessionFactory.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/session/SftpTestSessionFactory.java index 09170e4106..4debabd518 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/session/SftpTestSessionFactory.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/session/SftpTestSessionFactory.java @@ -19,11 +19,11 @@ import org.springframework.integration.file.remote.session.Session; /** * @author Oleg Zhurakousky - * + * */ public class SftpTestSessionFactory { - public static Session createSftpSession(com.jcraft.jsch.Session jschSession){ + public static Session createSftpSession(com.jcraft.jsch.Session jschSession) { SftpSession sftpSession = new SftpSession(jschSession); sftpSession.connect(); return sftpSession;