Merge pull request #301 from olegz/INT-2351-v2

This commit is contained in:
Mark Fisher
2012-01-05 16:57:02 -05:00
10 changed files with 147 additions and 77 deletions

View File

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

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

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

View File

@@ -46,4 +46,6 @@ public interface Session<T> {
void close();
boolean isOpen();
boolean exists(String path) throws IOException;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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() {

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