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

View File

@@ -52,7 +52,7 @@ import org.springframework.integration.test.util.SocketUtils;
* @since 3.0
*/
@Configuration
public class TesFtpServer {
public class TestFtpServer {
private final int ftpPort = SocketUtils.findAvailableServerSocket();
@@ -72,7 +72,7 @@ public class TesFtpServer {
private volatile FtpServer server;
public TesFtpServer(final String root) {
public TestFtpServer(final String root) {
this.ftpFolder = new TemporaryFolder() {
@Override

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.
@@ -27,26 +27,30 @@ import static org.mockito.Mockito.when;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.file.remote.session.CachingSessionFactory;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer;
import org.springframework.integration.ftp.filters.FtpSimplePatternFileListFilter;
import org.springframework.integration.ftp.inbound.FtpInboundFileSynchronizer;
import org.springframework.integration.ftp.inbound.FtpInboundFileSynchronizingMessageSource;
import org.springframework.integration.ftp.session.DefaultFtpSessionFactory;
import org.springframework.integration.ftp.session.FtpSession;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
@@ -56,23 +60,39 @@ import org.springframework.util.ReflectionUtils.MethodCallback;
* @author Gary Russell
* @author Gunnar Hillert
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class FtpInboundChannelAdapterParserTests {
@Autowired
private SourcePollingChannelAdapter ftpInbound;
@Autowired
private SourcePollingChannelAdapter simpleAdapterWithCachedSessions;
@Autowired
private MessageChannel autoChannel;
@Autowired
@Qualifier("autoChannel.adapter")
private SourcePollingChannelAdapter autoChannelAdapter;
@Autowired
private ApplicationContext context;
@Test
public void testFtpInboundChannelAdapterComplete() throws Exception{
ApplicationContext ac =
new ClassPathXmlApplicationContext("FtpInboundChannelAdapterParserTests-context.xml", this.getClass());
SourcePollingChannelAdapter adapter = ac.getBean("ftpInbound", SourcePollingChannelAdapter.class);
assertFalse(TestUtils.getPropertyValue(adapter, "autoStartup", Boolean.class));
PriorityBlockingQueue<?> blockingQueue = TestUtils.getPropertyValue(adapter, "source.fileSource.toBeReceived", PriorityBlockingQueue.class);
assertFalse(TestUtils.getPropertyValue(ftpInbound, "autoStartup", Boolean.class));
PriorityBlockingQueue<?> blockingQueue = TestUtils.getPropertyValue(ftpInbound, "source.fileSource.toBeReceived", PriorityBlockingQueue.class);
Comparator<?> comparator = blockingQueue.comparator();
assertNotNull(comparator);
assertEquals("ftpInbound", adapter.getComponentName());
assertEquals("ftp:inbound-channel-adapter", adapter.getComponentType());
assertNotNull(TestUtils.getPropertyValue(adapter, "poller"));
assertEquals(ac.getBean("ftpChannel"), TestUtils.getPropertyValue(adapter, "outputChannel"));
assertEquals("ftpInbound", ftpInbound.getComponentName());
assertEquals("ftp:inbound-channel-adapter", ftpInbound.getComponentType());
assertNotNull(TestUtils.getPropertyValue(ftpInbound, "poller"));
assertEquals(context.getBean("ftpChannel"), TestUtils.getPropertyValue(ftpInbound, "outputChannel"));
FtpInboundFileSynchronizingMessageSource inbound =
(FtpInboundFileSynchronizingMessageSource) TestUtils.getPropertyValue(adapter, "source");
(FtpInboundFileSynchronizingMessageSource) TestUtils.getPropertyValue(ftpInbound, "source");
FtpInboundFileSynchronizer fisync =
(FtpInboundFileSynchronizer) TestUtils.getPropertyValue(inbound, "synchronizer");
@@ -86,7 +106,7 @@ public class FtpInboundChannelAdapterParserTests {
assertNotNull(filter);
Object sessionFactory = TestUtils.getPropertyValue(fisync, "remoteFileTemplate.sessionFactory");
assertTrue(DefaultFtpSessionFactory.class.isAssignableFrom(sessionFactory.getClass()));
FileListFilter<?> acceptAllFilter = ac.getBean("acceptAllFilter", FileListFilter.class);
FileListFilter<?> acceptAllFilter = context.getBean("acceptAllFilter", FileListFilter.class);
assertTrue(TestUtils.getPropertyValue(inbound, "fileSource.scanner.filter.fileFilters", Collection.class).contains(acceptAllFilter));
final AtomicReference<Method> genMethod = new AtomicReference<Method>();
ReflectionUtils.doWithMethods(AbstractInboundFileSynchronizer.class, new MethodCallback() {
@@ -104,49 +124,26 @@ public class FtpInboundChannelAdapterParserTests {
@Test
public void cachingSessionFactory() throws Exception{
ApplicationContext ac = new ClassPathXmlApplicationContext(
"FtpInboundChannelAdapterParserTests-context.xml", this.getClass());
SourcePollingChannelAdapter adapter = ac.getBean("simpleAdapterWithCachedSessions", SourcePollingChannelAdapter.class);
Object sessionFactory = TestUtils.getPropertyValue(adapter, "source.synchronizer.remoteFileTemplate.sessionFactory");
Object sessionFactory = TestUtils.getPropertyValue(simpleAdapterWithCachedSessions, "source.synchronizer.remoteFileTemplate.sessionFactory");
assertEquals(CachingSessionFactory.class, sessionFactory.getClass());
FtpInboundFileSynchronizer fisync =
TestUtils.getPropertyValue(adapter, "source.synchronizer", FtpInboundFileSynchronizer.class);
TestUtils.getPropertyValue(simpleAdapterWithCachedSessions, "source.synchronizer", FtpInboundFileSynchronizer.class);
String remoteFileSeparator = (String) TestUtils.getPropertyValue(fisync, "remoteFileSeparator");
assertNotNull(remoteFileSeparator);
assertEquals("/", remoteFileSeparator);
}
@Test
public void testFtpInboundChannelAdapterCompleteNoId() throws Exception{
ApplicationContext ac =
new ClassPathXmlApplicationContext("FtpInboundChannelAdapterParserTests-context.xml", this.getClass());
Map<String, SourcePollingChannelAdapter> spcas = ac.getBeansOfType(SourcePollingChannelAdapter.class);
SourcePollingChannelAdapter adapter = null;
for (String key : spcas.keySet()) {
if (!key.equals("ftpInbound") && !key.equals("simpleAdapter")){
adapter = spcas.get(key);
}
}
assertNotNull(adapter);
}
@Test
public void testAutoChannel() {
ApplicationContext context =
new ClassPathXmlApplicationContext("FtpInboundChannelAdapterParserTests-context.xml", this.getClass());
// Auto-created channel
MessageChannel autoChannel = context.getBean("autoChannel", MessageChannel.class);
SourcePollingChannelAdapter autoChannelAdapter = context.getBean("autoChannel.adapter", SourcePollingChannelAdapter.class);
assertSame(autoChannel, TestUtils.getPropertyValue(autoChannelAdapter, "outputChannel"));
}
public static class TestSessionFactoryBean implements FactoryBean<DefaultFtpSessionFactory> {
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public DefaultFtpSessionFactory getObject() throws Exception {
DefaultFtpSessionFactory factory = mock(DefaultFtpSessionFactory.class);
Session session = mock(Session.class);
FtpSession session = mock(FtpSession.class);
when(factory.getSession()).thenReturn(session);
return factory;
}

View File

@@ -31,6 +31,7 @@
auto-create-directory="false"
remote-file-separator=""
temporary-file-suffix=".foo"
mode="APPEND"
remote-filename-generator="fileNameGenerator"
order="23"/>

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.
@@ -20,29 +20,34 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.Iterator;
import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ApplicationContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.integration.channel.PublishSubscribeChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
import org.springframework.integration.file.remote.session.CachingSessionFactory;
import org.springframework.integration.file.support.FileExistsMode;
import org.springframework.integration.ftp.session.DefaultFtpSessionFactory;
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Oleg Zhurakousky
@@ -50,25 +55,47 @@ import org.springframework.integration.test.util.TestUtils;
* @author Gunnar Hillert
* @since 2.0
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class FtpOutboundChannelAdapterParserTests {
private static volatile int adviceCalled;
@Autowired
private EventDrivenConsumer simpleAdapter;
@Autowired
private EventDrivenConsumer advisedAdapter;
@Autowired
private EventDrivenConsumer withBeanExpressions;
@Autowired
private EventDrivenConsumer ftpOutbound;
@Autowired
private EventDrivenConsumer ftpOutbound2;
@Autowired
private EventDrivenConsumer ftpOutbound3;
@Autowired
private PublishSubscribeChannel ftpChannel;
@Autowired
private FileNameGenerator fileNameGenerator;
@Test
public void testFtpOutboundChannelAdapterComplete() throws Exception{
ApplicationContext ac =
new ClassPathXmlApplicationContext("FtpOutboundChannelAdapterParserTests-context.xml", this.getClass());
Object consumer = ac.getBean("ftpOutbound");
assertTrue(consumer instanceof EventDrivenConsumer);
PublishSubscribeChannel channel = ac.getBean("ftpChannel", PublishSubscribeChannel.class);
assertEquals(channel, TestUtils.getPropertyValue(consumer, "inputChannel"));
assertEquals("ftpOutbound", ((EventDrivenConsumer)consumer).getComponentName());
FileTransferringMessageHandler<?> handler = TestUtils.getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class);
assertEquals(ftpChannel, TestUtils.getPropertyValue(ftpOutbound, "inputChannel"));
assertEquals("ftpOutbound", ftpOutbound.getComponentName());
FileTransferringMessageHandler<?> handler = TestUtils.getPropertyValue(ftpOutbound, "handler", FileTransferringMessageHandler.class);
String remoteFileSeparator = (String) TestUtils.getPropertyValue(handler, "remoteFileTemplate.remoteFileSeparator");
assertNotNull(remoteFileSeparator);
assertEquals(".foo", TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryFileSuffix", String.class));
assertEquals("", remoteFileSeparator);
assertEquals(ac.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator"));
assertEquals(this.fileNameGenerator, TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator"));
assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset"));
assertNotNull(TestUtils.getPropertyValue(handler, "remoteFileTemplate.directoryExpressionProcessor"));
assertNotNull(TestUtils.getPropertyValue(handler, "remoteFileTemplate.temporaryDirectoryExpressionProcessor"));
@@ -82,13 +109,15 @@ public class FtpOutboundChannelAdapterParserTests {
@SuppressWarnings("unchecked")
Set<MessageHandler> handlers = (Set<MessageHandler>) TestUtils
.getPropertyValue(
TestUtils.getPropertyValue(channel, "dispatcher"),
TestUtils.getPropertyValue(ftpChannel, "dispatcher"),
"handlers");
Iterator<MessageHandler> iterator = handlers.iterator();
assertSame(TestUtils.getPropertyValue(ac.getBean("ftpOutbound2"), "handler"), iterator.next());
assertSame(TestUtils.getPropertyValue(this.ftpOutbound2, "handler"), iterator.next());
assertSame(handler, iterator.next());
assertEquals(FileExistsMode.APPEND, TestUtils.getPropertyValue(ftpOutbound, "handler.mode"));
}
@SuppressWarnings("resource")
@Test(expected=BeanCreationException.class)
public void testFailWithEmptyRfsAndAcdTrue() throws Exception{
new ClassPathXmlApplicationContext("FtpOutboundChannelAdapterParserTests-fail.xml", this.getClass());
@@ -96,40 +125,30 @@ public class FtpOutboundChannelAdapterParserTests {
@Test
public void cachingByDefault() {
ApplicationContext ac = new ClassPathXmlApplicationContext(
"FtpOutboundChannelAdapterParserTests-context.xml", this.getClass());
Object adapter = ac.getBean("simpleAdapter");
Object sfProperty = TestUtils.getPropertyValue(adapter, "handler.remoteFileTemplate.sessionFactory");
Object sfProperty = TestUtils.getPropertyValue(simpleAdapter, "handler.remoteFileTemplate.sessionFactory");
assertEquals(CachingSessionFactory.class, sfProperty.getClass());
Object innerSfProperty = TestUtils.getPropertyValue(sfProperty, "sessionFactory");
assertEquals(DefaultFtpSessionFactory.class, innerSfProperty.getClass());
assertEquals(FileExistsMode.REPLACE, TestUtils.getPropertyValue(simpleAdapter, "handler.mode"));
}
@Test
public void adviceChain() {
ApplicationContext ac = new ClassPathXmlApplicationContext(
"FtpOutboundChannelAdapterParserTests-context.xml", this.getClass());
Object adapter = ac.getBean("advisedAdapter");
MessageHandler handler = TestUtils.getPropertyValue(adapter, "handler", MessageHandler.class);
MessageHandler handler = TestUtils.getPropertyValue(advisedAdapter, "handler", MessageHandler.class);
handler.handleMessage(new GenericMessage<String>("foo"));
assertEquals(1, adviceCalled);
}
@Test
public void testTemporaryFileSuffix() {
ApplicationContext ac =
new ClassPathXmlApplicationContext("FtpOutboundChannelAdapterParserTests-context.xml", this.getClass());
FileTransferringMessageHandler<?> handler =
(FileTransferringMessageHandler<?>)TestUtils.getPropertyValue(ac.getBean("ftpOutbound3"), "handler");
assertFalse((Boolean)TestUtils.getPropertyValue(handler,"remoteFileTemplate.useTemporaryFileName"));
FileTransferringMessageHandler<?> handler =
(FileTransferringMessageHandler<?>)TestUtils.getPropertyValue(ftpOutbound3, "handler");
assertFalse((Boolean)TestUtils.getPropertyValue(handler,"remoteFileTemplate.useTemporaryFileName"));
}
@Test
public void testBeanExpressions() throws Exception{
ApplicationContext ac =
new ClassPathXmlApplicationContext("FtpOutboundChannelAdapterParserTests-context.xml", this.getClass());
Object consumer = ac.getBean("withBeanExpressions");
FileTransferringMessageHandler<?> handler = TestUtils.getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class);
FileTransferringMessageHandler<?> handler = TestUtils.getPropertyValue(withBeanExpressions, "handler", FileTransferringMessageHandler.class);
ExpressionEvaluatingMessageProcessor<?> dirExpProc = TestUtils.getPropertyValue(handler,
"remoteFileTemplate.directoryExpressionProcessor", ExpressionEvaluatingMessageProcessor.class);
assertNotNull(dirExpProc);

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.
@@ -46,6 +46,7 @@ import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ReflectionUtils;
@@ -60,6 +61,7 @@ import org.springframework.util.ReflectionUtils;
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class FtpOutboundGatewayParserTests {
@Autowired

View File

@@ -19,35 +19,43 @@ package org.springframework.integration.ftp.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.ftp.inbound.FtpInboundFileSynchronizer;
import org.springframework.integration.ftp.inbound.FtpInboundFileSynchronizingMessageSource;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Oleg Zhurakousky
* @author Gunnar Hillert
* @author Gary Russell
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class FtpsInboundChannelAdapterParserTests {
@Autowired
private SourcePollingChannelAdapter ftpInbound;
@Autowired
private MessageChannel ftpChannel;
@Test
public void testFtpsInboundChannelAdapterComplete() throws Exception{
ApplicationContext ac =
new ClassPathXmlApplicationContext("FtpsInboundChannelAdapterParserTests-context.xml", this.getClass());
SourcePollingChannelAdapter adapter = ac.getBean("ftpInbound", SourcePollingChannelAdapter.class);
assertEquals("ftpInbound", adapter.getComponentName());
assertEquals("ftp:inbound-channel-adapter", adapter.getComponentType());
assertNotNull(TestUtils.getPropertyValue(adapter, "poller"));
assertEquals(ac.getBean("ftpChannel"), TestUtils.getPropertyValue(adapter, "outputChannel"));
assertEquals("ftpInbound", ftpInbound.getComponentName());
assertEquals("ftp:inbound-channel-adapter", ftpInbound.getComponentType());
assertNotNull(TestUtils.getPropertyValue(ftpInbound, "poller"));
assertEquals(this.ftpChannel, TestUtils.getPropertyValue(ftpInbound, "outputChannel"));
FtpInboundFileSynchronizingMessageSource inbound =
(FtpInboundFileSynchronizingMessageSource) TestUtils.getPropertyValue(adapter, "source");
(FtpInboundFileSynchronizingMessageSource) TestUtils.getPropertyValue(ftpInbound, "source");
FtpInboundFileSynchronizer fisync =
(FtpInboundFileSynchronizer) TestUtils.getPropertyValue(inbound, "synchronizer");
@@ -55,19 +63,4 @@ public class FtpsInboundChannelAdapterParserTests {
}
@Test
public void testFtpsInboundChannelAdapterCompleteNoId() throws Exception{
ApplicationContext ac =
new ClassPathXmlApplicationContext("FtpsInboundChannelAdapterParserTests-context.xml", this.getClass());
Map<String, SourcePollingChannelAdapter> spcas = ac.getBeansOfType(SourcePollingChannelAdapter.class);
SourcePollingChannelAdapter adapter = null;
for (String key : spcas.keySet()) {
if (!key.equals("ftpInbound")){
adapter = spcas.get(key);
}
}
assertNotNull(adapter);
}
}

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.
@@ -20,13 +20,18 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
import org.springframework.integration.ftp.session.DefaultFtpsSessionFactory;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Oleg Zhurakousky
@@ -34,17 +39,27 @@ import org.springframework.integration.test.util.TestUtils;
* @author Gary Russell
* @since 2.0
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class FtpsOutboundChannelAdapterParserTests {
@Autowired
private EventDrivenConsumer ftpOutbound;
@Autowired
private MessageChannel ftpChannel;
@Autowired
private FileNameGenerator fileNameGenerator;
@Test
public void testFtpsOutboundChannelAdapterComplete() throws Exception{
ApplicationContext ac = new ClassPathXmlApplicationContext("FtpsOutboundChannelAdapterParserTests-context.xml", this.getClass());
Object consumer = ac.getBean("ftpOutbound");
assertTrue(consumer instanceof EventDrivenConsumer);
assertEquals(ac.getBean("ftpChannel"), TestUtils.getPropertyValue(consumer, "inputChannel"));
assertEquals("ftpOutbound", ((EventDrivenConsumer)consumer).getComponentName());
FileTransferringMessageHandler<?> handler = TestUtils.getPropertyValue(consumer, "handler", FileTransferringMessageHandler.class);
assertEquals(ac.getBean("fileNameGenerator"), TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator"));
assertTrue(ftpOutbound instanceof EventDrivenConsumer);
assertEquals(this.ftpChannel, TestUtils.getPropertyValue(ftpOutbound, "inputChannel"));
assertEquals("ftpOutbound", ftpOutbound.getComponentName());
FileTransferringMessageHandler<?> handler = TestUtils.getPropertyValue(ftpOutbound, "handler", FileTransferringMessageHandler.class);
assertEquals(this.fileNameGenerator, TestUtils.getPropertyValue(handler, "remoteFileTemplate.fileNameGenerator"));
assertEquals("UTF-8", TestUtils.getPropertyValue(handler, "remoteFileTemplate.charset"));
DefaultFtpsSessionFactory sf = TestUtils.getPropertyValue(handler, "remoteFileTemplate.sessionFactory", DefaultFtpsSessionFactory.class);
assertEquals("localhost", TestUtils.getPropertyValue(sf, "host"));

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.
@@ -48,20 +48,20 @@ import org.mockito.stubbing.Answer;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.file.remote.FileInfo;
import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
import org.springframework.integration.ftp.session.AbstractFtpSessionFactory;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.util.FileCopyUtils;
/**
@@ -207,17 +207,20 @@ public class FtpOutboundTests {
File destFile = new File(targetDir, srcFile.getName());
destFile.deleteOnExit();
ApplicationContext context = new ClassPathXmlApplicationContext("FtpOutboundInsideChainTests-context.xml", getClass());
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
"FtpOutboundInsideChainTests-context.xml", getClass());
MessageChannel channel = context.getBean("outboundChainChannel", MessageChannel.class);
channel.send(new GenericMessage<File>(srcFile));
assertTrue("destination file was not created", destFile.exists());
context.close();
}
@Test //INT-2275
public void testFtpOutboundGatewayInsideChain() throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("FtpOutboundInsideChainTests-context.xml", getClass());
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(
"FtpOutboundInsideChainTests-context.xml", getClass());
MessageChannel channel = context.getBean("ftpOutboundGatewayInsideChain", MessageChannel.class);
@@ -235,6 +238,7 @@ public class FtpOutboundTests {
for (FileInfo<?> remoteFile : remoteFiles) {
assertTrue(files.contains(remoteFile.getFilename()));
}
context.close();
}

View File

@@ -8,7 +8,7 @@
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="ftpServer" class="org.springframework.integration.ftp.TesFtpServer">
<bean id="ftpServer" class="org.springframework.integration.ftp.TestFtpServer">
<constructor-arg value="FtpServerOutboundTests"/>
</bean>
@@ -104,4 +104,35 @@
remote-directory="ftpTarget"
reply-channel="output"/>
<int:channel id="appending" />
<int-ftp:outbound-channel-adapter id="appender"
session-factory="ftpSessionFactory"
channel="appending"
mode="APPEND"
use-temporary-file-name="false"
remote-directory="ftpTarget"
auto-create-directory="true"
remote-file-separator="/" />
<int:channel id="ignoring" />
<int-ftp:outbound-channel-adapter id="ignore"
session-factory="ftpSessionFactory"
channel="ignoring"
mode="IGNORE"
remote-directory="ftpTarget"
auto-create-directory="true"
remote-file-separator="/" />
<int:channel id="failing" />
<int-ftp:outbound-channel-adapter id="fail"
session-factory="ftpSessionFactory"
channel="failing"
mode="FAIL"
remote-directory="ftpTarget"
auto-create-directory="true"
remote-file-separator="/" />
</beans>

View File

@@ -17,6 +17,7 @@
package org.springframework.integration.ftp.outbound;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
@@ -42,12 +43,17 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.file.FileHeaders;
import org.springframework.integration.file.remote.InputStreamCallback;
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.integration.ftp.TesFtpServer;
import org.springframework.integration.ftp.TestFtpServer;
import org.springframework.integration.ftp.session.DefaultFtpSessionFactory;
import org.springframework.integration.ftp.session.FtpRemoteFileTemplate;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.context.ContextConfiguration;
@@ -65,10 +71,10 @@ import org.springframework.util.FileCopyUtils;
public class FtpServerOutboundTests {
@Autowired
private TesFtpServer ftpServer;
private TestFtpServer ftpServer;
@Autowired
private SessionFactory<FTPFile> ftpSessionFactory;
private DefaultFtpSessionFactory ftpSessionFactory;
@Autowired
private PollableChannel output;
@@ -97,6 +103,15 @@ public class FtpServerOutboundTests {
@Autowired
private DirectChannel inboundMPutRecursiveFiltered;
@Autowired
private DirectChannel appending;
@Autowired
private DirectChannel ignoring;
@Autowired
private DirectChannel failing;
@Before
public void setup() {
this.ftpServer.recursiveDelete(ftpServer.getTargetLocalDirectory());
@@ -304,4 +319,39 @@ public class FtpServerOutboundTests {
equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
}
@Test
public void testInt3412FileMode() {
Message<String> m = MessageBuilder.withPayload("foo")
.setHeader(FileHeaders.FILENAME, "appending.txt")
.build();
appending.send(m);
appending.send(m);
FtpRemoteFileTemplate template = new FtpRemoteFileTemplate(ftpSessionFactory);
assertLength6(template);
ignoring.send(m);
assertLength6(template);
try {
failing.send(m);
fail("Expected exception");
}
catch (MessagingException e) {
assertThat(e.getCause().getCause().getMessage(), containsString("The destination file already exists"));
}
}
private void assertLength6(FtpRemoteFileTemplate template) {
FTPFile[] files = template.execute(new SessionCallback<FTPFile, FTPFile[]>() {
@Override
public FTPFile[] doInSession(Session<FTPFile> session) throws IOException {
return session.list("ftpTarget/appending.txt");
}
});
assertEquals(1, files.length);
assertEquals(6, files[0].getSize());
}
}

View File

@@ -0,0 +1,15 @@
<?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-ftp="http://www.springframework.org/schema/integration/ftp"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/integration/ftp
http://www.springframework.org/schema/integration/ftp/spring-integration-ftp.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="ftpServer" class="org.springframework.integration.ftp.TestFtpServer">
<constructor-arg value="FtpRemoteFileTemplateTests"/>
</bean>
</beans>

View File

@@ -0,0 +1,112 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.remote.ClientCallbackWithoutResult;
import org.springframework.integration.file.remote.SessionCallback;
import org.springframework.integration.file.remote.SessionCallbackWithoutResult;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.ftp.TestFtpServer;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Gary Russell
* @since 4.1
*
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class FtpRemoteFileTemplateTests {
@Autowired
private TestFtpServer ftpServer;
@Autowired
private DefaultFtpSessionFactory sessionFactory;
@Before
@After
public void setup() {
this.ftpServer.recursiveDelete(ftpServer.getTargetLocalDirectory());
this.ftpServer.recursiveDelete(ftpServer.getTargetFtpDirectory());
}
@Test
public void testINT3412AppendStatRmdir() {
FtpRemoteFileTemplate template = new FtpRemoteFileTemplate(sessionFactory);
DefaultFileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
fileNameGenerator.setExpression("'foobar.txt'");
template.setFileNameGenerator(fileNameGenerator);
template.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
template.setUseTemporaryFileName(false);
template.execute(new SessionCallback<FTPFile, Boolean>() {
@Override
public Boolean doInSession(Session<FTPFile> session) throws IOException {
session.mkdir("foo/");
return session.mkdir("foo/bar/");
}
});
template.append(new GenericMessage<String>("foo"));
template.append(new GenericMessage<String>("bar"));
assertTrue(template.exists("foo/foobar.txt"));
template.executeWithClient(new ClientCallbackWithoutResult<FTPClient>() {
@Override
public void doWithClientWithoutResult(FTPClient client) {
try {
FTPFile[] files = client.listFiles("foo/foobar.txt");
assertEquals(6, files[0].getSize());
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
});
template.execute(new SessionCallbackWithoutResult<FTPFile>() {
@Override
public void doInSessionWithoutResult(Session<FTPFile> session) throws IOException {
assertTrue(session.remove("foo/foobar.txt"));
assertTrue(session.rmdir("foo/bar/"));
FTPFile[] files = session.list("foo/");
assertEquals(0, files.length);
assertTrue(session.rmdir("foo/"));
}
});
assertFalse(template.exists("foo"));
}
}