Merge pull request #301 from olegz/INT-2351-v2
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<F> 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<F> 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<F> 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<F> extends AbstractMessageHandler {
|
||||
}
|
||||
return directoryPath;
|
||||
}
|
||||
|
||||
private void makeDirectories(String path, Session<F> 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(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<F> implements SessionFactory<F>, DisposableBe
|
||||
public void mkdir(String directory) throws IOException {
|
||||
this.targetSession.mkdir(directory);
|
||||
}
|
||||
|
||||
public boolean exists(String path) throws IOException{
|
||||
return this.targetSession.exists(path);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -46,4 +46,6 @@ public interface Session<T> {
|
||||
void close();
|
||||
|
||||
boolean isOpen();
|
||||
|
||||
boolean exists(String path) throws IOException;
|
||||
}
|
||||
|
||||
@@ -273,6 +273,10 @@ public class RemoteFileOutboundGatewayTests {
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
}
|
||||
|
||||
public boolean exists(String path) throws IOException {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<File> out = (Message<File>) gw.handleRequestMessage(new GenericMessage<String>("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<File> out = (Message<File>) gw.handleRequestMessage(new GenericMessage<String>("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<String>("f1"));
|
||||
File out = new File(this.tmpDir + "/x/f1");
|
||||
|
||||
@@ -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<FTPFile> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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.xsd
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp.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"
|
||||
temporary-remote-directory="baz/abc"
|
||||
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>
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<LsEntry> {
|
||||
|
||||
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<LsEntry> {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user