From 9babf3d784e85caa96d2b4f9dcdae7a793cd0eb7 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Fri, 6 Jan 2012 10:52:47 -0500 Subject: [PATCH] INT-2390 backported INT-2350 and INT-2351 to 2.0.6 --- ...emoteFileOutboundChannelAdapterParser.java | 2 +- .../FileTransferringMessageHandler.java | 48 +++++++++++++++++-- .../remote/session/CachingSessionFactory.java | 6 ++- .../file/remote/session/Session.java | 4 +- .../integration/ftp/session/FtpSession.java | 20 ++++++++ ...boundChannelAdapterParserTests-context.xml | 3 +- ...OutboundChannelAdapterParserTests-fail.xml | 37 ++++++++++++++ .../FtpOutboundChannelAdapterParserTests.java | 20 +++++--- .../integration/sftp/session/SftpSession.java | 47 ++---------------- 9 files changed, 130 insertions(+), 57 deletions(-) create mode 100644 spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-fail.xml 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 4a94afc433..b285badcbc 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 @@ -97,7 +97,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 4ddb465355..7f8f15499a 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,8 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.LinkedList; +import java.util.List; import org.springframework.expression.Expression; import org.springframework.integration.Message; @@ -74,7 +76,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; } @@ -106,6 +108,9 @@ 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'"); + } } @@ -194,7 +199,13 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { // write remote file first with .writing extension String tempFilePath = remoteFilePath + 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); + } } try { session.write(fileInputStream, tempFilePath); @@ -209,4 +220,35 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { } } + 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(0, pathSegment); + nextSeparatorIndex = pathSegment.lastIndexOf(remoteFileSeparator); + } + } + + 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 bb67b4efaa..490727e77c 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-2010 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. @@ -152,6 +152,10 @@ public class CachingSessionFactory implements SessionFactory, DisposableBean { 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 be3413b4ab..b49083869d 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 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. @@ -46,4 +46,6 @@ public interface Session { void close(); boolean isOpen(); + + boolean exists(String path) throws IOException; } 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 3d225774e9..369d48baee 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 @@ -136,4 +136,24 @@ class FtpSession implements Session { this.client.makeDirectory(directory); } } + + + 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 0acdeb5166..756098890b 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 @@ -22,7 +22,8 @@ cache-sessions="false" remote-directory="foo/bar" 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..7993836171 --- /dev/null +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-fail.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + 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 6c1100db1c..66d0becbbf 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 @@ -16,15 +16,12 @@ package org.springframework.integration.ftp.config; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertTrue; -import static org.junit.Assert.assertSame; - 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; @@ -35,6 +32,12 @@ import org.springframework.integration.file.remote.session.CachingSessionFactory import org.springframework.integration.ftp.session.DefaultFtpSessionFactory; import org.springframework.integration.test.util.TestUtils; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; + +import static org.junit.Assert.assertSame; + /** * @author Oleg Zhurakousky * @author Gary Russell @@ -55,7 +58,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, "temporaryDirectory")); @@ -86,5 +89,10 @@ public class FtpOutboundChannelAdapterParserTests { Object innerSfProperty = TestUtils.getPropertyValue(sfProperty, "sessionFactory"); assertEquals(DefaultFtpSessionFactory.class, innerSfProperty.getClass()); } + + @Test(expected=BeanCreationException.class) + public void testFailWithEmptyRfsAndAcdTrue() throws Exception{ + new ClassPathXmlApplicationContext("FtpOutboundChannelAdapterParserTests-fail.xml", this.getClass()); + } } 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 2808b06a6c..b668ac6502 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; @@ -156,7 +154,7 @@ 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); @@ -178,46 +176,7 @@ class SftpSession implements Session { } } - /** - * 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) { + public boolean exists(String path) { try { this.channel.lstat(path); return true;