INTSAMPLES-41 - Use embedded Apache FTP Server

For Reference see: https://jira.springsource.org/browse/INTSAMPLES-41
This commit is contained in:
Gunnar Hillert
2011-12-21 14:19:45 -05:00
parent 785271573d
commit 9ec98f8c74
18 changed files with 486 additions and 133 deletions

View File

@@ -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.
@@ -15,25 +15,54 @@
*/
package org.springframework.integration.samples.ftp;
import org.junit.Test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import org.springframework.context.ApplicationContext;
import java.io.File;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.Message;
import org.springframework.integration.core.PollableChannel;
/**
*
* @author Oleg Zhurakousky
* @author Gunnar Hillert
*
*/
public class FtpInboundChannelAdapterSample {
private static final Logger LOGGER = LoggerFactory.getLogger(FtpInboundChannelAdapterSample.class);
@Test
public void runDemo() throws Exception{
ApplicationContext ac =
ConfigurableApplicationContext ctx =
new ClassPathXmlApplicationContext("META-INF/spring/integration/FtpInboundChannelAdapterSample-context.xml");
PollableChannel ftpChannel = ac.getBean("ftpChannel", PollableChannel.class);
System.out.println("Received first file message: " + ftpChannel.receive(5000));
System.out.println("Received scond file message: " + ftpChannel.receive(5000));
System.out.println("Received nothing else: " + ftpChannel.receive(2000));
PollableChannel ftpChannel = ctx.getBean("ftpChannel", PollableChannel.class);
Message<?> message1 = ftpChannel.receive(2000);
Message<?> message2 = ftpChannel.receive(2000);
Message<?> message3 = ftpChannel.receive(1000);
LOGGER.info("Received first file message: {}.", message1);
LOGGER.info("Received second file message: {}.", message2);
LOGGER.info("Received nothing else: {}.", message3);
assertNotNull(message1);
assertNotNull(message2);
assertNull("Was NOT expecting a third message.", message3);
}
@After
public void cleanup() {
FileUtils.deleteQuietly(new File(TestSuite.LOCAL_FTP_TEMP_DIR));
}
}

View File

@@ -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.
@@ -15,35 +15,74 @@
*/
package org.springframework.integration.samples.ftp;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.InputStream;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.support.MessageBuilder;
/**
*
* @author Oleg Zhurakousky
* @author Gunnar Hillert
*
*/
public class FtpOutboundChannelAdapterSample {
private static final Logger LOGGER = LoggerFactory.getLogger(FtpOutboundChannelAdapterSample.class);
private final File baseFolder = new File("target" + File.separator + "toSend");
@Test
public void runDemo() throws Exception{
ApplicationContext ac =
ConfigurableApplicationContext ctx =
new ClassPathXmlApplicationContext("META-INF/spring/integration/FtpOutboundChannelAdapterSample-context.xml");
MessageChannel ftpChannel = ac.getBean("ftpChannel", MessageChannel.class);
File file = new File("readme.txt");
if (file.exists()){
Message<File> message = MessageBuilder.withPayload(file).build();
ftpChannel.send(message);
Thread.sleep(2000);
}
if (new File("remote-target-dir/readme.txt").exists()){
System.out.println("Successfully transfered 'readme.txt' file to a remote location under the name 'readme.txt'");
}
MessageChannel ftpChannel = ctx.getBean("ftpChannel", MessageChannel.class);
baseFolder.mkdirs();
final File fileToSendA = new File(baseFolder, "a.txt");
final File fileToSendB = new File(baseFolder, "b.txt");
final InputStream inputStreamA = FtpOutboundChannelAdapterSample.class.getResourceAsStream("/test-files/a.txt");
final InputStream inputStreamB = FtpOutboundChannelAdapterSample.class.getResourceAsStream("/test-files/b.txt");
FileUtils.copyInputStreamToFile(inputStreamA, fileToSendA);
FileUtils.copyInputStreamToFile(inputStreamB, fileToSendB);
assertTrue(fileToSendA.exists());
assertTrue(fileToSendB.exists());
final Message<File> messageA = MessageBuilder.withPayload(fileToSendA).build();
final Message<File> messageB = MessageBuilder.withPayload(fileToSendB).build();
ftpChannel.send(messageA);
ftpChannel.send(messageB);
Thread.sleep(2000);
assertTrue(new File(TestSuite.FTP_ROOT_DIR + File.separator + "a.txt").exists());
assertTrue(new File(TestSuite.FTP_ROOT_DIR + File.separator + "b.txt").exists());
LOGGER.info("Successfully transfered file 'a.txt' and 'b.txt' to a remote FTP location.");
}
@After
public void cleanup() {
FileUtils.deleteQuietly(baseFolder);
}
}

