INT-3412 (S)FTP Append, rmdir, Client Access

JIRA: https://jira.spring.io/browse/INT-3412

Initial commit - review only.

TODO:
- SFTP Tests
- Namespace/Adapter support for file append
- Docs

INT-3412 Polishing

- Addressed PR comments
- Completed SFTP implementation
- Added namespace/parser support for `FileExistsMode` (append, etc)
- Added SFTP Tests
- Created Embedded SFTP server for tests (similar to FTP)
- Converted tests that needed a real server to use the embedded server

INT-3412 Docs and Polish (PR Comments)
This commit is contained in:
Gary Russell
2014-08-04 15:07:50 +03:00
committed by Artem Bilan
parent bc3db5d4a9
commit 403c91801d
51 changed files with 1682 additions and 807 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors
* Copyright 2002-2014 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.
@@ -17,7 +17,6 @@
package org.springframework.integration.ftp.config;
import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler;
import org.springframework.integration.file.config.RemoteFileOutboundChannelAdapterParser;
/**
* Provides namespace support for using FTP
@@ -26,13 +25,15 @@ import org.springframework.integration.file.config.RemoteFileOutboundChannelAdap
*
* @author Josh Long
* @author Oleg Zhurakousky
* @author Gary Russell
* @since 2.0
*/
public class FtpNamespaceHandler extends AbstractIntegrationNamespaceHandler {
@Override
public void init() {
registerBeanDefinitionParser("inbound-channel-adapter", new FtpInboundChannelAdapterParser());
registerBeanDefinitionParser("outbound-channel-adapter", new RemoteFileOutboundChannelAdapterParser());
registerBeanDefinitionParser("outbound-channel-adapter", new FtpOutboundChannelAdapterParser());
registerBeanDefinitionParser("outbound-gateway", new FtpOutboundGatewayParser());
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.ftp.config;
import org.springframework.integration.file.config.RemoteFileOutboundChannelAdapterParser;
import org.springframework.integration.file.remote.RemoteFileOperations;
import org.springframework.integration.ftp.session.FtpRemoteFileTemplate;
/**
* Parser for FTP Outbound Channel Adapters.
*
* @author Gary Russell
* @since 4.1
*
*/
public class FtpOutboundChannelAdapterParser extends RemoteFileOutboundChannelAdapterParser {
@Override
protected Class<? extends RemoteFileOperations<?>> getTemplateClass() {
return FtpRemoteFileTemplate.class;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@@ -16,9 +16,11 @@
package org.springframework.integration.ftp.config;
import org.springframework.integration.file.config.AbstractRemoteFileOutboundGatewayParser;
import org.springframework.integration.file.remote.RemoteFileOperations;
import org.springframework.integration.ftp.filters.FtpRegexPatternFileListFilter;
import org.springframework.integration.ftp.filters.FtpSimplePatternFileListFilter;
import org.springframework.integration.ftp.gateway.FtpOutboundGateway;
import org.springframework.integration.ftp.session.FtpRemoteFileTemplate;
/**
* @author Gary Russell
@@ -28,6 +30,7 @@ import org.springframework.integration.ftp.gateway.FtpOutboundGateway;
*/
public class FtpOutboundGatewayParser extends AbstractRemoteFileOutboundGatewayParser {
@Override
public String getGatewayClassName() {
return FtpOutboundGateway.class.getName();
}
@@ -42,4 +45,9 @@ public class FtpOutboundGatewayParser extends AbstractRemoteFileOutboundGatewayP
return FtpRegexPatternFileListFilter.class.getName();
}
@Override
protected Class<? extends RemoteFileOperations<?>> getTemplateClass() {
return FtpRemoteFileTemplate.class;
}
}

View File

@@ -26,7 +26,6 @@ import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
@@ -164,7 +163,7 @@ public abstract class AbstractFtpSessionFactory<T extends FTPClient> implements
}
@Override
public Session<FTPFile> getSession() {
public FtpSession getSession() {
try {
return new FtpSession(this.createClient());
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.ftp.session;
import java.io.IOException;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.integration.file.remote.ClientCallback;
import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.file.remote.SessionCallback;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.messaging.MessagingException;
/**
* FTP version of {@code RemoteFileTemplate} providing type-safe access to
* the underlying FTPClient object.
*
* @author Gary Russell
* @since 4.1
*
*/
public class FtpRemoteFileTemplate extends RemoteFileTemplate<FTPFile> {
public FtpRemoteFileTemplate(SessionFactory<FTPFile> sessionFactory) {
super(sessionFactory);
}
@SuppressWarnings("unchecked")
@Override
public <T, C> T executeWithClient(final ClientCallback<C, T> callback) {
return doExecuteWithClient((ClientCallback<FTPClient, T>) callback);
}
protected <T> T doExecuteWithClient(final ClientCallback<FTPClient, T> callback) {
return execute(new SessionCallback<FTPFile, T>() {
@Override
public T doInSession(Session<FTPFile> session) throws IOException {
return callback.doWithClient((FTPClient) session.getClientInstance());
}
});
}
@Override
public boolean exists(final String path) {
return executeWithClient(new ClientCallback<FTPClient, Boolean>() {
@Override
public Boolean doWithClient(FTPClient client) {
try {
return client.getStatus(path) != null;
}
catch (IOException e) {
throw new MessagingException("Failed to stat " + path, e);
}
}
});
}
}

View File

@@ -116,7 +116,7 @@ public class FtpSession implements Session<FTPFile> {
@Override
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");
Assert.hasText(path, "path must not be null or empty");
boolean completed = this.client.storeFile(path, inputStream);
if (!completed) {
throw new IOException("Failed to write to '" + path
@@ -127,6 +127,20 @@ public class FtpSession implements Session<FTPFile> {
}
}
@Override
public void append(InputStream inputStream, String path) throws IOException {
Assert.notNull(inputStream, "inputStream must not be null");
Assert.hasText(path, "path must not be null or empty");
boolean completed = this.client.appendFile(path, inputStream);
if (!completed) {
throw new IOException("Failed to append to '" + path
+ "'. Server replied with: " + this.client.getReplyString());
}
if (logger.isInfoEnabled()) {
logger.info("File has been successfully appended to: " + path);
}
}
@Override
public void close() {
try {
@@ -168,6 +182,12 @@ public class FtpSession implements Session<FTPFile> {
return this.client.makeDirectory(remoteDirectory);
}
@Override
public boolean rmdir(String directory) throws IOException {
return this.client.removeDirectory(directory);
}
@Override
public boolean exists(String path) throws IOException{
Assert.hasText(path, "'path' must not be empty");
@@ -187,4 +207,10 @@ public class FtpSession implements Session<FTPFile> {
return exists;
}
@Override
public FTPClient getClientInstance() {
return this.client;
}
}