INT-2390 backported INT-2350 and INT-2351 to 2.0.6

This commit is contained in:
Oleg Zhurakousky
2012-01-06 10:52:47 -05:00
parent 575a80f9c0
commit 9babf3d784
9 changed files with 130 additions and 57 deletions

View File

@@ -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();
}

View File

@@ -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<String> pathsToCreate = new LinkedList<String>();
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);
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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"/>

View File

@@ -0,0 +1,37 @@
<?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">
<bean id="ftpSessionFactory" class="org.springframework.integration.ftp.session.DefaultFtpSessionFactory">
<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="ftpOutbound"
channel="ftpChannel"
session-factory="ftpSessionFactory"
cache-sessions="false"
remote-directory="foo/bar"
charset="UTF-8"
auto-create-directory="true"
remote-file-separator=""
temporary-file-suffix=".foo"
remote-filename-generator="fileNameGenerator"
order="23"/>
<int:publish-subscribe-channel id="ftpChannel"/>
<bean id="fileNameGenerator" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.file.FileNameGenerator"/>
</bean>
</beans>

View File

@@ -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());
}
}

View File

@@ -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;