View File

@@ -20,57 +20,59 @@ import static org.junit.Assert.assertTrue;
import java.io.File;
import java.util.List;
import java.util.Random;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Demonstrates use of the outbound gateway to use ls, get and rm.
* Creates a temporary directory with 2 files; retrieves and removes them.
*
* The previous Test {@link FtpOutboundChannelAdapterSample} was uploading 2 test
* files:
*
* <ul>
* <li>a.txt</li>
* <li>b.txt</li>
* </ul>
*
* This test will now retrieves those 2 files and removes them. Instead of just
* polling the file, the files are instead retrieved and deleted using explicit
* FTP commands (LS and RM)
*
* @author Gary Russell
* @since 2.1
*
*/
public class FtpOutboundGatewaySample {
@Test
public void testLsGetRm() throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(
"classpath:/META-INF/spring/integration/FtpOutboundGatewaySample-context.xml");
ToFtpFlowGateway toFtpFlow = ctx.getBean(ToFtpFlowGateway.class);
try {
String tmpDir = System.getProperty("java.io.tmpdir");
final ToFtpFlowGateway toFtpFlow = ctx.getBean(ToFtpFlowGateway.class);
// remove the previous output files if necessary
new File(new File(tmpDir), "1.ftptest").delete();
new File(new File(tmpDir), "2.ftptest").delete();
// create a couple of files in a temp dir
File dir = new File(tmpDir + "/" + new Random().nextInt());
dir.mkdir();
File f1 = new File(dir, "1.ftptest");
f1.createNewFile();
File f2 = new File(dir, "2.ftptest");
f2.createNewFile();
// execute the flow (ls, get, rm, aggregate results)
List<Boolean> rmResults = toFtpFlow.lsGetAndRmFiles(dir.getAbsolutePath());
//Check everything went as expected, and clean up
assertEquals(2, rmResults.size());
for (Boolean result : rmResults) {
assertTrue(result);
}
assertTrue("Expected remote dir to be empty", dir.delete());
assertTrue("Could note delete retrieved file", new File(new File(tmpDir), "1.ftptest").delete());
assertTrue("Could note delete retrieved file", new File(new File(tmpDir), "2.ftptest").delete());
} finally {
ctx.close();
// execute the flow (ls, get, rm, aggregate results)
List<Boolean> rmResults = toFtpFlow.lsGetAndRmFiles("/");
//Check everything went as expected, and clean up
assertEquals("Was expecting the collection 'rmResults' to contain 2 elements.", 2, rmResults.size());
for (Boolean result : rmResults) {
assertTrue(result);
}
}
assertTrue("Expected FTP remote directory to be empty", new File(TestSuite.FTP_ROOT_DIR).delete());
}
@After
public void cleanup() {
FileUtils.deleteQuietly(new File(TestSuite.LOCAL_FTP_TEMP_DIR));
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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.
* 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.samples.ftp;
import java.io.File;
import java.io.IOException;
import java.net.SocketException;
import org.apache.commons.io.FileUtils;
import org.apache.ftpserver.FtpServer;
import org.apache.ftpserver.FtpServerFactory;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.listener.ListenerFactory;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.springframework.integration.samples.ftp.support.TestUserManager;
/**
* Test Suite that will bootstrap an embedded Apache FTP Server. Additionally some
* test files will be send to the FTP Server.
*
*
* @author Gunnar Hillert
*
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
FtpOutboundChannelAdapterSample.class,
FtpInboundChannelAdapterSample.class,
FtpOutboundGatewaySample.class
})
public class TestSuite {
public static final String FTP_ROOT_DIR = "target" + File.separator + "ftproot";
public static final String LOCAL_FTP_TEMP_DIR = "target" + File.separator + "local-ftp-temp";
@ClassRule
public static final TemporaryFolder temporaryFolder = new TemporaryFolder();
public static FtpServer server;
@BeforeClass
public static void setupFtpServer() throws FtpException, SocketException, IOException {
File ftpRoot = new File (FTP_ROOT_DIR);
ftpRoot.mkdirs();
TestUserManager userManager = new TestUserManager(ftpRoot.getAbsolutePath());
FtpServerFactory serverFactory = new FtpServerFactory();
serverFactory.setUserManager(userManager);
ListenerFactory factory = new ListenerFactory();
factory.setPort(3333);
serverFactory.addListener("default", factory.createListener());
server = serverFactory.createServer();
server.start();
}
@AfterClass
public static void shutDown() {
server.stop();
FileUtils.deleteQuietly(new File(FTP_ROOT_DIR));
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.
* 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.samples.ftp.support;
import java.util.Arrays;
import org.apache.ftpserver.ftplet.Authentication;
import org.apache.ftpserver.ftplet.AuthenticationFailedException;
import org.apache.ftpserver.ftplet.Authority;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.User;
import org.apache.ftpserver.usermanager.AnonymousAuthentication;
import org.apache.ftpserver.usermanager.ClearTextPasswordEncryptor;
import org.apache.ftpserver.usermanager.UsernamePasswordAuthentication;
import org.apache.ftpserver.usermanager.impl.AbstractUserManager;
import org.apache.ftpserver.usermanager.impl.BaseUser;
import org.apache.ftpserver.usermanager.impl.ConcurrentLoginPermission;
import org.apache.ftpserver.usermanager.impl.WritePermission;
/**
*
* @author Gunnar Hillert
*
*/
public class TestUserManager extends AbstractUserManager {
private BaseUser testUser;
private BaseUser anonUser;
private static final String TEST_USERNAME = "demo";
private static final String TEST_PASSWORD = "demo";
public TestUserManager(String homeDirectory) {
super("admin", new ClearTextPasswordEncryptor());
testUser = new BaseUser();
testUser.setAuthorities(Arrays.asList(new Authority[] {new ConcurrentLoginPermission(1, 1), new WritePermission()}));
testUser.setEnabled(true);
testUser.setHomeDirectory(homeDirectory);
testUser.setMaxIdleTime(10000);
testUser.setName(TEST_USERNAME);
testUser.setPassword(TEST_PASSWORD);
anonUser = new BaseUser(testUser);
anonUser.setName("anonymous");
}
public User getUserByName(String username) throws FtpException {
if(TEST_USERNAME.equals(username)) {
return testUser;
} else if(anonUser.getName().equals(username)) {
return anonUser;
}
return null;
}
public String[] getAllUserNames() throws FtpException {
return new String[] {TEST_USERNAME, anonUser.getName()};
}
public void delete(String username) throws FtpException {
throw new UnsupportedOperationException("Deleting of FTP Users is not supported.");
}
public void save(User user) throws FtpException {
throw new UnsupportedOperationException("Saving of FTP Users is not supported.");
}
public boolean doesExist(String username) throws FtpException {
return (TEST_USERNAME.equals(username) || anonUser.getName().equals(username)) ? true : false;
}
public User authenticate(Authentication authentication) throws AuthenticationFailedException {
if(UsernamePasswordAuthentication.class.isAssignableFrom(authentication.getClass())) {
UsernamePasswordAuthentication upAuth = (UsernamePasswordAuthentication) authentication;
if(TEST_USERNAME.equals(upAuth.getUsername()) && TEST_PASSWORD.equals(upAuth.getPassword())) {
return testUser;
}
if(anonUser.getName().equals(upAuth.getUsername())) {
return anonUser;
}
} else if(AnonymousAuthentication.class.isAssignableFrom(authentication.getClass())) {
return anonUser;
}
return null;
}
}

View File

@@ -12,19 +12,20 @@
<context:property-placeholder location="classpath:user.properties"/>
<bean id="ftpClientFactory" class="org.springframework.integration.ftp.session.DefaultFtpSessionFactory">
<property name="host" value="localhost"/>
<property name="host" value="${host}"/>
<property name="port" value="${port}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
</bean>
<int-ftp:inbound-channel-adapter id="ftpInbound"
<int-ftp:inbound-channel-adapter id="ftpInbound" cache-sessions="false"
channel="ftpChannel"
session-factory="ftpClientFactory"
filename-regex=".*\.txt$"
filename-pattern="*.txt"
auto-create-local-directory="true"
delete-remote-files="false"
remote-directory="<SPECIFY REMOTE DIRECTORY (e.g., /{workspace}/samples/basic/ftp/remote-source-dir)"
local-directory="file:local-target-dir">
remote-directory="/"
local-directory="#{ T(org.springframework.integration.samples.ftp.TestSuite).LOCAL_FTP_TEMP_DIR}/ftpInbound">
<int:poller fixed-rate="1000"/>
</int-ftp:inbound-channel-adapter>

View File

@@ -2,26 +2,27 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-ftp="http://www.springframework.org/schema/integration/ftp"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
xmlns:int-ftp="http://www.springframework.org/schema/integration/ftp"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp.xsd">
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:user.properties"/>
<bean id="ftpClientFactory" class="org.springframework.integration.ftp.session.DefaultFtpSessionFactory">
<property name="host" value="localhost"/>
<property name="host" value="${host}"/>
<property name="port" value="${port}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
</bean>
<int:channel id="ftpChannel"/>
<int-ftp:outbound-channel-adapter id="ftpOutbound"
<int-ftp:outbound-channel-adapter id="ftpOutbound" cache-sessions="false"
channel="ftpChannel"
remote-directory="<SPECIFY REMOTE DIRECTORY (e.g., /{workspace}/samples/basic/ftp/remote-target-dir)"
client-factory="ftpClientFactory"/>
remote-directory="/"
session-factory="ftpClientFactory" />
</beans>

View File

@@ -4,7 +4,7 @@
xmlns:context="http://www.springframework.org/schema/context"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-ftp="http://www.springframework.org/schema/integration/ftp"
xsi:schemaLocation="http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp.xsd
xsi:schemaLocation="http://www.springframework.org/schema/integration/ftp http://www.springframework.org/schema/integration/ftp/spring-integration-ftp-2.1.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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
@@ -16,36 +16,54 @@
<bean id="ftpSessionFactory"
class="org.springframework.integration.ftp.session.DefaultFtpSessionFactory">
<property name="host" value="localhost"/>
<property name="host" value="${host}"/>
<property name="port" value="${port}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
</bean>
<int-ftp:outbound-gateway id="gatewayLS"
<int-ftp:outbound-gateway id="gatewayLS" cache-sessions="false"
session-factory="ftpSessionFactory"
request-channel="inbound"
command="ls"
command-options=""
expression="payload"
reply-channel="toSplitter"/>
<int:channel id="toSplitter">
<int:interceptors>
<int:wire-tap channel="logger"/>
</int:interceptors>
</int:channel>
<int:splitter input-channel="toSplitter" output-channel="toGet"/>
<int:logging-channel-adapter id="logger" log-full-message="true"/>
<int-ftp:outbound-gateway id="gatewayGET"
local-directory="#{ T(System).getProperty('java.io.tmpdir')}"
<int:splitter id="splitter" input-channel="toSplitter" output-channel="toGet"/>
<int-ftp:outbound-gateway id="gatewayGET" cache-sessions="false"
local-directory="#{ T(org.springframework.integration.samples.ftp.TestSuite).LOCAL_FTP_TEMP_DIR}/gatewayGET"
session-factory="ftpSessionFactory"
request-channel="toGet"
reply-channel="toRm"
command="get"
reply-channel="toRemoveChannel"
command="get"
command-options="-P"
expression="payload.remoteDirectory + '/' + payload.filename"/>
<int-ftp:outbound-gateway id="gatewayRM" reply-channel="aggregateResultsChannel"
session-factory="ftpSessionFactory"
expression="headers['file_remoteDirectory'] + '/' + headers['file_remoteFile']"
request-channel="toRm"
command="rm"/>
<int:channel id="toRemoveChannel">
<int:interceptors>
<int:wire-tap channel="logger2"/>
</int:interceptors>
</int:channel>
<int:aggregator input-channel="aggregateResultsChannel"/>
<int:logging-channel-adapter id="logger2" log-full-message="true"/>
<int-ftp:outbound-gateway id="gatewayRM"
session-factory="ftpSessionFactory" cache-sessions="false"
expression="headers['file_remoteDirectory'] + '/' + headers['file_remoteFile']"
request-channel="toRemoveChannel"
command="rm"
reply-channel="aggregateResultsChannel"/>
<int:aggregator input-channel="aggregateResultsChannel"/>
</beans>

View File

@@ -1,8 +0,0 @@
log4j.rootCategory=WARN, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n
log4j.category.org.springframework=WARN
log4j.category.org.springframework.integration=DEBUG

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %5p | %t | %-55logger{55} | %m %n</pattern>
</encoder>
</appender>
<logger name="org.springframework.integration">
<level value="INFO" />
</logger>
<logger name="org.springframework">
<level value="INFO" />
</logger>
<logger name="org.apache.ftpserver">
<level value="WARN" />
</logger>
<root>
<level value="INFO" />
<appender-ref ref="CONSOLE" />
</root>
</configuration>

View File

@@ -0,0 +1 @@
A

View File

@@ -1,2 +1,4 @@
user=
password=
user=demo
password=demo
port=3333
host=localhost