diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java index aebd118fbd..643790e4a9 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java @@ -76,7 +76,7 @@ public class RemoteFileOutboundChannelAdapterParser extends AbstractOutboundChan } } IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "charset"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "remote-file-separator"); + handlerBuilder.addPropertyValue("remoteFileSeparator", element.getAttribute("remote-file-separator")); return handlerBuilder.getBeanDefinition(); } 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 0070988ac4..94642ff3fe 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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,6 +20,9 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; import org.springframework.expression.Expression; import org.springframework.integration.Message; @@ -76,7 +79,7 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { } public void setRemoteFileSeparator(String remoteFileSeparator) { - Assert.hasText(remoteFileSeparator, "'remoteFileSeparator' must not be empty"); + Assert.notNull(remoteFileSeparator, "'remoteFileSeparator' must not be null"); this.remoteFileSeparator = remoteFileSeparator; } @@ -113,9 +116,11 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { protected void onInit() throws Exception { Assert.notNull(this.directoryExpressionProcessor, "remoteDirectoryExpression is required"); + if (this.autoCreateDirectory){ + Assert.hasText(this.remoteFileSeparator, "'remoteFileSeparator' must not be empty when 'autoCreateDirectory' is set to 'true'"); + } } - @Override protected void handleMessageInternal(Message message) throws Exception { File file = this.redeemForStorableFile(message); @@ -201,9 +206,17 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { String tempRemoteFilePath = temporaryRemoteDirectory + fileName; // write remote file first with .writing extension String tempFilePath = tempRemoteFilePath + this.temporaryFileSuffix; + if (this.autoCreateDirectory) { - session.mkdir(remoteDirectory); + try { + this.makeDirectories(remoteDirectory, session); + } + catch (IllegalStateException e) { + // Revert to old FTP behavior if recursive mkdir fails, for backwards compatibility + session.mkdir(remoteDirectory); + } } + FileInputStream fileInputStream = new FileInputStream(file); try { session.write(fileInputStream, tempFilePath); @@ -227,5 +240,36 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { } return directoryPath; } + + private void makeDirectories(String path, Session session) throws IOException { + if (!session.exists(path)){ + int nextSeparatorIndex = path.lastIndexOf(remoteFileSeparator); + + if (nextSeparatorIndex > -1){ + List pathsToCreate = new LinkedList(); + while (nextSeparatorIndex > -1){ + String pathSegment = path.substring(0, nextSeparatorIndex); + if (session.exists(pathSegment)){ + // no more paths to create + break; + } + else { + pathsToCreate.add(pathSegment); + nextSeparatorIndex = pathSegment.lastIndexOf(remoteFileSeparator); + } + } + Collections.reverse(pathsToCreate); + for (String pathToCreate : pathsToCreate) { + if (logger.isDebugEnabled()){ + logger.debug("Creating '" + pathToCreate + "'"); + } + session.mkdir(pathToCreate); + } + } + else { + session.mkdir(path); + } + } + } } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/CachingSessionFactory.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/CachingSessionFactory.java index 438d9573dd..56ec47e639 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/CachingSessionFactory.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/CachingSessionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -158,6 +158,10 @@ public class CachingSessionFactory implements SessionFactory, DisposableBe public void mkdir(String directory) throws IOException { this.targetSession.mkdir(directory); } + + public boolean exists(String path) throws IOException{ + return this.targetSession.exists(path); + } } } 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 33d334b006..62f6da68a2 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,4 +46,6 @@ public interface Session { void close(); boolean isOpen(); + + boolean exists(String path) throws IOException; } 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 758bb17285..691fb25ee8 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 @@ -273,6 +273,10 @@ public class RemoteFileOutboundGatewayTests { public boolean isOpen() { return open; } + + public boolean exists(String path) throws IOException { + return true; + } }); @SuppressWarnings("unchecked") Message out = (Message) gw.handleRequestMessage(new GenericMessage("f1")); @@ -327,6 +331,9 @@ public class RemoteFileOutboundGatewayTests { public boolean isOpen() { return open; } + public boolean exists(String path) throws IOException { + return true; + } }); @SuppressWarnings("unchecked") Message out = (Message) gw.handleRequestMessage(new GenericMessage("x/f1")); @@ -379,6 +386,9 @@ public class RemoteFileOutboundGatewayTests { public boolean isOpen() { return open; } + public boolean exists(String path) throws IOException { + return true; + } }); gw.handleRequestMessage(new GenericMessage("f1")); File out = new File(this.tmpDir + "/x/f1"); 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 6f7d903c04..67d3d34c43 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 @@ -27,7 +27,6 @@ import org.apache.commons.net.ftp.FTPFile; import org.springframework.integration.file.remote.session.Session; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * Implementation of {@link Session} for FTP. @@ -119,20 +118,27 @@ class FtpSession implements Session { logger.info("File has been successfully renamed from: " + pathFrom + " to " + pathTo); } } - /** - * Since the underlying FTP API does not give us a clean method to create multiple directories - * we need to create them manually one at the time starting from the first segment of the path - * regardless if it exists or not since makeDirectory(path) will return - * false in the case when directory exists. - */ - public void mkdir(String remoteDirectory) throws IOException { - String remoteFileSeparator = "/"; - String[] directories = StringUtils.tokenizeToStringArray(remoteDirectory, remoteFileSeparator); - String directory = ""; - for (String directorySegment : directories) { - directory += directorySegment + remoteFileSeparator; - this.client.makeDirectory(directory); + public void mkdir(String remoteDirectory) throws IOException { + this.client.makeDirectory(remoteDirectory); + } + + public boolean exists(String path) throws IOException{ + Assert.hasText(path, "'path' must not be empty"); + + String currentWorkingPath = this.client.printWorkingDirectory(); + Assert.state(currentWorkingPath != null, "working directory cannot be determined, therefore exists check can not be completed"); + boolean exists = false; + + try { + if (this.client.changeWorkingDirectory(path)){ + exists = true; + } + } + finally { + this.client.changeWorkingDirectory(currentWorkingPath); } + + return exists; } } diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml index c0b0ff9bf3..3d3c19e61c 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml @@ -29,7 +29,8 @@ remote-directory="foo/bar" temporary-remote-directory="baz/abc" charset="UTF-8" - remote-file-separator="." + auto-create-directory="false" + remote-file-separator="" temporary-file-suffix=".foo" remote-filename-generator="fileNameGenerator" order="23"/> diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-fail.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-fail.xml new file mode 100644 index 0000000000..de485bd6a9 --- /dev/null +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-fail.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java index 4fbb9145e4..1fb8b57f00 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java @@ -25,6 +25,8 @@ import java.util.Iterator; import java.util.Set; import org.junit.Test; + +import org.springframework.beans.factory.BeanCreationException; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.integration.channel.PublishSubscribeChannel; @@ -55,7 +57,7 @@ public class FtpOutboundChannelAdapterParserTests { String remoteFileSeparator = (String) TestUtils.getPropertyValue(handler, "remoteFileSeparator"); assertNotNull(remoteFileSeparator); assertEquals(".foo", TestUtils.getPropertyValue(handler, "temporaryFileSuffix", String.class)); - assertEquals(".", remoteFileSeparator); + assertEquals("", remoteFileSeparator); assertEquals(ac.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "fileNameGenerator")); assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "charset")); assertNotNull(TestUtils.getPropertyValue(handler, "directoryExpressionProcessor")); @@ -77,6 +79,11 @@ public class FtpOutboundChannelAdapterParserTests { assertSame(TestUtils.getPropertyValue(ac.getBean("ftpOutbound2"), "handler"), iterator.next()); assertSame(handler, iterator.next()); } + + @Test(expected=BeanCreationException.class) + public void testFailWithEmptyRfsAndAcdTrue() throws Exception{ + new ClassPathXmlApplicationContext("FtpOutboundChannelAdapterParserTests-fail.xml", this.getClass()); + } @Test public void cachingByDefault() { 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 52d2d225e2..60d12378c6 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-2011 the original author or authors. + * Copyright 2002-2012 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,11 +25,9 @@ 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; @@ -155,13 +153,24 @@ class SftpSession implements Session { public void mkdir(String remoteDirectory) throws IOException { try { - this.mkdirRecursively(remoteDirectory, remoteDirectory); + this.channel.mkdir(remoteDirectory); } catch (SftpException e) { throw new NestedIOException("failed to create remote directory '" + remoteDirectory + "'.", e); } } + public boolean exists(String path) { + try { + this.channel.lstat(path); + return true; + } + catch (SftpException e) { + // ignore + } + return false; + } + void connect() { try { if (!this.jschSession.isConnected()) { @@ -176,55 +185,4 @@ class SftpSession implements Session { 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; - } - }