Merge pull request #135 from olegz/INT-2048
Improved check for existing directories
This commit is contained in:
@@ -68,6 +68,7 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
|
||||
this.sessionFactory = sessionFactory;
|
||||
}
|
||||
|
||||
|
||||
public void setAutoCreateDirectory(boolean autoCreateDirectory) {
|
||||
this.autoCreateDirectory = autoCreateDirectory;
|
||||
}
|
||||
@@ -78,11 +79,12 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
|
||||
}
|
||||
|
||||
public void setRemoteDirectoryExpression(Expression remoteDirectoryExpression) {
|
||||
Assert.notNull(remoteDirectoryExpression, "remoteDirectoryExpression must not be null");
|
||||
this.directoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor<String>(remoteDirectoryExpression, String.class);
|
||||
}
|
||||
|
||||
protected String getTemporaryFileSuffix() {
|
||||
return temporaryFileSuffix;
|
||||
return this.temporaryFileSuffix;
|
||||
}
|
||||
|
||||
public void setTemporaryDirectory(File temporaryDirectory) {
|
||||
@@ -136,7 +138,7 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
|
||||
try {
|
||||
file.delete();
|
||||
}
|
||||
catch (Throwable th) {
|
||||
catch (Throwable t) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
@@ -160,7 +162,7 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
|
||||
sendableFile = new File(this.temporaryDirectory, tempFileName); // will only create temp file for String/byte[]
|
||||
byte[] bytes = null;
|
||||
if (payload instanceof String) {
|
||||
bytes = ((String) payload).getBytes(charset);
|
||||
bytes = ((String) payload).getBytes(this.charset);
|
||||
}
|
||||
else {
|
||||
bytes = (byte[]) payload;
|
||||
@@ -169,7 +171,7 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported payload type. The only supported payloads are " +
|
||||
"java.io.File, java.lang.String and byte[]");
|
||||
"java.io.File, java.lang.String, and byte[]");
|
||||
}
|
||||
return sendableFile;
|
||||
}
|
||||
@@ -185,17 +187,15 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
|
||||
if (!StringUtils.hasText(remoteDirectory)) {
|
||||
remoteDirectory = "";
|
||||
}
|
||||
else if (!remoteDirectory.endsWith(remoteFileSeparator)) {
|
||||
remoteDirectory += remoteFileSeparator;
|
||||
else if (!remoteDirectory.endsWith(this.remoteFileSeparator)) {
|
||||
remoteDirectory += this.remoteFileSeparator;
|
||||
}
|
||||
String remoteFilePath = remoteDirectory + fileName;
|
||||
// write remote file first with .writing extension
|
||||
String tempFilePath = remoteFilePath + this.temporaryFileSuffix;
|
||||
|
||||
if (this.autoCreateDirectory){
|
||||
this.ensureDirectoryExists(session, remoteDirectory, remoteDirectory);
|
||||
if (this.autoCreateDirectory) {
|
||||
session.mkdir(remoteDirectory);
|
||||
}
|
||||
|
||||
try {
|
||||
session.write(fileInputStream, tempFilePath);
|
||||
// then rename it to its final name
|
||||
@@ -209,35 +209,4 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureDirectoryExists(Session session, String remoteDirectory, String originalRemoteDirectory){
|
||||
try {
|
||||
session.list(remoteDirectory);
|
||||
} catch (IOException e) {
|
||||
if (logger.isDebugEnabled()){
|
||||
logger.debug("Directory '" + remoteDirectory + "' does not exist. Will attempt to auto-create it");
|
||||
}
|
||||
int nextSeparatorIndex = remoteDirectory.lastIndexOf(this.remoteFileSeparator);
|
||||
if (nextSeparatorIndex <= 0){
|
||||
throw new MessagingException("Failed to auto-create directory '" + originalRemoteDirectory + "'");
|
||||
}
|
||||
else {
|
||||
remoteDirectory = remoteDirectory.substring(0, nextSeparatorIndex);
|
||||
this.ensureDirectoryExists(session, remoteDirectory, originalRemoteDirectory);
|
||||
}
|
||||
}
|
||||
String missingDirectoryPath = originalRemoteDirectory.substring(remoteDirectory.length());
|
||||
String[] directories = StringUtils.tokenizeToStringArray(missingDirectoryPath, this.remoteFileSeparator);
|
||||
String directory = remoteDirectory + this.remoteFileSeparator;
|
||||
for (String directorySegment : directories) {
|
||||
directory += directorySegment+this.remoteFileSeparator;
|
||||
if (logger.isDebugEnabled()){
|
||||
logger.debug("Creating '" + directory + "'");
|
||||
}
|
||||
try {
|
||||
session.mkdir(directory);
|
||||
} catch (Exception e) {
|
||||
throw new MessagingException("Failed to auto-create directory '" + directory + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,5 +46,4 @@ public interface Session {
|
||||
void close();
|
||||
|
||||
boolean isOpen();
|
||||
|
||||
}
|
||||
|
||||
@@ -270,7 +270,8 @@ public class RemoteFileOutboundGatewayTests {
|
||||
}
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
} });
|
||||
}
|
||||
});
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<File> out = (Message<File>) gw.handleRequestMessage(new GenericMessage<String>("f1"));
|
||||
File outFile = new File(this.tmpDir + "/f1");
|
||||
@@ -323,7 +324,8 @@ public class RemoteFileOutboundGatewayTests {
|
||||
}
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
} });
|
||||
}
|
||||
});
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<File> out = (Message<File>) gw.handleRequestMessage(new GenericMessage<String>("x/f1"));
|
||||
File outFile = new File(this.tmpDir + "/f1");
|
||||
@@ -374,7 +376,8 @@ public class RemoteFileOutboundGatewayTests {
|
||||
}
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
} });
|
||||
}
|
||||
});
|
||||
gw.handleRequestMessage(new GenericMessage<String>("f1"));
|
||||
File out = new File(this.tmpDir + "/x/f1");
|
||||
assertTrue(out.exists());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2011 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.
|
||||
@@ -51,7 +51,7 @@ class FtpSession implements Session {
|
||||
public boolean remove(String path) throws IOException {
|
||||
Assert.hasText(path, "path must not be null");
|
||||
boolean completed = this.client.deleteFile(path);
|
||||
if (!completed){
|
||||
if (!completed) {
|
||||
throw new IOException("Failed to delete '" + path + "'. Server replied with: " + client.getReplyString());
|
||||
}
|
||||
return completed;
|
||||
@@ -63,25 +63,28 @@ class FtpSession implements Session {
|
||||
return this.client.listFiles(path);
|
||||
}
|
||||
|
||||
public void read(String path, OutputStream fos) throws IOException{
|
||||
public void read(String path, OutputStream fos) throws IOException {
|
||||
Assert.hasText(path, "path must not be null");
|
||||
Assert.notNull(fos, "outputStream must not be null");
|
||||
boolean completed = this.client.retrieveFile(path, fos);
|
||||
if (!completed){
|
||||
throw new IOException("Failed to copy '" + path + "'. Server replied with: " + client.getReplyString());
|
||||
if (!completed) {
|
||||
throw new IOException("Failed to copy '" + path +
|
||||
"'. Server replied with: " + this.client.getReplyString());
|
||||
}
|
||||
logger.info("File have been successfully transfered to: " + path);
|
||||
}
|
||||
|
||||
public void write(InputStream inputStream, String path) throws IOException{
|
||||
public void write(InputStream inputStream, String path) throws IOException {
|
||||
Assert.notNull(inputStream, "inputStream must not be null");
|
||||
Assert.hasText(path, "path must not be null");
|
||||
boolean completed = client.storeFile(path, inputStream);
|
||||
if (!completed){
|
||||
boolean completed = this.client.storeFile(path, inputStream);
|
||||
if (!completed) {
|
||||
throw new IOException("Failed to write to '" + path
|
||||
+ "'. Server replied with: " + client.getReplyString());
|
||||
+ "'. Server replied with: " + this.client.getReplyString());
|
||||
}
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("File has been successfully transfered to: " + path);
|
||||
}
|
||||
logger.info("File have been successfully transfered to: " + path);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
@@ -97,21 +100,24 @@ class FtpSession implements Session {
|
||||
|
||||
public boolean isOpen() {
|
||||
try {
|
||||
client.noop();
|
||||
} catch (Exception e) {
|
||||
this.client.noop();
|
||||
}
|
||||
catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void rename(String pathFrom, String pathTo) throws IOException{
|
||||
client.deleteFile(pathTo);
|
||||
boolean completed = client.rename(pathFrom, pathTo);
|
||||
if (!completed){
|
||||
this.client.deleteFile(pathTo);
|
||||
boolean completed = this.client.rename(pathFrom, pathTo);
|
||||
if (!completed) {
|
||||
throw new IOException("Failed to rename '" + pathFrom +
|
||||
"' to " + pathTo + "'. Server replied with: " + client.getReplyString());
|
||||
"' to " + pathTo + "'. Server replied with: " + this.client.getReplyString());
|
||||
}
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("File has been successfully renamed from: " + pathFrom + " to " + pathTo);
|
||||
}
|
||||
logger.info("File have been successfully renamed from: " + pathFrom + " to " + pathTo);
|
||||
}
|
||||
|
||||
public void mkdir(String directory) throws IOException {
|
||||
@@ -124,4 +130,5 @@ class FtpSession implements Session {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,6 +37,13 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="auto-create-directory" type="xsd:string" default="false">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Specify whether to automatically create the remote target directory if it doesn't exist.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="remote-file-separator" type="xsd:string" default="/">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
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="192.168.1.155"/>
|
||||
<property name="username" value="oleg"/>
|
||||
<property name="host" value="192.168.28.143"/>
|
||||
<property name="username" value="user"/>
|
||||
<property name="password" value="password"/>
|
||||
<property name="bufferSize" value="1000000"/>
|
||||
</bean>
|
||||
@@ -17,7 +17,8 @@
|
||||
<int:channel id="ftpChannel"/>
|
||||
|
||||
<int-ftp:outbound-channel-adapter
|
||||
auto-create-directory="true"
|
||||
session-factory="ftpSessionFactory"
|
||||
remote-directory="/home/ozhurakousky"
|
||||
remote-directory="./dsf/sdfsfs/sdffs"
|
||||
channel="ftpChannel"/>
|
||||
</beans>
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
*/
|
||||
package org.springframework.integration.ftp.session;
|
||||
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.apache.commons.net.ftp.FTPClient;
|
||||
import org.junit.Test;
|
||||
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
@@ -47,7 +47,6 @@ public class SessionFactoryTests {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2011 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,9 +25,11 @@ 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;
|
||||
@@ -44,12 +46,13 @@ import com.jcraft.jsch.SftpException;
|
||||
* @since 2.0
|
||||
*/
|
||||
class SftpSession implements Session {
|
||||
|
||||
private final Log logger = LogFactory.getLog(this.getClass());
|
||||
|
||||
private volatile ChannelSftp channel;
|
||||
|
||||
|
||||
private final com.jcraft.jsch.Session jschSession;
|
||||
|
||||
private volatile ChannelSftp channel;
|
||||
|
||||
|
||||
public SftpSession(com.jcraft.jsch.Session jschSession) {
|
||||
Assert.notNull(jschSession, "jschSession must not be null");
|
||||
@@ -57,7 +60,7 @@ class SftpSession implements Session {
|
||||
}
|
||||
|
||||
|
||||
public boolean remove(String path) throws IOException{
|
||||
public boolean remove(String path) throws IOException {
|
||||
Assert.state(this.channel != null, "session is not connected");
|
||||
try {
|
||||
this.channel.rm(path);
|
||||
@@ -89,9 +92,8 @@ class SftpSession implements Session {
|
||||
return new LsEntry[0];
|
||||
}
|
||||
|
||||
public void read(String source, OutputStream os) throws IOException{
|
||||
public void read(String source, OutputStream os) throws IOException {
|
||||
Assert.state(this.channel != null, "session is not connected");
|
||||
|
||||
try {
|
||||
InputStream is = this.channel.get(source);
|
||||
FileCopyUtils.copy(is, os);
|
||||
@@ -101,7 +103,7 @@ class SftpSession implements Session {
|
||||
}
|
||||
}
|
||||
|
||||
public void write(InputStream inputStream, String destination) throws IOException{
|
||||
public void write(InputStream inputStream, String destination) throws IOException {
|
||||
Assert.state(this.channel != null, "session is not connected");
|
||||
try {
|
||||
this.channel.put(inputStream, destination);
|
||||
@@ -117,21 +119,6 @@ class SftpSession implements Session {
|
||||
}
|
||||
}
|
||||
|
||||
void connect() {
|
||||
try {
|
||||
if (!this.jschSession.isConnected()) {
|
||||
this.jschSession.connect();
|
||||
this.channel = (ChannelSftp) this.jschSession.openChannel("sftp");
|
||||
}
|
||||
if (this.channel != null && !this.channel.isConnected()) {
|
||||
this.channel.connect();
|
||||
}
|
||||
}
|
||||
catch (JSchException e) {
|
||||
throw new IllegalStateException("failed to connect", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return this.jschSession.isConnected();
|
||||
}
|
||||
@@ -147,7 +134,7 @@ class SftpSession implements Session {
|
||||
}
|
||||
try {
|
||||
this.remove(pathTo);
|
||||
if (logger.isDebugEnabled()){
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Delete file: " + pathTo + " succeeded. Will attempt rename again");
|
||||
}
|
||||
}
|
||||
@@ -160,19 +147,85 @@ class SftpSession implements Session {
|
||||
}
|
||||
catch (SftpException sftpex2) {
|
||||
throw new NestedIOException("failed to rename from " + pathFrom + " to " + pathTo, sftpex2);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (logger.isDebugEnabled()){
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("File: " + pathFrom + " was successfully renamed to " + pathTo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void mkdir(String directory) throws IOException {
|
||||
try {
|
||||
this.channel.mkdir(directory);
|
||||
} catch (SftpException e) {
|
||||
throw new NestedIOException("failed to create remote directory '" + directory + "'.", e);
|
||||
public void mkdir(String remoteDirectory) throws IOException {
|
||||
try {
|
||||
this.mkdirRecursively(remoteDirectory, remoteDirectory);
|
||||
}
|
||||
catch (SftpException e) {
|
||||
throw new NestedIOException("failed to create remote directory '" + remoteDirectory + "'.", e);
|
||||
}
|
||||
}
|
||||
|
||||
void connect() {
|
||||
try {
|
||||
if (!this.jschSession.isConnected()) {
|
||||
this.jschSession.connect();
|
||||
this.channel = (ChannelSftp) this.jschSession.openChannel("sftp");
|
||||
}
|
||||
if (this.channel != null && !this.channel.isConnected()) {
|
||||
this.channel.connect();
|
||||
}
|
||||
}
|
||||
catch (JSchException e) {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,18 +15,17 @@
|
||||
*/
|
||||
package org.springframework.integration.sftp.config;
|
||||
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.integration.MessageChannel;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousy
|
||||
*
|
||||
@@ -72,11 +71,10 @@ public class SftpInboundOutboundSanitySample {
|
||||
MessageChannel ftpChannel = ac.getBean("ftpChannel", MessageChannel.class);
|
||||
ftpChannel.send(new GenericMessage<File>(fileA));
|
||||
ftpChannel.send(new GenericMessage<File>(fileB));
|
||||
Thread.sleep(3000);
|
||||
Thread.sleep(6000);
|
||||
fileA = new File("remote-target-dir/a.test-foo");
|
||||
fileB = new File("remote-target-dir/b.test-foo");
|
||||
assertTrue(fileA.exists());
|
||||
assertTrue(fileB.exists());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
temporary-file-suffix=".foo"
|
||||
remote-filename-generator-expression="payload.getName() + '-foo'"
|
||||
auto-create-directory="true"
|
||||
remote-directory="/Users/ozhurakousky/workspace-sts-2.3.3.M2/si/spring-integration/spring-integration-sftp/remote-target-dir/foo/bar/baz"/>
|
||||
remote-directory="spring-integration-sftp/remote-target-dir/bar/baz"/>
|
||||
|
||||
</beans>
|
||||
|
||||
@@ -19,11 +19,11 @@ import org.springframework.integration.file.remote.session.Session;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class SftpTestSessionFactory {
|
||||
|
||||
public static Session createSftpSession(com.jcraft.jsch.Session jschSession){
|
||||
public static Session createSftpSession(com.jcraft.jsch.Session jschSession) {
|
||||
SftpSession sftpSession = new SftpSession(jschSession);
|
||||
sftpSession.connect();
|
||||
return sftpSession;
|
||||
|
||||
Reference in New Issue
Block a user