INT-2390 backported INT-2350 and INT-2351 to 2.0.6
This commit is contained in:
@@ -97,7 +97,7 @@ public class RemoteFileOutboundChannelAdapterParser extends AbstractOutboundChan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "charset");
|
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "charset");
|
||||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "remote-file-separator");
|
handlerBuilder.addPropertyValue("remoteFileSeparator", element.getAttribute("remote-file-separator"));
|
||||||
return handlerBuilder.getBeanDefinition();
|
return handlerBuilder.getBeanDefinition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.expression.Expression;
|
import org.springframework.expression.Expression;
|
||||||
import org.springframework.integration.Message;
|
import org.springframework.integration.Message;
|
||||||
@@ -74,7 +76,7 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setRemoteFileSeparator(String remoteFileSeparator) {
|
public void setRemoteFileSeparator(String remoteFileSeparator) {
|
||||||
Assert.hasText(remoteFileSeparator, "'remoteFileSeparator' must not be empty");
|
Assert.notNull(remoteFileSeparator, "'remoteFileSeparator' must not be null");
|
||||||
this.remoteFileSeparator = remoteFileSeparator;
|
this.remoteFileSeparator = remoteFileSeparator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +108,9 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
|
|||||||
|
|
||||||
protected void onInit() throws Exception {
|
protected void onInit() throws Exception {
|
||||||
Assert.notNull(this.directoryExpressionProcessor, "remoteDirectoryExpression is required");
|
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
|
// write remote file first with .writing extension
|
||||||
String tempFilePath = remoteFilePath + this.temporaryFileSuffix;
|
String tempFilePath = remoteFilePath + this.temporaryFileSuffix;
|
||||||
if (this.autoCreateDirectory) {
|
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 {
|
try {
|
||||||
session.write(fileInputStream, tempFilePath);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 {
|
public void mkdir(String directory) throws IOException {
|
||||||
this.targetSession.mkdir(directory);
|
this.targetSession.mkdir(directory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean exists(String path) throws IOException {
|
||||||
|
return this.targetSession.exists(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -46,4 +46,6 @@ public interface Session {
|
|||||||
void close();
|
void close();
|
||||||
|
|
||||||
boolean isOpen();
|
boolean isOpen();
|
||||||
|
|
||||||
|
boolean exists(String path) throws IOException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,4 +136,24 @@ class FtpSession implements Session {
|
|||||||
this.client.makeDirectory(directory);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,8 @@
|
|||||||
cache-sessions="false"
|
cache-sessions="false"
|
||||||
remote-directory="foo/bar"
|
remote-directory="foo/bar"
|
||||||
charset="UTF-8"
|
charset="UTF-8"
|
||||||
remote-file-separator="."
|
auto-create-directory="false"
|
||||||
|
remote-file-separator=""
|
||||||
temporary-file-suffix=".foo"
|
temporary-file-suffix=".foo"
|
||||||
remote-filename-generator="fileNameGenerator"
|
remote-filename-generator="fileNameGenerator"
|
||||||
order="23"/>
|
order="23"/>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -16,15 +16,12 @@
|
|||||||
|
|
||||||
package org.springframework.integration.ftp.config;
|
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.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.BeanCreationException;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||||
import org.springframework.integration.channel.PublishSubscribeChannel;
|
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.ftp.session.DefaultFtpSessionFactory;
|
||||||
import org.springframework.integration.test.util.TestUtils;
|
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 Oleg Zhurakousky
|
||||||
* @author Gary Russell
|
* @author Gary Russell
|
||||||
@@ -55,7 +58,7 @@ public class FtpOutboundChannelAdapterParserTests {
|
|||||||
String remoteFileSeparator = (String) TestUtils.getPropertyValue(handler, "remoteFileSeparator");
|
String remoteFileSeparator = (String) TestUtils.getPropertyValue(handler, "remoteFileSeparator");
|
||||||
assertNotNull(remoteFileSeparator);
|
assertNotNull(remoteFileSeparator);
|
||||||
assertEquals(".foo", TestUtils.getPropertyValue(handler, "temporaryFileSuffix", String.class));
|
assertEquals(".foo", TestUtils.getPropertyValue(handler, "temporaryFileSuffix", String.class));
|
||||||
assertEquals(".", remoteFileSeparator);
|
assertEquals("", remoteFileSeparator);
|
||||||
assertEquals(ac.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "fileNameGenerator"));
|
assertEquals(ac.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "fileNameGenerator"));
|
||||||
assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "charset"));
|
assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "charset"));
|
||||||
assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectory"));
|
assertNotNull(TestUtils.getPropertyValue(handler, "temporaryDirectory"));
|
||||||
@@ -86,5 +89,10 @@ public class FtpOutboundChannelAdapterParserTests {
|
|||||||
Object innerSfProperty = TestUtils.getPropertyValue(sfProperty, "sessionFactory");
|
Object innerSfProperty = TestUtils.getPropertyValue(sfProperty, "sessionFactory");
|
||||||
assertEquals(DefaultFtpSessionFactory.class, innerSfProperty.getClass());
|
assertEquals(DefaultFtpSessionFactory.class, innerSfProperty.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected=BeanCreationException.class)
|
||||||
|
public void testFailWithEmptyRfsAndAcdTrue() throws Exception{
|
||||||
|
new ClassPathXmlApplicationContext("FtpOutboundChannelAdapterParserTests-fail.xml", this.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import org.springframework.core.NestedIOException;
|
import org.springframework.core.NestedIOException;
|
||||||
import org.springframework.integration.MessagingException;
|
|
||||||
import org.springframework.integration.file.remote.session.Session;
|
import org.springframework.integration.file.remote.session.Session;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.FileCopyUtils;
|
import org.springframework.util.FileCopyUtils;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import com.jcraft.jsch.ChannelSftp;
|
import com.jcraft.jsch.ChannelSftp;
|
||||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||||
@@ -156,7 +154,7 @@ class SftpSession implements Session {
|
|||||||
|
|
||||||
public void mkdir(String remoteDirectory) throws IOException {
|
public void mkdir(String remoteDirectory) throws IOException {
|
||||||
try {
|
try {
|
||||||
this.mkdirRecursively(remoteDirectory, remoteDirectory);
|
this.channel.mkdir(remoteDirectory);
|
||||||
}
|
}
|
||||||
catch (SftpException e) {
|
catch (SftpException e) {
|
||||||
throw new NestedIOException("failed to create remote directory '" + remoteDirectory + "'.", e);
|
throw new NestedIOException("failed to create remote directory '" + remoteDirectory + "'.", e);
|
||||||
@@ -178,46 +176,7 @@ class SftpSession implements Session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public boolean exists(String path) {
|
||||||
* 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 {
|
try {
|
||||||
this.channel.lstat(path);
|
this.channel.lstat(path);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user