org.springframework.integration.ftp.outbound.FtpServerOutboundTests.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.ftp.outbound.FtpServerOutboundTests.java

Source

/*
 * Copyright 2013-2017 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.outbound;

import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.isOneOf;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;

import org.apache.commons.io.FileUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.file.FileHeaders;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.file.remote.InputStreamCallback;
import org.springframework.integration.file.remote.MessageSessionCallback;
import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.ftp.FtpTestSupport;
import org.springframework.integration.ftp.session.FtpRemoteFileTemplate;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.support.PartialSuccessException;
import org.springframework.integration.test.util.TestUtils;
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.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.FileCopyUtils;

/**
 * @author Artem Bilan
 * @author Gary Russell
 *
 * @since 3.0
 */
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class FtpServerOutboundTests extends FtpTestSupport {

    @Autowired
    private SessionFactory<FTPFile> ftpSessionFactory;

    @Autowired
    private PollableChannel output;

    @Autowired
    private DirectChannel inboundGet;

    @Autowired
    private DirectChannel invalidDirExpression;

    @Autowired
    private DirectChannel inboundMGet;

    @Autowired
    private DirectChannel inboundMGetRecursive;

    @Autowired
    private DirectChannel inboundMGetRecursiveFiltered;

    @Autowired
    private DirectChannel inboundMPut;

    @Autowired
    private DirectChannel inboundMPutRecursive;

    @Autowired
    private DirectChannel inboundMPutRecursiveFiltered;

    @Autowired
    private DirectChannel appending;

    @Autowired
    private DirectChannel ignoring;

    @Autowired
    private DirectChannel failing;

    @Autowired
    private DirectChannel inboundGetStream;

    @Autowired
    private DirectChannel inboundCallback;

    @Autowired
    private DirectChannel inboundLs;

    @Autowired
    private SourcePollingChannelAdapter ftpInbound;

    @Autowired
    private Config config;

    @Before
    public void setup() {
        this.config.targetLocalDirectoryName = getTargetLocalDirectoryName();
    }

    @Test
    public void testInt2866LocalDirectoryExpressionGET() {
        String dir = "ftpSource/";
        long modified = setModifiedOnSource1();
        this.inboundGet.send(new GenericMessage<Object>(dir + " ftpSource1.txt"));
        Message<?> result = this.output.receive(1000);
        assertNotNull(result);
        File localFile = (File) result.getPayload();
        assertThat(localFile.getPath().replaceAll(Matcher.quoteReplacement(File.separator), "/"),
                containsString(dir.toUpperCase()));
        assertPreserved(modified, localFile);

        dir = "ftpSource/subFtpSource/";
        this.inboundGet.send(new GenericMessage<Object>(dir + "subFtpSource1.txt"));
        result = this.output.receive(1000);
        assertNotNull(result);
        localFile = (File) result.getPayload();
        assertThat(localFile.getPath().replaceAll(Matcher.quoteReplacement(File.separator), "/"),
                containsString(dir.toUpperCase()));
    }

    @Test
    public void testInt2866InvalidLocalDirectoryExpression() {
        try {
            this.invalidDirExpression.send(new GenericMessage<Object>("/ftpSource/ ftpSource1.txt"));
            fail("Exception expected.");
        } catch (Exception e) {
            Throwable cause = e.getCause();
            assertNotNull(cause);
            cause = cause.getCause();
            assertThat(cause, Matchers.instanceOf(IllegalArgumentException.class));
            assertThat(cause.getMessage(), Matchers.startsWith("Failed to make local directory"));
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testInt2866LocalDirectoryExpressionMGET() {
        String dir = "ftpSource/";
        long modified = setModifiedOnSource1();
        this.inboundMGet.send(new GenericMessage<Object>(dir + "*.txt"));
        Message<?> result = this.output.receive(1000);
        assertNotNull(result);
        List<File> localFiles = (List<File>) result.getPayload();

        assertThat(localFiles.size(), Matchers.greaterThan(0));

        boolean assertedModified = false;
        for (File file : localFiles) {
            assertThat(file.getPath().replaceAll(Matcher.quoteReplacement(File.separator), "/"),
                    containsString(dir));
            if (file.getPath().contains("localTarget1")) {
                assertedModified = assertPreserved(modified, file);
            }
        }
        assertTrue(assertedModified);

        dir = "ftpSource/subFtpSource/";
        this.inboundMGet.send(new GenericMessage<Object>(dir + "*.txt"));
        result = this.output.receive(1000);
        assertNotNull(result);
        localFiles = (List<File>) result.getPayload();

        assertThat(localFiles.size(), Matchers.greaterThan(0));

        for (File file : localFiles) {
            assertThat(file.getPath().replaceAll(Matcher.quoteReplacement(File.separator), "/"),
                    containsString(dir));
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testMGETOnNullDir() throws IOException {
        Session<FTPFile> session = ftpSessionFactory.getSession();
        ((FTPClient) session.getClientInstance()).changeWorkingDirectory("ftpSource");
        session.close();

        this.inboundMGet.send(new GenericMessage<Object>(""));
        Message<?> result = this.output.receive(1000);
        assertNotNull(result);
        List<File> localFiles = (List<File>) result.getPayload();

        assertThat(localFiles.size(), Matchers.greaterThan(0));

        for (File file : localFiles) {
            assertThat(file.getName(), isOneOf(" localTarget1.txt", "localTarget2.txt"));
            assertThat(file.getName(), not(containsString("null")));
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testInt3172LocalDirectoryExpressionMGETRecursive() throws IOException {
        String dir = "ftpSource/";
        long modified = setModifiedOnSource1();
        File secondRemote = new File(getSourceRemoteDirectory(), "ftpSource2.txt");
        secondRemote.setLastModified(System.currentTimeMillis() - 1_000_000);
        this.inboundMGetRecursive.send(new GenericMessage<Object>("*"));
        Message<?> result = this.output.receive(1000);
        assertNotNull(result);
        List<File> localFiles = (List<File>) result.getPayload();
        assertEquals(3, localFiles.size());

        boolean assertedModified = false;
        for (File file : localFiles) {
            assertThat(file.getPath().replaceAll(Matcher.quoteReplacement(File.separator), "/"),
                    containsString(dir));
            if (file.getPath().contains("localTarget1")) {
                assertedModified = assertPreserved(modified, file);
            }
        }
        assertTrue(assertedModified);
        assertThat(localFiles.get(2).getPath().replaceAll(Matcher.quoteReplacement(File.separator), "/"),
                containsString(dir + "subFtpSource"));

        File secondTarget = new File(getTargetLocalDirectory() + File.separator + "ftpSource", "localTarget2.txt");
        ByteArrayOutputStream remoteContents = new ByteArrayOutputStream();
        ByteArrayOutputStream localContents = new ByteArrayOutputStream();
        FileUtils.copyFile(secondRemote, remoteContents);
        FileUtils.copyFile(secondTarget, localContents);
        String localAsString = new String(localContents.toByteArray());
        assertEquals(new String(remoteContents.toByteArray()), localAsString);
        long oldLastModified = secondRemote.lastModified();
        FileUtils.copyInputStreamToFile(new ByteArrayInputStream("junk".getBytes()), secondRemote);
        long newLastModified = secondRemote.lastModified();
        secondRemote.setLastModified(oldLastModified);
        this.inboundMGetRecursive.send(new GenericMessage<Object>("*"));
        this.output.receive(0);
        localContents = new ByteArrayOutputStream();
        FileUtils.copyFile(secondTarget, localContents);
        assertEquals(localAsString, new String(localContents.toByteArray()));
        secondRemote.setLastModified(newLastModified);
        this.inboundMGetRecursive.send(new GenericMessage<Object>("*"));
        this.output.receive(0);
        localContents = new ByteArrayOutputStream();
        FileUtils.copyFile(secondTarget, localContents);
        assertEquals("junk", new String(localContents.toByteArray()));
        // restore the remote file contents
        FileUtils.copyInputStreamToFile(new ByteArrayInputStream(localAsString.getBytes()), secondRemote);
    }

    private long setModifiedOnSource1() {
        File firstRemote = new File(getSourceRemoteDirectory(), " ftpSource1.txt");
        firstRemote.setLastModified(System.currentTimeMillis() - 1_000_000);
        long modified = firstRemote.lastModified();
        assertTrue(modified > 0);
        return modified;
    }

    private boolean assertPreserved(long modified, File file) {
        // ftp only has 1 minute resolution
        assertTrue("lastModified wrong by " + (modified - file.lastModified()),
                Math.abs(file.lastModified() - modified) < 61_000);
        return true;
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testInt3172LocalDirectoryExpressionMGETRecursiveFiltered() {
        String dir = "ftpSource/";
        this.inboundMGetRecursiveFiltered.send(new GenericMessage<Object>(dir + "*"));
        Message<?> result = this.output.receive(1000);
        assertNotNull(result);
        List<File> localFiles = (List<File>) result.getPayload();
        // should have filtered ftpSource2.txt
        assertEquals(2, localFiles.size());

        for (File file : localFiles) {
            assertThat(file.getPath().replaceAll(Matcher.quoteReplacement(File.separator), "/"),
                    containsString(dir));
        }
        assertThat(localFiles.get(1).getPath().replaceAll(Matcher.quoteReplacement(File.separator), "/"),
                containsString(dir + "subFtpSource"));

    }

    @Test
    public void testInt3100RawGET() throws Exception {
        Session<?> session = this.ftpSessionFactory.getSession();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        FileCopyUtils.copy(session.readRaw("ftpSource/ ftpSource1.txt"), baos);
        assertTrue(session.finalizeRaw());
        assertEquals("source1", new String(baos.toByteArray()));

        baos = new ByteArrayOutputStream();
        FileCopyUtils.copy(session.readRaw("ftpSource/ftpSource2.txt"), baos);
        assertTrue(session.finalizeRaw());
        assertEquals("source2", new String(baos.toByteArray()));

        session.close();
    }

    @Test
    public void testRawGETWithTemplate() throws Exception {
        RemoteFileTemplate<FTPFile> template = new RemoteFileTemplate<FTPFile>(this.ftpSessionFactory);
        template.setFileNameExpression(new SpelExpressionParser().parseExpression("payload"));
        template.setBeanFactory(mock(BeanFactory.class));
        template.afterPropertiesSet();
        final ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
        assertTrue(template.get(new GenericMessage<String>("ftpSource/ ftpSource1.txt"),
                (InputStreamCallback) stream -> FileCopyUtils.copy(stream, baos1)));
        assertEquals("source1", new String(baos1.toByteArray()));

        final ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
        assertTrue(template.get(new GenericMessage<String>("ftpSource/ftpSource2.txt"),
                (InputStreamCallback) stream -> FileCopyUtils.copy(stream, baos2)));
        assertEquals("source2", new String(baos2.toByteArray()));
    }

    @Test
    public void testInt3088MPutNotRecursive() {
        this.inboundMPut.send(new GenericMessage<File>(getSourceLocalDirectory()));
        @SuppressWarnings("unchecked")
        Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
        assertNotNull(out);
        assertEquals(2, out.getPayload().size());
        assertThat(out.getPayload().get(0), not(equalTo(out.getPayload().get(1))));
        assertThat(out.getPayload().get(0),
                anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt")));
        assertThat(out.getPayload().get(1),
                anyOf(equalTo("ftpTarget/localSource1.txt"), equalTo("ftpTarget/localSource2.txt")));
    }

    @Test
    public void testInt3088MPutRecursive() {
        this.inboundMPutRecursive.send(new GenericMessage<File>(getSourceLocalDirectory()));
        @SuppressWarnings("unchecked")
        Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
        assertNotNull(out);
        assertEquals(3, out.getPayload().size());
        assertThat(out.getPayload().get(0), not(equalTo(out.getPayload().get(1))));
        assertThat(out.getPayload().get(0), anyOf(equalTo("ftpTarget/localSource1.txt"),
                equalTo("ftpTarget/localSource2.txt"), equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
        assertThat(out.getPayload().get(1), anyOf(equalTo("ftpTarget/localSource1.txt"),
                equalTo("ftpTarget/localSource2.txt"), equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
        assertThat(out.getPayload().get(2), anyOf(equalTo("ftpTarget/localSource1.txt"),
                equalTo("ftpTarget/localSource2.txt"), equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
    }

    @Test
    public void testInt3088MPutRecursiveFiltered() {
        this.inboundMPutRecursiveFiltered.send(new GenericMessage<File>(getSourceLocalDirectory()));
        @SuppressWarnings("unchecked")
        Message<List<String>> out = (Message<List<String>>) this.output.receive(1000);
        assertNotNull(out);
        assertEquals(2, out.getPayload().size());
        assertThat(out.getPayload().get(0), not(equalTo(out.getPayload().get(1))));
        assertThat(out.getPayload().get(0), anyOf(equalTo("ftpTarget/localSource1.txt"),
                equalTo("ftpTarget/localSource2.txt"), equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
        assertThat(out.getPayload().get(1), anyOf(equalTo("ftpTarget/localSource1.txt"),
                equalTo("ftpTarget/localSource2.txt"), equalTo("ftpTarget/subLocalSource/subLocalSource1.txt")));
    }

    @Test
    public void testInt3412FileMode() {
        FtpRemoteFileTemplate template = new FtpRemoteFileTemplate(ftpSessionFactory);
        assertFalse(template.exists("ftpTarget/appending.txt"));
        Message<String> m = MessageBuilder.withPayload("foo").setHeader(FileHeaders.FILENAME, "appending.txt")
                .build();
        appending.send(m);
        appending.send(m);

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

    }

    @Test
    public void testStream() {
        String dir = "ftpSource/";
        this.inboundGetStream.send(new GenericMessage<Object>(dir + " ftpSource1.txt"));
        Message<?> result = this.output.receive(1000);
        assertNotNull(result);
        assertEquals("source1", result.getPayload());
        assertEquals("ftpSource/", result.getHeaders().get(FileHeaders.REMOTE_DIRECTORY));
        assertEquals(" ftpSource1.txt", result.getHeaders().get(FileHeaders.REMOTE_FILE));

        Session<?> session = (Session<?>) result.getHeaders()
                .get(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE);
        // Returned to cache
        assertTrue(session.isOpen());
        // Raw reading is finished
        assertFalse(TestUtils.getPropertyValue(session, "targetSession.readingRaw", AtomicBoolean.class).get());

        // Check that we can use the same session from cache to read another remote InputStream
        this.inboundGetStream.send(new GenericMessage<Object>(dir + "ftpSource2.txt"));
        result = this.output.receive(1000);
        assertNotNull(result);
        assertEquals("source2", result.getPayload());
        assertEquals("ftpSource/", result.getHeaders().get(FileHeaders.REMOTE_DIRECTORY));
        assertEquals("ftpSource2.txt", result.getHeaders().get(FileHeaders.REMOTE_FILE));
        assertSame(TestUtils.getPropertyValue(session, "targetSession"), TestUtils.getPropertyValue(
                result.getHeaders().get(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE), "targetSession"));
    }

    @Test
    public void testMgetPartial() throws Exception {
        Session<FTPFile> session = spyOnSession();
        doAnswer(invocation -> {
            FTPFile[] files = (FTPFile[]) invocation.callRealMethod();
            // add an extra file where the get will fail
            files = Arrays.copyOf(files, files.length + 1);
            FTPFile bogusFile = new FTPFile();
            bogusFile.setName("bogus.txt");
            files[files.length - 1] = bogusFile;
            return files;
        }).when(session).list("ftpSource/subFtpSource/*");
        String dir = "ftpSource/subFtpSource/";
        try {
            this.inboundMGet.send(new GenericMessage<Object>(dir + "*"));
            fail("expected exception");
        } catch (PartialSuccessException e) {
            assertEquals(2, e.getDerivedInput().size());
            assertEquals(1, e.getPartialResults().size());
            assertThat(e.getCause().getMessage(),
                    containsString("/ftpSource/subFtpSource/bogus.txt: No such file or directory."));
        }

    }

    @Test
    public void testMgetRecursivePartial() throws Exception {
        Session<FTPFile> session = spyOnSession();
        doAnswer(invocation -> {
            FTPFile[] files = (FTPFile[]) invocation.callRealMethod();
            // add an extra file where the get will fail
            files = Arrays.copyOf(files, files.length + 1);
            FTPFile bogusFile = new FTPFile();
            bogusFile.setName("bogus.txt");
            bogusFile.setTimestamp(Calendar.getInstance());
            files[files.length - 1] = bogusFile;
            return files;
        }).when(session).list("ftpSource/subFtpSource/");
        String dir = "ftpSource/";
        try {
            this.inboundMGetRecursive.send(new GenericMessage<Object>(dir + "*"));
            fail("expected exception");
        } catch (PartialSuccessException e) {
            assertEquals(4, e.getDerivedInput().size());
            assertEquals(2, e.getPartialResults().size());
            assertThat(e.getCause().getMessage(),
                    containsString("/ftpSource/subFtpSource/bogus.txt: No such file or directory."));
        }
    }

    @Test
    public void testMputPartial() throws Exception {
        Session<FTPFile> session = spyOnSession();
        doAnswer(invocation -> {
            throw new IOException("Failed to send localSource2");
        }).when(session).write(Mockito.any(InputStream.class), Mockito.contains("localSource2"));
        try {
            this.inboundMPut.send(new GenericMessage<File>(getSourceLocalDirectory()));
            fail("expected exception");
        } catch (PartialSuccessException e) {
            assertEquals(3, e.getDerivedInput().size());
            assertEquals(1, e.getPartialResults().size());
            assertEquals("ftpTarget/localSource1.txt", e.getPartialResults().iterator().next());
            assertThat(e.getCause().getMessage(), containsString("Failed to send localSource2"));
        }
    }

    @Test
    public void testMputRecursivePartial() throws Exception {
        Session<FTPFile> session = spyOnSession();
        File sourceLocalSubDirectory = new File(getSourceLocalDirectory(), "subLocalSource");
        assertTrue(sourceLocalSubDirectory.isDirectory());
        File extra = new File(sourceLocalSubDirectory, "subLocalSource2.txt");
        FileOutputStream writer = new FileOutputStream(extra);
        writer.write("foo".getBytes());
        writer.close();
        doAnswer(invocation -> {
            throw new IOException("Failed to send subLocalSource2");
        }).when(session).write(Mockito.any(InputStream.class), Mockito.contains("subLocalSource2"));
        try {
            this.inboundMPutRecursive.send(new GenericMessage<File>(getSourceLocalDirectory()));
            fail("expected exception");
        } catch (PartialSuccessException e) {
            assertEquals(3, e.getDerivedInput().size());
            assertEquals(2, e.getPartialResults().size());
            assertThat(e.getCause(), Matchers.instanceOf(PartialSuccessException.class));
            PartialSuccessException cause = (PartialSuccessException) e.getCause();
            assertEquals(2, cause.getDerivedInput().size());
            assertEquals(1, cause.getPartialResults().size());
            assertThat(cause.getCause().getMessage(), containsString("Failed to send subLocalSource2"));
        }
        extra.delete();
    }

    private Session<FTPFile> spyOnSession() {
        Session<FTPFile> session = spy(this.ftpSessionFactory.getSession());
        session.close();
        @SuppressWarnings("unchecked")
        BlockingQueue<Session<FTPFile>> cache = TestUtils.getPropertyValue(ftpSessionFactory, "pool.available",
                BlockingQueue.class);
        assertNotNull(cache.poll());
        cache.offer(session);
        @SuppressWarnings("unchecked")
        Set<Session<FTPFile>> allocated = TestUtils.getPropertyValue(ftpSessionFactory, "pool.allocated",
                Set.class);
        allocated.clear();
        allocated.add(session);
        return session;
    }

    private void assertLength6(FtpRemoteFileTemplate template) {
        FTPFile[] files = template.execute(session -> session.list("ftpTarget/appending.txt"));
        assertEquals(1, files.length);
        assertEquals(6, files[0].getSize());
    }

    @Test
    public void testMessageSessionCallback() {
        this.inboundCallback.send(new GenericMessage<String>("foo"));
        Message<?> receive = this.output.receive(10000);
        assertNotNull(receive);
        assertEquals("FOO", receive.getPayload());
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testLsForNullDir() throws IOException {
        Session<FTPFile> session = ftpSessionFactory.getSession();
        ((FTPClient) session.getClientInstance()).changeWorkingDirectory("ftpSource");
        session.close();

        this.inboundLs.send(new GenericMessage<String>("foo"));
        Message<?> receive = this.output.receive(10000);
        assertNotNull(receive);
        assertThat(receive.getPayload(), instanceOf(List.class));
        List<String> files = (List<String>) receive.getPayload();
        assertEquals(2, files.size());
        assertThat(files, containsInAnyOrder(" ftpSource1.txt", "ftpSource2.txt"));

        FTPFile[] ftpFiles = ftpSessionFactory.getSession().list(null);
        for (FTPFile ftpFile : ftpFiles) {
            if (!ftpFile.isDirectory()) {
                assertTrue(files.contains(ftpFile.getName()));
            }
        }
    }

    @Test
    public void testInboundChannelAdapterWithNullDir() throws IOException {
        Session<FTPFile> session = ftpSessionFactory.getSession();
        ((FTPClient) session.getClientInstance()).changeWorkingDirectory("ftpSource");
        session.close();
        this.ftpInbound.start();

        Message<?> message = this.output.receive(10000);
        assertNotNull(message);
        assertThat(message.getPayload(), instanceOf(File.class));
        assertEquals(" ftpSource1.txt", ((File) message.getPayload()).getName());

        message = this.output.receive(10000);
        assertNotNull(message);
        assertThat(message.getPayload(), instanceOf(File.class));
        assertEquals("ftpSource2.txt", ((File) message.getPayload()).getName());

        assertNull(this.output.receive(10));

        this.ftpInbound.stop();
    }

    public static class SortingFileListFilter implements FileListFilter<File> {

        @Override
        public List<File> filterFiles(File[] files) {
            File[] sorted = Arrays.copyOf(files, files.length);
            Arrays.sort(sorted, (o1, o2) -> {
                if (o1.isDirectory() && !o2.isDirectory()) {
                    return 1;
                } else if (!o1.isDirectory() && o2.isDirectory()) {
                    return -1;
                } else {
                    return o1.getName().compareTo(o2.getName());
                }
            });
            return Arrays.asList(sorted);
        }

    }

    @SuppressWarnings("unused")
    private static final class TestMessageSessionCallback implements MessageSessionCallback<FTPFile, Object> {

        @Override
        public Object doInSession(Session<FTPFile> session, Message<?> requestMessage) throws IOException {
            return ((String) requestMessage.getPayload()).toUpperCase();
        }

    }

    public static class Config {

        private volatile String targetLocalDirectoryName;

        @Bean
        public SessionFactory<FTPFile> ftpSessionFactory() {
            return FtpServerOutboundTests.sessionFactory();
        }

        public String getTargetLocalDirectoryName() {
            return this.targetLocalDirectoryName;
        }

    }

}