diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java index 2e90df8dd1..a2a0fa98d7 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java @@ -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. @@ -25,6 +25,7 @@ import org.springframework.integration.config.xml.AbstractConsumerEndpointParser import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.file.filters.RegexPatternFileListFilter; import org.springframework.integration.file.filters.SimplePatternFileListFilter; +import org.springframework.integration.file.remote.RemoteFileOperations; import org.springframework.util.StringUtils; /** @@ -45,7 +46,8 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo @Override protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { - BeanDefinition templateDefinition = FileParserUtils.parseRemoteFileTemplate(element, parserContext, false); + BeanDefinition templateDefinition = FileParserUtils.parseRemoteFileTemplate(element, parserContext, false, + getTemplateClass()); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(getGatewayClassName()); @@ -120,4 +122,6 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo protected abstract String getGatewayClassName(); + protected abstract Class> getTemplateClass(); + } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileParserUtils.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileParserUtils.java index e9ea713af8..fbfc5a27b0 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileParserUtils.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileParserUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -22,7 +22,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.file.DefaultFileNameGenerator; -import org.springframework.integration.file.remote.RemoteFileTemplate; +import org.springframework.integration.file.remote.RemoteFileOperations; import org.springframework.util.StringUtils; /** @@ -39,8 +39,8 @@ public final class FileParserUtils { } public static BeanDefinition parseRemoteFileTemplate(Element element, ParserContext parserContext, - boolean atLeastOneRemoteDirectoryAttributeRequired) { - BeanDefinitionBuilder templateBuilder = BeanDefinitionBuilder.genericBeanDefinition(RemoteFileTemplate.class); + boolean atLeastOneRemoteDirectoryAttributeRequired, Class> templateClass) { + BeanDefinitionBuilder templateBuilder = BeanDefinitionBuilder.genericBeanDefinition(templateClass); templateBuilder.addConstructorArgReference(element.getAttribute("session-factory")); // configure MessageHandler properties diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java index 2d4faa9ea6..f1af695d3e 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java @@ -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. @@ -23,8 +23,11 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; +import org.springframework.integration.file.remote.RemoteFileOperations; import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler; +import reactor.util.StringUtils; + /** * @author Oleg Zhurakousky * @author Mark Fisher @@ -32,16 +35,23 @@ import org.springframework.integration.file.remote.handler.FileTransferringMessa * @author Gary Russell * @since 2.0 */ -public class RemoteFileOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { +public abstract class RemoteFileOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { @Override protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { BeanDefinitionBuilder handlerBuilder = BeanDefinitionBuilder.genericBeanDefinition(FileTransferringMessageHandler.class); - BeanDefinition templateDefinition = FileParserUtils.parseRemoteFileTemplate(element, parserContext, true); + BeanDefinition templateDefinition = FileParserUtils.parseRemoteFileTemplate(element, parserContext, true, + getTemplateClass()); handlerBuilder.addConstructorArgValue(templateDefinition); + String mode = element.getAttribute("mode"); + if (StringUtils.hasText(mode)) { + handlerBuilder.addConstructorArgValue(mode); + } return handlerBuilder.getBeanDefinition(); } + protected abstract Class> getTemplateClass(); + } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/ClientCallback.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/ClientCallback.java new file mode 100644 index 0000000000..9e31f13791 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/ClientCallback.java @@ -0,0 +1,44 @@ +/* + * 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.file.remote; + +import org.springframework.integration.file.remote.session.Session; + +/** + * {@code RemoteFileTemplate} callback with the underlying client instance providing + * access to lower level methods. + * + * @author Gary Russell + * + * @param The type of the underlying client object. + * @param The return type of the callback method. + * @since 4.1 + * + */ +public interface ClientCallback { + + /** + * Called within the context of a {@link Session}. + * Perform some operation(s) on the client instance underlying the session. The caller will take + * care of closing the session after this method exits. However, the implementation + * is required to perform any clean up required by the client after performing + * operations. + * @param client The client instance. + * @return The return value. + */ + T doWithClient(C client); + +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/ClientCallbackWithoutResult.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/ClientCallbackWithoutResult.java new file mode 100644 index 0000000000..d7400b1c61 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/ClientCallbackWithoutResult.java @@ -0,0 +1,47 @@ +/* + * 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.file.remote; + + +/** + * {@code RemoteFileTemplate} callback with the underlying client instance providing + * access to lower level methods where no result is returned. + * + * @author Gary Russell + * + * @param The type of the underlying client object. + * @since 4.1 + * + */ +public abstract class ClientCallbackWithoutResult implements ClientCallback { + + @Override + public Object doWithClient(C client) { + doWithClientWithoutResult(client); + return null; + } + + /** + * Called within the context of a session. + * Perform some operation(s) on the client instance underlying the session. The caller will take + * care of closing the session after this method exits. However, the implementation + * is required to perform any clean up required by the client after performing + * operations. + * @param client The client. + */ + protected abstract void doWithClientWithoutResult(C client); + +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileOperations.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileOperations.java index c97f94d4a6..fa03331c9f 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileOperations.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileOperations.java @@ -15,6 +15,7 @@ */ package org.springframework.integration.file.remote; +import org.springframework.integration.file.support.FileExistsMode; import org.springframework.messaging.Message; /** @@ -30,9 +31,12 @@ public interface RemoteFileOperations { * Send a file to a remote server, based on information in a message. * * @param message The message. + * @param mode See {@link FileExistsMode} (optional; default REPLACE). A + * vararg is used to make the argument optional; only the first will be + * used if more than one is provided. * @return The remote path, or null if no local file was found. */ - String send(Message message); + String send(Message message, FileExistsMode... mode); /** * Send a file to a remote server, based on information in a message. @@ -41,9 +45,33 @@ public interface RemoteFileOperations { * * @param message The message. * @param subDirectory The sub directory. + * @param mode See {@link FileExistsMode} (optional; default REPLACE). A + * vararg is used to make the argument optional; only the first will be + * used if more than one is provided. * @return The remote path, or null if no local file was found. */ - String send(Message message, String subDirectory); + String send(Message message, String subDirectory, FileExistsMode... mode); + + /** + * Send a file to a remote server, based on information in a message, appending. + * + * @param message The message. + * @return The remote path, or null if no local file was found. + * @since 4.1 + */ + String append(Message message); + + /** + * Send a file to a remote server, based on information in a message, appending. + * The subDirectory is appended to the remote directory evaluated from + * the message. + * + * @param message The message. + * @param subDirectory The sub directory. + * @return The remote path, or null if no local file was found. + * @since 4.1 + */ + String append(Message message, String subDirectory); /** * Retrieve a remote file as an InputStream. @@ -63,11 +91,20 @@ public interface RemoteFileOperations { */ boolean get(Message message, InputStreamCallback callback); + /** + * Check if a file exists on the remote server. + * + * @param path The full path to the file. + * @return true when the file exists. + * @since 4.1 + */ + boolean exists(String path); + /** * Remove a remote file. * * @param path The full path to the file. - * @return true when successful + * @return true when successful. */ boolean remove(String path); @@ -84,9 +121,22 @@ public interface RemoteFileOperations { * Reliably closes the session when the method exits. * * @param callback the SessionCallback. - * @param The type returned by {@link SessionCallback#doInSession(org.springframework.integration.file.remote.session.Session)}. + * @param The type returned by + * {@link SessionCallback#doInSession(org.springframework.integration.file.remote.session.Session)}. * @return The result of the callback method. */ T execute(SessionCallback callback); + /** + * Execute the callback's doWithClient method after obtaining a session's + * client, providing access to low level methods. + * Reliably closes the session when the method exits. + * + * @param callback the ClientCallback. + * @param The type returned by {@link ClientCallback#doWithClient(Object)}. + * @return The result of the callback method. + * @since 4.1 + */ + T executeWithClient(ClientCallback callback); + } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java index 6afa03e9bd..eeff5a674d 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -36,6 +36,7 @@ import org.springframework.integration.file.FileNameGenerator; import org.springframework.integration.file.remote.session.CachingSessionFactory; import org.springframework.integration.file.remote.session.Session; import org.springframework.integration.file.remote.session.SessionFactory; +import org.springframework.integration.file.support.FileExistsMode; import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; import org.springframework.messaging.Message; import org.springframework.messaging.MessageDeliveryException; @@ -60,7 +61,7 @@ public class RemoteFileTemplate implements RemoteFileOperations, Initializ /** * the {@link SessionFactory} for acquiring remote file Sessions. */ - private final SessionFactory sessionFactory; + protected final SessionFactory sessionFactory; private volatile String temporaryFileSuffix =".writing"; @@ -178,13 +179,30 @@ public class RemoteFileTemplate implements RemoteFileOperations, Initializ } @Override - public String send(final Message message) { - return this.send(message, null); + public String append(final Message message) { + return append(message, null); } @Override - public String send(final Message message, final String subDirectory) { + public String append(final Message message, String subDirectory) { + return send(message, subDirectory, FileExistsMode.APPEND); + } + + @Override + public String send(Message message, FileExistsMode... mode) { + return send(message, null, mode); + } + + @Override + public String send(final Message message, String subDirectory, FileExistsMode... mode) { + FileExistsMode modeToUse = mode == null || mode.length < 1 ? FileExistsMode.REPLACE : mode[0]; + return send(message, subDirectory, modeToUse); + } + + private String send(final Message message, final String subDirectory, final FileExistsMode mode) { Assert.notNull(this.directoryExpressionProcessor, "'remoteDirectoryExpression' is required"); + Assert.isTrue(!FileExistsMode.APPEND.equals(mode) || !this.useTemporaryFileName, + "Cannot append when using a temporary file name"); final StreamHolder inputStreamHolder = this.payloadToInputStream(message); if (inputStreamHolder != null) { return this.execute(new SessionCallback() { @@ -211,7 +229,7 @@ public class RemoteFileTemplate implements RemoteFileOperations, Initializ } fileName = RemoteFileTemplate.this.fileNameGenerator.generateFileName(message); RemoteFileTemplate.this.sendFileToRemoteDirectory(inputStreamHolder.getStream(), - temporaryRemoteDirectory, remoteDirectory, fileName, session); + temporaryRemoteDirectory, remoteDirectory, fileName, session, mode); return remoteDirectory + fileName; } catch (FileNotFoundException e) { @@ -239,6 +257,11 @@ public class RemoteFileTemplate implements RemoteFileOperations, Initializ } } + @Override + public boolean exists(String path) { + throw new UnsupportedOperationException("exists() is not supported by the generic template"); + } + @Override public boolean remove(final String path) { return this.execute(new SessionCallback() { @@ -324,6 +347,11 @@ public class RemoteFileTemplate implements RemoteFileOperations, Initializ } } + @Override + public T executeWithClient(ClientCallback callback) { + throw new UnsupportedOperationException("executeWithClient() is not supported by the generic template"); + } + private StreamHolder payloadToInputStream(Message message) throws MessageDeliveryException { try { Object payload = message.getPayload(); @@ -365,7 +393,7 @@ public class RemoteFileTemplate implements RemoteFileOperations, Initializ } private void sendFileToRemoteDirectory(InputStream inputStream, String temporaryRemoteDirectory, - String remoteDirectory, String fileName, Session session) throws IOException { + String remoteDirectory, String fileName, Session session, FileExistsMode mode) throws IOException { remoteDirectory = this.normalizeDirectoryPath(remoteDirectory); temporaryRemoteDirectory = this.normalizeDirectoryPath(temporaryRemoteDirectory); @@ -387,9 +415,29 @@ public class RemoteFileTemplate implements RemoteFileOperations, Initializ } try { - session.write(inputStream, tempFilePath); + boolean rename = this.useTemporaryFileName; + if (FileExistsMode.REPLACE.equals(mode)) { + session.write(inputStream, tempFilePath); + } + else if (FileExistsMode.APPEND.equals(mode)) { + session.append(inputStream, tempFilePath); + } + else { + if (exists(remoteFilePath)) { + if (FileExistsMode.FAIL.equals(mode)) { + throw new MessagingException( + "The destination file already exists at '" + remoteFilePath + "'."); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("File not transferred to '" + remoteFilePath + "'; already exists."); + } + } + rename = false; + } + } // then rename it to its final name if necessary - if (useTemporaryFileName){ + if (rename) { session.rename(tempFilePath, remoteFilePath); } } @@ -432,5 +480,4 @@ public class RemoteFileTemplate implements RemoteFileOperations, Initializ } - } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/SessionCallbackWithoutResult.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/SessionCallbackWithoutResult.java index 590f9f88d8..8ce4aaef02 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/SessionCallbackWithoutResult.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/SessionCallbackWithoutResult.java @@ -31,7 +31,7 @@ public abstract class SessionCallbackWithoutResult implements SessionCallback @Override public Object doInSession(Session session) throws IOException { - this.doInSessionWithoutResult(session); + doInSessionWithoutResult(session); return null; } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java index 6a8af5b68c..1903239fe3 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java @@ -20,6 +20,7 @@ import org.springframework.expression.Expression; import org.springframework.integration.file.FileNameGenerator; import org.springframework.integration.file.remote.RemoteFileTemplate; import org.springframework.integration.file.remote.session.SessionFactory; +import org.springframework.integration.file.support.FileExistsMode; import org.springframework.integration.handler.AbstractMessageHandler; import org.springframework.messaging.Message; import org.springframework.util.Assert; @@ -39,14 +40,22 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { private final RemoteFileTemplate remoteFileTemplate; + private final FileExistsMode mode; + public FileTransferringMessageHandler(SessionFactory sessionFactory) { Assert.notNull(sessionFactory, "sessionFactory must not be null"); this.remoteFileTemplate = new RemoteFileTemplate(sessionFactory); + this.mode = FileExistsMode.REPLACE; } public FileTransferringMessageHandler(RemoteFileTemplate remoteFileTemplate) { + this(remoteFileTemplate, FileExistsMode.REPLACE); + } + + public FileTransferringMessageHandler(RemoteFileTemplate remoteFileTemplate, FileExistsMode mode) { Assert.notNull(remoteFileTemplate, "remoteFileTemplate must not be null"); this.remoteFileTemplate = remoteFileTemplate; + this.mode = mode; } @@ -98,7 +107,7 @@ public class FileTransferringMessageHandler extends AbstractMessageHandler { @Override protected void handleMessageInternal(Message message) throws Exception { - this.remoteFileTemplate.send(message); + this.remoteFileTemplate.send(message, this.mode); } } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/CachingSessionFactory.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/CachingSessionFactory.java index 495acc443a..2863028cdf 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/CachingSessionFactory.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/CachingSessionFactory.java @@ -215,6 +215,11 @@ public class CachingSessionFactory implements SessionFactory, DisposableBe this.targetSession.write(inputStream, destination); } + @Override + public void append(InputStream inputStream, String destination) throws IOException { + this.targetSession.append(inputStream, destination); + } + @Override public boolean isOpen() { return this.targetSession.isOpen(); @@ -230,6 +235,11 @@ public class CachingSessionFactory implements SessionFactory, DisposableBe return this.targetSession.mkdir(directory); } + @Override + public boolean rmdir(String directory) throws IOException { + return this.targetSession.rmdir(directory); + } + @Override public boolean exists(String path) throws IOException{ return this.targetSession.exists(path); @@ -254,6 +264,11 @@ public class CachingSessionFactory implements SessionFactory, DisposableBe this.dirty = true; } + @Override + public Object getClientInstance() { + return this.targetSession.getClientInstance(); + } + } } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/Session.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/Session.java index de13dc30ea..4549c2e7b1 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/Session.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/Session.java @@ -30,18 +30,36 @@ import java.io.OutputStream; * @author Gary Russell * @since 2.0 */ -public interface Session { +public interface Session { boolean remove(String path) throws IOException; - T[] list(String path) throws IOException; + F[] list(String path) throws IOException; void read(String source, OutputStream outputStream) throws IOException; void write(InputStream inputStream, String destination) throws IOException; + /** + * Append to a file. + * @param inputStream the stream. + * @param destination the destination. + * @throws IOException an IO Exception. + * @since 4.1 + */ + void append(InputStream inputStream, String destination) throws IOException; + boolean mkdir(String directory) throws IOException; + /** + * Remove a remote directory. + * @param directory The directory. + * @return True if the directory was removed. + * @throws IOException an IO exception. + * @since 4.1 + */ + boolean rmdir(String directory) throws IOException; + void rename(String pathFrom, String pathTo) throws IOException; void close(); @@ -57,6 +75,7 @@ public interface Session { * @param source The path of the remote file. * @return The raw inputStream. * @throws IOException Any IOException. + * @since 3.0 */ InputStream readRaw(String source) throws IOException; @@ -65,7 +84,19 @@ public interface Session { * Required by some session providers. * @return true if successful. * @throws IOException Any IOException. + * @since 3.0 */ boolean finalizeRaw() throws IOException; + /** + * Get the underlying client library's client instance for this session. + * Returns an {@code Object} to avoid significant changes to -file, -ftp, -sftp + * modules, which would be required + * if we added another generic parameter. Implementations should narrow the + * return type. + * @return The client instance. + * @since 4.1 + */ + Object getClientInstance(); + } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/SessionFactoryFactoryBean.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/SessionFactoryFactoryBean.java deleted file mode 100644 index b49d2c3b24..0000000000 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/session/SessionFactoryFactoryBean.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2002-2013 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.file.remote.session; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.factory.FactoryBean; - -/** - * Temporary factory bean to manage SessionFactory until deprecated 'cache-sessions' attribute - * is removed. - * - * The attribute is now removed so we deprecate this class and log a message in case someone - * is using it directly. It is no longer used by the framework. - * - * @author Oleg Zhurakousky - * @author Gary Russell - * @since 2.1 - * - * @deprecated - */ -@Deprecated -public class SessionFactoryFactoryBean implements FactoryBean> { - - Log logger = LogFactory.getLog(this.getClass()); - - private final SessionFactory sessionFactory; - - public SessionFactoryFactoryBean(SessionFactory sessionFactory, boolean cacheSessions){ - if (logger.isWarnEnabled()) { - logger.warn("Do not use this factory bean; " - + "instantiate the session factory directly; " - + "if cached sessions are required, wrap it in a CachingSessionFactory."); - } - if (cacheSessions && !(sessionFactory instanceof CachingSessionFactory)){ - this.sessionFactory = new CachingSessionFactory(sessionFactory); - } - else { - this.sessionFactory = sessionFactory; - } - } - - public SessionFactory getObject() throws Exception { - return this.sessionFactory; - } - - - public Class getObjectType() { - return this.sessionFactory.getClass(); - } - - public boolean isSingleton() { - return true; - } - -} diff --git a/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-4.1.xsd b/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-4.1.xsd index dcfb187932..a2e0605d1a 100644 --- a/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-4.1.xsd +++ b/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-4.1.xsd @@ -398,48 +398,7 @@ Only files matching this regular expression will be picked up by this adapter. - - - - - - - - + + + + + + + + + + + + + diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java index 0acf9ef931..715d297461 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java @@ -159,19 +159,9 @@ public class RemoteFileOutboundGatewayTests { gw.afterPropertiesSet(); new File(this.tmpDir + "/f1").delete(); new File(this.tmpDir + "/f2").delete(); - when(sessionFactory.getSession()).thenReturn(new Session() { + when(sessionFactory.getSession()).thenReturn(new TestSession() { int n; - @Override - public boolean remove(String path) throws IOException { - return false; - } - - @Override - public Object[] list(String path) throws IOException { - return null; - } - @Override public void read(String source, OutputStream outputStream) throws IOException { @@ -184,49 +174,11 @@ public class RemoteFileOutboundGatewayTests { outputStream.write("testData".getBytes()); } - @Override - public void write(InputStream inputStream, String destination) - throws IOException { - } - - @Override - public boolean mkdir(String directory) throws IOException { - return false; - } - - @Override - public void rename(String pathFrom, String pathTo) - throws IOException { - } - - @Override - public void close() { - } - - @Override - public boolean isOpen() { - return false; - } - - @Override - public boolean exists(String path) throws IOException { - return false; - } - @Override public String[] listNames(String path) throws IOException { - return new String[]{path1, path2}; + return new String[] { path1, path2 }; } - @Override - public InputStream readRaw(String source) throws IOException { - return null; - } - - @Override - public boolean finalizeRaw() throws IOException { - return false; - } }); @SuppressWarnings("unchecked") Message> out = (Message>) gw @@ -246,16 +198,7 @@ public class RemoteFileOutboundGatewayTests { gw.setLocalDirectory(new File(this.tmpDir)); gw.afterPropertiesSet(); new File(this.tmpDir + "/f1").delete(); - when(sessionFactory.getSession()).thenReturn(new Session() { - @Override - public boolean remove(String path) throws IOException { - return false; - } - - @Override - public Object[] list(String path) throws IOException { - return null; - } + when(sessionFactory.getSession()).thenReturn(new TestSession() { @Override public void read(String source, OutputStream outputStream) @@ -263,49 +206,11 @@ public class RemoteFileOutboundGatewayTests { outputStream.write("testData".getBytes()); } - @Override - public void write(InputStream inputStream, String destination) - throws IOException { - } - - @Override - public boolean mkdir(String directory) throws IOException { - return false; - } - - @Override - public void rename(String pathFrom, String pathTo) - throws IOException { - } - - @Override - public void close() { - } - - @Override - public boolean isOpen() { - return false; - } - - @Override - public boolean exists(String path) throws IOException { - return false; - } - @Override public String[] listNames(String path) throws IOException { return new String[]{"f1"}; } - @Override - public InputStream readRaw(String source) throws IOException { - return null; - } - - @Override - public boolean finalizeRaw() throws IOException { - return false; - } }); @SuppressWarnings("unchecked") Message> out = (Message>) gw @@ -326,16 +231,7 @@ public class RemoteFileOutboundGatewayTests { gw.afterPropertiesSet(); new File(this.tmpDir + "/f1").delete(); new File(this.tmpDir + "/f2").delete(); - when(sessionFactory.getSession()).thenReturn(new Session() { - @Override - public boolean remove(String path) throws IOException { - return false; - } - - @Override - public Object[] list(String path) throws IOException { - return null; - } + when(sessionFactory.getSession()).thenReturn(new TestSession() { @Override public void read(String source, OutputStream outputStream) @@ -343,49 +239,6 @@ public class RemoteFileOutboundGatewayTests { outputStream.write("testData".getBytes()); } - @Override - public void write(InputStream inputStream, String destination) - throws IOException { - } - - @Override - public boolean mkdir(String directory) throws IOException { - return false; - } - - @Override - public void rename(String pathFrom, String pathTo) - throws IOException { - } - - @Override - public void close() { - } - - @Override - public boolean isOpen() { - return false; - } - - @Override - public boolean exists(String path) throws IOException { - return false; - } - - @Override - public String[] listNames(String path) throws IOException { - return new String[0]; - } - - @Override - public InputStream readRaw(String source) throws IOException { - return null; - } - - @Override - public boolean finalizeRaw() throws IOException { - return false; - } }); gw.handleRequestMessage(new GenericMessage("testremote/*")); } @@ -733,13 +586,7 @@ public class RemoteFileOutboundGatewayTests { gw.setLocalDirectory(new File(this.tmpDir)); gw.afterPropertiesSet(); new File(this.tmpDir + "/f1").delete(); - when(sessionFactory.getSession()).thenReturn(new Session() { - private boolean open = true; - - @Override - public boolean remove(String path) throws IOException { - return false; - } + when(sessionFactory.getSession()).thenReturn(new TestSession() { @Override public TestLsEntry[] list(String path) throws IOException { @@ -754,50 +601,6 @@ public class RemoteFileOutboundGatewayTests { outputStream.write("testfile".getBytes()); } - @Override - public void write(InputStream inputStream, String destination) - throws IOException { - } - - @Override - public boolean mkdir(String directory) throws IOException { - return true; - } - - @Override - public void rename(String pathFrom, String pathTo) - throws IOException { - } - - @Override - public void close() { - open = false; - } - - @Override - public boolean isOpen() { - return open; - } - - @Override - public boolean exists(String path) throws IOException { - return true; - } - - @Override - public String[] listNames(String path) throws IOException { - return null; - } - - @Override - public InputStream readRaw(String source) throws IOException { - return null; - } - - @Override - public boolean finalizeRaw() throws IOException { - return false; - } }); @SuppressWarnings("unchecked") Message out = (Message) gw.handleRequestMessage(new GenericMessage("f1")); @@ -819,13 +622,7 @@ public class RemoteFileOutboundGatewayTests { gw.setLocalDirectory(new File(this.tmpDir)); gw.afterPropertiesSet(); new File(this.tmpDir + "/f1").delete(); - when(sessionFactory.getSession()).thenReturn(new Session() { - private boolean open = true; - - @Override - public boolean remove(String path) throws IOException { - return false; - } + when(sessionFactory.getSession()).thenReturn(new TestSession() { @Override public TestLsEntry[] list(String path) throws IOException { @@ -839,50 +636,6 @@ public class RemoteFileOutboundGatewayTests { throw new RuntimeException("test remove .writing"); } - @Override - public void write(InputStream inputStream, String destination) - throws IOException { - } - - @Override - public boolean mkdir(String directory) throws IOException { - return true; - } - - @Override - public void rename(String pathFrom, String pathTo) - throws IOException { - } - - @Override - public void close() { - open = false; - } - - @Override - public boolean isOpen() { - return open; - } - - @Override - public boolean exists(String path) throws IOException { - return true; - } - - @Override - public String[] listNames(String path) throws IOException { - return null; - } - - @Override - public InputStream readRaw(String source) throws IOException { - return null; - } - - @Override - public boolean finalizeRaw() throws IOException { - return false; - } }); try{ gw.handleRequestMessage(new GenericMessage("f1")); @@ -911,13 +664,7 @@ public class RemoteFileOutboundGatewayTests { Calendar cal = Calendar.getInstance(); cal.add(Calendar.MONTH, -1); final Date modified = new Date(cal.getTime().getTime() / 1000 * 1000); - when(sessionFactory.getSession()).thenReturn(new Session() { - private boolean open = true; - - @Override - public boolean remove(String path) throws IOException { - return false; - } + when(sessionFactory.getSession()).thenReturn(new TestSession() { @Override public TestLsEntry[] list(String path) throws IOException { @@ -932,50 +679,6 @@ public class RemoteFileOutboundGatewayTests { outputStream.write("testfile".getBytes()); } - @Override - public void write(InputStream inputStream, String destination) - throws IOException { - } - - @Override - public boolean mkdir(String directory) throws IOException { - return true; - } - - @Override - public void rename(String pathFrom, String pathTo) - throws IOException { - } - - @Override - public void close() { - open = false; - } - - @Override - public boolean isOpen() { - return open; - } - - @Override - public boolean exists(String path) throws IOException { - return true; - } - - @Override - public String[] listNames(String path) throws IOException { - return null; - } - - @Override - public InputStream readRaw(String source) throws IOException { - return null; - } - - @Override - public boolean finalizeRaw() throws IOException { - return false; - } }); @SuppressWarnings("unchecked") Message out = (Message) gw.handleRequestMessage(new GenericMessage("x/f1")); @@ -999,13 +702,7 @@ public class RemoteFileOutboundGatewayTests { (sessionFactory, "get", "payload"); gw.setLocalDirectory(new File(this.tmpDir + "/x")); gw.afterPropertiesSet(); - when(sessionFactory.getSession()).thenReturn(new Session() { - private boolean open = true; - - @Override - public boolean remove(String path) throws IOException { - return false; - } + when(sessionFactory.getSession()).thenReturn(new TestSession() { @Override public TestLsEntry[] list(String path) throws IOException { @@ -1020,50 +717,6 @@ public class RemoteFileOutboundGatewayTests { outputStream.write("testfile".getBytes()); } - @Override - public void write(InputStream inputStream, String destination) - throws IOException { - } - - @Override - public boolean mkdir(String directory) throws IOException { - return true; - } - - @Override - public void rename(String pathFrom, String pathTo) - throws IOException { - } - - @Override - public void close() { - open = false; - } - - @Override - public boolean isOpen() { - return open; - } - - @Override - public boolean exists(String path) throws IOException { - return true; - } - - @Override - public String[] listNames(String path) throws IOException { - return null; - } - - @Override - public InputStream readRaw(String source) throws IOException { - return null; - } - - @Override - public boolean finalizeRaw() throws IOException { - return false; - } }); gw.handleRequestMessage(new GenericMessage("f1")); File out = new File(this.tmpDir + "/x/f1"); @@ -1207,6 +860,88 @@ public class RemoteFileOutboundGatewayTests { } +abstract class TestSession implements org.springframework.integration.file.remote.session.Session { + + private boolean open; + + + @Override + public boolean remove(String path) throws IOException { + return false; + } + + @Override + public TestLsEntry[] list(String path) throws IOException { + return null; + } + + @Override + public void read(String source, OutputStream outputStream) + throws IOException { + } + + @Override + public void write(InputStream inputStream, String destination) + throws IOException { + } + + @Override + public void append(InputStream inputStream, String destination) + throws IOException { + } + + @Override + public boolean mkdir(String directory) throws IOException { + return true; + } + + @Override + public boolean rmdir(String directory) throws IOException { + return true; + } + + @Override + public void rename(String pathFrom, String pathTo) + throws IOException { + } + + @Override + public void close() { + open = false; + } + + @Override + public boolean isOpen() { + return open; + } + + @Override + public boolean exists(String path) throws IOException { + return true; + } + + @Override + public String[] listNames(String path) throws IOException { + return null; + } + + @Override + public InputStream readRaw(String source) throws IOException { + return null; + } + + @Override + public boolean finalizeRaw() throws IOException { + return false; + } + + @Override + public Object getClientInstance() { + return null; + } + +} + class TestRemoteFileOutboundGateway extends AbstractRemoteFileOutboundGateway { @SuppressWarnings({"rawtypes", "unchecked"}) diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/CachingSessionFactoryTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/CachingSessionFactoryTests.java index 5697e66e09..c621e1f011 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/CachingSessionFactoryTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/session/CachingSessionFactoryTests.java @@ -147,11 +147,20 @@ public class CachingSessionFactoryTests { public void write(InputStream inputStream, String destination) throws IOException { } + @Override + public void append(InputStream inputStream, String destination) throws IOException { + } + @Override public boolean mkdir(String directory) throws IOException { return false; } + @Override + public boolean rmdir(String directory) throws IOException { + return false; + } + @Override public void rename(String pathFrom, String pathTo) throws IOException { } @@ -186,6 +195,11 @@ public class CachingSessionFactoryTests { return false; } + @Override + public Object getClientInstance() { + return null; + } + } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/synchronizer/AbstractRemoteFileSynchronizerTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/synchronizer/AbstractRemoteFileSynchronizerTests.java index 637f0979cc..1106442f3f 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/remote/synchronizer/AbstractRemoteFileSynchronizerTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/remote/synchronizer/AbstractRemoteFileSynchronizerTests.java @@ -120,11 +120,20 @@ public class AbstractRemoteFileSynchronizerTests { public void write(InputStream inputStream, String destination) throws IOException { } + @Override + public void append(InputStream inputStream, String destination) throws IOException { + } + @Override public boolean mkdir(String directory) throws IOException { return true; } + @Override + public boolean rmdir(String directory) throws IOException { + return true; + } + @Override public void rename(String pathFrom, String pathTo) throws IOException { } @@ -158,6 +167,11 @@ public class AbstractRemoteFileSynchronizerTests { return true; } + @Override + public Object getClientInstance() { + return null; + } + } } diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpNamespaceHandler.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpNamespaceHandler.java index e3c819f9a9..eaf1b3c2db 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpNamespaceHandler.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpNamespaceHandler.java @@ -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()); } diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParser.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParser.java new file mode 100644 index 0000000000..201206352b --- /dev/null +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParser.java @@ -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> getTemplateClass() { + return FtpRemoteFileTemplate.class; + } + +} diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParser.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParser.java index 67121f5337..47d4d64b8d 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParser.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParser.java @@ -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> getTemplateClass() { + return FtpRemoteFileTemplate.class; + } + } diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/session/AbstractFtpSessionFactory.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/session/AbstractFtpSessionFactory.java index 5ab775219f..e1765764b2 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/session/AbstractFtpSessionFactory.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/session/AbstractFtpSessionFactory.java @@ -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 implements } @Override - public Session getSession() { + public FtpSession getSession() { try { return new FtpSession(this.createClient()); } diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/session/FtpRemoteFileTemplate.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/session/FtpRemoteFileTemplate.java new file mode 100644 index 0000000000..2f0c61ffba --- /dev/null +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/session/FtpRemoteFileTemplate.java @@ -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 { + + public FtpRemoteFileTemplate(SessionFactory sessionFactory) { + super(sessionFactory); + } + + @SuppressWarnings("unchecked") + @Override + public T executeWithClient(final ClientCallback callback) { + return doExecuteWithClient((ClientCallback) callback); + } + + protected T doExecuteWithClient(final ClientCallback callback) { + return execute(new SessionCallback() { + + @Override + public T doInSession(Session session) throws IOException { + return callback.doWithClient((FTPClient) session.getClientInstance()); + } + }); + } + + @Override + public boolean exists(final String path) { + return executeWithClient(new ClientCallback() { + + @Override + public Boolean doWithClient(FTPClient client) { + try { + return client.getStatus(path) != null; + } + catch (IOException e) { + throw new MessagingException("Failed to stat " + path, e); + } + } + }); + } + + +} diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/session/FtpSession.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/session/FtpSession.java index 24718c8f5e..c2f85dc419 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/session/FtpSession.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/session/FtpSession.java @@ -116,7 +116,7 @@ public class FtpSession implements Session { @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 { } } + @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 { 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 { return exists; } + + @Override + public FTPClient getClientInstance() { + return this.client; + } + } diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/TesFtpServer.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/TestFtpServer.java similarity index 98% rename from spring-integration-ftp/src/test/java/org/springframework/integration/ftp/TesFtpServer.java rename to spring-integration-ftp/src/test/java/org/springframework/integration/ftp/TestFtpServer.java index 3cbb16db9c..3859943c76 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/TesFtpServer.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/TestFtpServer.java @@ -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 diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java index f28d013d2d..e8cced862c 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpInboundChannelAdapterParserTests.java @@ -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 genMethod = new AtomicReference(); 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 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 { @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; } diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml index 3d4da6a070..3930e621de 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml @@ -31,6 +31,7 @@ auto-create-directory="false" remote-file-separator="" temporary-file-suffix=".foo" + mode="APPEND" remote-filename-generator="fileNameGenerator" order="23"/> diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java index 7d4e85acab..2cf3627fbc 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java @@ -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 handlers = (Set) TestUtils .getPropertyValue( - TestUtils.getPropertyValue(channel, "dispatcher"), + TestUtils.getPropertyValue(ftpChannel, "dispatcher"), "handlers"); Iterator 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("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); diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests.java index 96dd9613e3..4b57de89f6 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests.java @@ -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 diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpsInboundChannelAdapterParserTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpsInboundChannelAdapterParserTests.java index fbf1ad69b7..b611e5ee45 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpsInboundChannelAdapterParserTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpsInboundChannelAdapterParserTests.java @@ -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 spcas = ac.getBeansOfType(SourcePollingChannelAdapter.class); - SourcePollingChannelAdapter adapter = null; - for (String key : spcas.keySet()) { - if (!key.equals("ftpInbound")){ - adapter = spcas.get(key); - } - } - assertNotNull(adapter); - } - } diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpsOutboundChannelAdapterParserTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpsOutboundChannelAdapterParserTests.java index cac33c6ce7..20d67565a8 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpsOutboundChannelAdapterParserTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpsOutboundChannelAdapterParserTests.java @@ -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")); diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpOutboundTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpOutboundTests.java index 44d1c8e828..083e5b7e1d 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpOutboundTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpOutboundTests.java @@ -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(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(); } diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpServerOutboundTests-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpServerOutboundTests-context.xml index 41fca66c3c..d1e42ed8f8 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpServerOutboundTests-context.xml +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpServerOutboundTests-context.xml @@ -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"> - + @@ -104,4 +104,35 @@ remote-directory="ftpTarget" reply-channel="output"/> + + + + + + + + + + + + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpServerOutboundTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpServerOutboundTests.java index bee1dcfde4..4393df6cb0 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpServerOutboundTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpServerOutboundTests.java @@ -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 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 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() { + + @Override + public FTPFile[] doInSession(Session session) throws IOException { + return session.list("ftpTarget/appending.txt"); + } + }); + assertEquals(1, files.length); + assertEquals(6, files[0].getSize()); + } + } diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/session/FtpRemoteFileTemplateTests-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/session/FtpRemoteFileTemplateTests-context.xml new file mode 100644 index 0000000000..4a7e5793fb --- /dev/null +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/session/FtpRemoteFileTemplateTests-context.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/session/FtpRemoteFileTemplateTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/session/FtpRemoteFileTemplateTests.java new file mode 100644 index 0000000000..97bfb67924 --- /dev/null +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/session/FtpRemoteFileTemplateTests.java @@ -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() { + + @Override + public Boolean doInSession(Session session) throws IOException { + session.mkdir("foo/"); + return session.mkdir("foo/bar/"); + } + + }); + template.append(new GenericMessage("foo")); + template.append(new GenericMessage("bar")); + assertTrue(template.exists("foo/foobar.txt")); + template.executeWithClient(new ClientCallbackWithoutResult() { + + @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() { + + @Override + public void doInSessionWithoutResult(Session 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")); + } + +} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java index d693cd739b..46338fd4cf 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java @@ -17,7 +17,6 @@ package org.springframework.integration.sftp.config; import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; -import org.springframework.integration.file.config.RemoteFileOutboundChannelAdapterParser; /** * Provides namespace support for using SFTP. @@ -30,9 +29,10 @@ import org.springframework.integration.file.config.RemoteFileOutboundChannelAdap */ public class SftpNamespaceHandler extends AbstractIntegrationNamespaceHandler { + @Override public void init() { registerBeanDefinitionParser("inbound-channel-adapter", new SftpInboundChannelAdapterParser()); - registerBeanDefinitionParser("outbound-channel-adapter", new RemoteFileOutboundChannelAdapterParser()); + registerBeanDefinitionParser("outbound-channel-adapter", new SftpOutboundChannelAdapterParser()); registerBeanDefinitionParser("outbound-gateway", new SftpOutboundGatewayParser()); } diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundChannelAdapterParser.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundChannelAdapterParser.java new file mode 100644 index 0000000000..ff6344e6fb --- /dev/null +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundChannelAdapterParser.java @@ -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.sftp.config; + +import org.springframework.integration.file.config.RemoteFileOutboundChannelAdapterParser; +import org.springframework.integration.file.remote.RemoteFileOperations; +import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; + +/** + * Parser for SFTP Outbound Channel Adapters. + * + * @author Gary Russell + * @since 4.1 + * + */ +public class SftpOutboundChannelAdapterParser extends RemoteFileOutboundChannelAdapterParser { + + @Override + protected Class> getTemplateClass() { + return SftpRemoteFileTemplate.class; + } + +} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParser.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParser.java index 5dfb7a56b3..128c1daa1b 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParser.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParser.java @@ -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.sftp.config; import org.springframework.integration.file.config.AbstractRemoteFileOutboundGatewayParser; +import org.springframework.integration.file.remote.RemoteFileOperations; import org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilter; import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFilter; import org.springframework.integration.sftp.gateway.SftpOutboundGateway; +import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; /** * @author Gary Russell @@ -28,6 +30,7 @@ import org.springframework.integration.sftp.gateway.SftpOutboundGateway; */ public class SftpOutboundGatewayParser extends AbstractRemoteFileOutboundGatewayParser { + @Override public String getGatewayClassName() { return SftpOutboundGateway.class.getName(); } @@ -42,4 +45,9 @@ public class SftpOutboundGatewayParser extends AbstractRemoteFileOutboundGateway return SftpRegexPatternFileListFilter.class.getName(); } + @Override + protected Class> getTemplateClass() { + return SftpRemoteFileTemplate.class; + } + } diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/DefaultSftpSessionFactory.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/DefaultSftpSessionFactory.java index f2dbbebc50..7c27cba7be 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/DefaultSftpSessionFactory.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/DefaultSftpSessionFactory.java @@ -21,7 +21,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import org.springframework.beans.factory.BeanCreationException; import org.springframework.core.io.Resource; -import org.springframework.integration.file.remote.session.Session; import org.springframework.integration.file.remote.session.SessionFactory; import org.springframework.integration.file.remote.session.SharedSessionCapable; import org.springframework.util.Assert; @@ -314,7 +313,7 @@ public class DefaultSftpSessionFactory implements SessionFactory, Share @Override - public Session getSession() { + public SftpSession getSession() { Assert.hasText(this.host, "host must not be empty"); Assert.hasText(this.user, "user must not be empty"); Assert.isTrue(this.port >= 0, "port must be a positive number"); diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpRemoteFileTemplate.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpRemoteFileTemplate.java new file mode 100644 index 0000000000..1a32b8e1e6 --- /dev/null +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpRemoteFileTemplate.java @@ -0,0 +1,78 @@ +/* + * 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.sftp.session; + +import java.io.IOException; + +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 com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.ChannelSftp.LsEntry; +import com.jcraft.jsch.SftpException; + +/** + * SFTP version of {@code RemoteFileTemplate} providing type-safe access to + * the underlying ChannelSftp object. + * + * @author Gary Russell + * @since 4.1 + * + */ +public class SftpRemoteFileTemplate extends RemoteFileTemplate { + + public SftpRemoteFileTemplate(SessionFactory sessionFactory) { + super(sessionFactory); + } + + @SuppressWarnings("unchecked") + @Override + public T executeWithClient(final ClientCallback callback) { + return doExecuteWithClient((ClientCallback) callback); + } + + protected T doExecuteWithClient(final ClientCallback callback) { + return execute(new SessionCallback() { + + @Override + public T doInSession(Session session) throws IOException { + return callback.doWithClient((ChannelSftp) session.getClientInstance()); + } + }); + } + + @Override + public boolean exists(final String path) { + return executeWithClient(new ClientCallback() { + + @Override + public Boolean doWithClient(ChannelSftp client) { + try { + return client.stat(path) != null; + } + catch (SftpException e) { + return false; + } + } + }); + } + + + +} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpSession.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpSession.java index 9fa26b40b9..b574e58846 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpSession.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/session/SftpSession.java @@ -47,7 +47,7 @@ import com.jcraft.jsch.SftpException; * @author Gary Russell * @since 2.0 */ -class SftpSession implements Session { +public class SftpSession implements Session { private final Log logger = LogFactory.getLog(this.getClass()); @@ -159,6 +159,17 @@ class SftpSession implements Session { } } + @Override + public void append(InputStream inputStream, String destination) throws IOException { + Assert.state(this.channel != null, "session is not connected"); + try { + this.channel.put(inputStream, destination, ChannelSftp.APPEND); + } + catch (SftpException e) { + throw new NestedIOException("failed to write file", e); + } + } + @Override public void close() { this.closed = true; @@ -223,6 +234,17 @@ class SftpSession implements Session { return true; } + @Override + public boolean rmdir(String remoteDirectory) throws IOException { + try { + this.channel.rmdir(remoteDirectory); + } + catch (SftpException e) { + throw new NestedIOException("failed to remove remote directory '" + remoteDirectory + "'.", e); + } + return true; + } + @Override public boolean exists(String path) { try { @@ -251,4 +273,9 @@ class SftpSession implements Session { } } + @Override + public ChannelSftp getClientInstance() { + return this.channel; + } + } diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpServer.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpServer.java new file mode 100644 index 0000000000..a9965f4b78 --- /dev/null +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpServer.java @@ -0,0 +1,198 @@ +/* + * 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.sftp; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; + +import org.apache.sshd.SshServer; +import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.file.FileSystemView; +import org.apache.sshd.common.file.nativefs.NativeFileSystemFactory; +import org.apache.sshd.common.file.nativefs.NativeFileSystemView; +import org.apache.sshd.server.Command; +import org.apache.sshd.server.PasswordAuthenticator; +import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; +import org.apache.sshd.server.session.ServerSession; +import org.apache.sshd.server.sftp.SftpSubsystem; +import org.junit.rules.TemporaryFolder; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; +import org.springframework.util.SocketUtils; + +/** + * @author Gary Russell + * @since 4.1 + * + */ +public class TestSftpServer implements InitializingBean, DisposableBean { + + private final SshServer server = SshServer.setUpDefaultServer(); + + private final int port = SocketUtils.findAvailableTcpPort(); + + private final TemporaryFolder sftpFolder; + + private final TemporaryFolder localFolder; + + private volatile File sftpRootFolder; + + private volatile File sourceSftpDirectory; + + private volatile File targetSftpDirectory; + + private volatile File sourceLocalDirectory; + + private volatile File targetLocalDirectory; + + public TestSftpServer() { + this.sftpFolder = new TemporaryFolder() { + + @Override + public void create() throws IOException { + super.create(); + sftpRootFolder = this.newFolder("test"); + sourceSftpDirectory = new File(sftpRootFolder, "sftpSource"); + sourceSftpDirectory.mkdir(); + File file = new File(sourceSftpDirectory, "sftpSource1.txt"); + file.createNewFile(); + FileOutputStream fos = new FileOutputStream(file); + fos.write("source1".getBytes()); + fos.close(); + file = new File(sourceSftpDirectory, "sftpSource2.txt"); + file.createNewFile(); + fos = new FileOutputStream(file); + fos.write("source2".getBytes()); + fos.close(); + + File subSourceFtpDirectory = new File(sourceSftpDirectory, "subSftpSource"); + subSourceFtpDirectory.mkdir(); + file = new File(subSourceFtpDirectory, "subSftpSource1.txt"); + file.createNewFile(); + fos = new FileOutputStream(file); + fos.write("subSource1".getBytes()); + fos.close(); + + targetSftpDirectory = new File(sftpRootFolder, "sftpTarget"); + targetSftpDirectory.mkdir(); + } + }; + this.localFolder = new TemporaryFolder() { + + @Override + public void create() throws IOException { + super.create(); + File rootFolder = this.newFolder("test"); + sourceLocalDirectory = new File(rootFolder, "localSource"); + sourceLocalDirectory.mkdirs(); + File file = new File(sourceLocalDirectory, "localSource1.txt"); + file.createNewFile(); + file = new File(sourceLocalDirectory, "localSource2.txt"); + file.createNewFile(); + + File subSourceLocalDirectory = new File(sourceLocalDirectory, "subLocalSource"); + subSourceLocalDirectory.mkdir(); + file = new File(subSourceLocalDirectory, "subLocalSource1.txt"); + file.createNewFile(); + + targetLocalDirectory = new File(rootFolder, "slocalTarget"); + targetLocalDirectory.mkdir(); + } + }; + } + + @SuppressWarnings("unchecked") + @Override + public void afterPropertiesSet() throws Exception { + server.setPasswordAuthenticator(new PasswordAuthenticator() { + + @Override + public boolean authenticate(String arg0, String arg1, ServerSession arg2) { + return true; + } + }); + server.setPort(port); + server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("hostkey.ser")); + SftpSubsystem.Factory sftp = new SftpSubsystem.Factory(); + server.setSubsystemFactories(Arrays.>asList(sftp)); + server.setFileSystemFactory(new NativeFileSystemFactory() { + + @Override + public FileSystemView createFileSystemView(org.apache.sshd.common.Session session) { + return new NativeFileSystemView(session.getUsername(), false) { + + @Override + public String getVirtualUserDir() { + return sftpRootFolder.getAbsolutePath(); + } + }; + } + + }); + this.sftpFolder.create(); + this.localFolder.create(); + server.start(); + } + + @Override + public void destroy() throws Exception { + this.server.stop(); + this.sftpFolder.delete(); + this.localFolder.delete(); + } + + public File getSourceLocalDirectory() { + return this.sourceLocalDirectory; + } + + public File getTargetLocalDirectory() { + return this.targetLocalDirectory; + } + + public String getTargetLocalDirectoryName() { + return this.targetLocalDirectory.getAbsolutePath() + File.separator; + } + + public File getTargetSftpDirectory() { + return this.targetSftpDirectory; + } + + public void recursiveDelete(File file) { + File[] files = file.listFiles(); + if (files != null) { + for (File each : files) { + recursiveDelete(each); + } + } + if (!(file.equals(this.targetSftpDirectory) || file.equals(this.targetLocalDirectory))) { + file.delete(); + } + } + + public DefaultSftpSessionFactory getSessionFactory() { + DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true); + factory.setHost("localhost"); + factory.setPort(this.port); + factory.setUser("foo"); + factory.setPassword("foo"); + return factory; + } + +} diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpServerConfig.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpServerConfig.java new file mode 100644 index 0000000000..bcb29891bb --- /dev/null +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpServerConfig.java @@ -0,0 +1,40 @@ +/* + * 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.sftp; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; + +/** + * @author Gary Russell + * @since 4.1 + * + */ +@Configuration +public class TestSftpServerConfig { + + @Bean + public TestSftpServer sftpServer() { + return new TestSftpServer(); + } + + @Bean + public DefaultSftpSessionFactory sftpSessionFactory(TestSftpServer server) { + return sftpServer().getSessionFactory(); + } + +} diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java index 6c6b3db519..7d45a36af0 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java @@ -45,11 +45,11 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.integration.expression.ExpressionUtils; import org.springframework.integration.file.filters.CompositeFileListFilter; import org.springframework.integration.file.filters.FileListFilter; -import org.springframework.integration.file.remote.session.Session; import org.springframework.integration.metadata.PropertiesPersistingMetadataStore; import org.springframework.integration.sftp.filters.SftpPersistentAcceptOnceFileListFilter; import org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilter; import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; +import org.springframework.integration.sftp.session.SftpSession; import org.springframework.integration.sftp.session.SftpTestSessionFactory; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; @@ -93,7 +93,6 @@ public class SftpInboundRemoteFileSystemSynchronizerTests { ftpSessionFactory.setPassword("frog"); ftpSessionFactory.setHost("foo.com"); - SftpInboundFileSynchronizer synchronizer = spy(new SftpInboundFileSynchronizer(ftpSessionFactory)); synchronizer.setDeleteRemoteFiles(true); synchronizer.setPreserveTimestamp(true); @@ -168,7 +167,7 @@ public class SftpInboundRemoteFileSystemSynchronizerTests { } @Override - public Session getSession() { + public SftpSession getSession() { if (this.sftpEntries.size() == 0) { this.init(); } @@ -184,7 +183,8 @@ public class SftpInboundRemoteFileSystemSynchronizerTests { when(jschSession.openChannel("sftp")).thenReturn(channel); return SftpTestSessionFactory.createSftpSession(jschSession); - } catch (Exception e) { + } + catch (Exception e) { throw new RuntimeException("Failed to create mock sftp session", e); } } diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpOutboundTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpOutboundTests.java index af997447e5..d75aacc140 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpOutboundTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpOutboundTests.java @@ -56,6 +56,7 @@ import org.springframework.integration.file.remote.session.CachingSessionFactory import org.springframework.integration.file.remote.session.Session; import org.springframework.integration.file.remote.session.SessionFactory; import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; +import org.springframework.integration.sftp.session.SftpSession; import org.springframework.integration.sftp.session.SftpTestSessionFactory; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; @@ -375,7 +376,7 @@ public class SftpOutboundTests { public static class TestSftpSessionFactory extends DefaultSftpSessionFactory { @Override - public Session getSession() { + public SftpSession getSession() { try { ChannelSftp channel = mock(ChannelSftp.class); diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests-context.xml index d1df996a33..ad8633a758 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests-context.xml +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests-context.xml @@ -14,17 +14,17 @@ - - - - - - - - - - - + - - - - - - - + - - - - - - - - - - - - + + + + + + + + + diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests.java index 3f56861989..f63a44e8c0 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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,6 +17,7 @@ package org.springframework.integration.sftp.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; @@ -25,8 +26,6 @@ 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.mock; -import static org.mockito.Mockito.when; import java.io.ByteArrayOutputStream; import java.io.File; @@ -44,29 +43,28 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.file.FileHeaders; +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.sftp.session.SftpFileInfo; +import org.springframework.integration.sftp.TestSftpServer; +import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; +import org.springframework.integration.sftp.session.SftpRemoteFileTemplate; +import org.springframework.integration.support.MessageBuilder; 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.IfProfileValue; -import org.springframework.test.annotation.ProfileValueUtils; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.FileCopyUtils; import com.jcraft.jsch.ChannelSftp.LsEntry; -import com.jcraft.jsch.SftpATTRS; /** - * Run with -Dspring-profiles-active=realSSH to run with a real SSH server. - * - * Assumes ftptest account on localhost with the following directory tree in the user's root... + * Runs against an embedded SFTP Server with the following directory tree: * *
  *  $ tree sftpSource/
@@ -113,81 +111,30 @@ public class SftpServerOutboundTests {
 	private DirectChannel inboundMPutRecursiveFiltered;
 
 	@Autowired
-	private SessionFactory sessionFactory;
+	private DefaultSftpSessionFactory sessionFactory;
+
+	@Autowired
+	private DirectChannel appending;
+
+	@Autowired
+	private DirectChannel ignoring;
+
+	@Autowired
+	private DirectChannel failing;
+
+	@Autowired
+	private TestSftpServer sftpServer;
 
 	@Before
-	public void setup() throws Exception {
-		purge();
-		setUpMocksIfNeeded();
-	}
-
-	@SuppressWarnings({ "rawtypes", "unchecked" })
-	private void setUpMocksIfNeeded() throws IOException {
-		String profile = ProfileValueUtils.retrieveProfileValueSource(this.getClass()).get("spring.profiles.active");
-		boolean usingMocks = profile == null || ! profile.startsWith("realSSH");
-		if (usingMocks) {
-			Session session = mock(Session.class);
-			when(sessionFactory.getSession()).thenReturn(session);
-			LsEntry entry1 = mock(LsEntry.class);
-			SftpATTRS attrs1 = mock(SftpATTRS.class);
-			when(entry1.getAttrs()).thenReturn(attrs1);
-			when(entry1.getFilename()).thenReturn("sftpSource1.txt");
-			LsEntry entry2 = mock(LsEntry.class);
-			SftpATTRS attrs2 = mock(SftpATTRS.class);
-			when(entry2.getAttrs()).thenReturn(attrs2);
-			when(entry2.getFilename()).thenReturn("sftpSource2.txt");
-			LsEntry entry3 = mock(LsEntry.class);
-			when(entry3.getFilename()).thenReturn("subSftpSource");
-			SftpATTRS attrs3 = mock(SftpATTRS.class);
-			when(entry3.getAttrs()).thenReturn(attrs3);
-			when(attrs3.isDir()).thenReturn(true);
-			LsEntry entry4 = mock(LsEntry.class);
-			SftpATTRS attrs4 = mock(SftpATTRS.class);
-			when(entry4.getAttrs()).thenReturn(attrs4);
-			// recursion uses a DFA to update the filename to include the subdirectory
-			new DirectFieldAccessor(entry4).setPropertyValue("filename", "subSftpSource1.txt");
-			when(entry4.getFilename()).thenCallRealMethod();
-			when(session.list("sftpSource/sftpSource1.txt")).thenReturn(new LsEntry[] {
-					entry1
-			});
-			when(session.list("sftpSource/")).thenReturn(new LsEntry[] {
-					entry1, entry2, entry3
-			});
-			when(session.list("sftpSource/subSftpSource/")).thenReturn(new LsEntry[] {
-					entry4
-			});
-			when(session.list("sftpSource/subSftpSource/subSftpSource1.txt")).thenReturn(new LsEntry[] {
-					entry4
-			});
-		}
-	}
-
 	@After
-	public void purge() {
-		File local = new File("/tmp/sftpOutboundTests/");
-		purge(local);
-		local.delete();
-	}
-
-	private void purge(File local) {
-		File[] files = local.listFiles();
-		if (files != null) {
-			for (File file : files) {
-				if (file.isDirectory()) {
-					this.purge(file);
-				}
-				file.delete();
-			}
-		}
+	public void setup() {
+		this.sftpServer.recursiveDelete(sftpServer.getTargetLocalDirectory());
+		this.sftpServer.recursiveDelete(sftpServer.getTargetSftpDirectory());
 	}
 
 	@Test
 	public void testInt2866LocalDirectoryExpressionGET() {
-		Session session = null;
-		boolean sharedSession = "realSSHSharedSession".equals(System.getProperty("spring.profiles.active"));
-		if (sharedSession) {
-			session = this.sessionFactory.getSession();
-		}
+		Session session = this.sessionFactory.getSession();
 		String dir = "sftpSource/";
 		this.inboundGet.send(new GenericMessage(dir + "sftpSource1.txt"));
 		Message result = this.output.receive(1000);
@@ -203,11 +150,9 @@ public class SftpServerOutboundTests {
 		localFile = (File) result.getPayload();
 		assertThat(localFile.getPath().replaceAll(java.util.regex.Matcher.quoteReplacement(File.separator), "/"),
 				Matchers.containsString(dir.toUpperCase()));
-		if (sharedSession) {
-			Session session2 = this.sessionFactory.getSession();
-			assertSame(TestUtils.getPropertyValue(session, "targetSession.jschSession"),
-					TestUtils.getPropertyValue(session2, "targetSession.jschSession"));
-		}
+		Session session2 = this.sessionFactory.getSession();
+		assertSame(TestUtils.getPropertyValue(session, "jschSession"),
+				TestUtils.getPropertyValue(session2, "jschSession"));
 	}
 
 	@Test
@@ -294,7 +239,6 @@ public class SftpServerOutboundTests {
 	 * Only runs with a real server (see class javadocs).
 	 */
 	@Test
-	@IfProfileValue(name="spring.profiles.active", value="realSSH")
 	public void testInt3100RawGET() throws Exception {
 		Session session = this.sessionFactory.getSession();
 		ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -311,7 +255,6 @@ public class SftpServerOutboundTests {
 	}
 
 	@Test
-	@IfProfileValue(name="spring.profiles.active", value="realSSHSharedSession")
 	public void testInt3047ConcurrentSharedSession() throws Exception {
 		final Session session1 = this.sessionFactory.getSession();
 		final Session session2 = this.sessionFactory.getSession();
@@ -371,12 +314,11 @@ public class SftpServerOutboundTests {
 	}
 
 	@Test
-	@IfProfileValue(name="spring.profiles.active", value="realSSH")
 	public void testInt3088MPutNotRecursive() {
 		String dir = "sftpSource/";
 		this.inboundMGetRecursive.send(new GenericMessage(dir + "*"));
 		while (output.receive(0) != null) { }
-		this.inboundMPut.send(new GenericMessage(new File("/tmp/sftpOutboundTests/sftpSource")));
+		this.inboundMPut.send(new GenericMessage(this.sftpServer.getSourceLocalDirectory()));
 		@SuppressWarnings("unchecked")
 		Message> out = (Message>) this.output.receive(1000);
 		assertNotNull(out);
@@ -385,19 +327,18 @@ public class SftpServerOutboundTests {
 				not(equalTo(out.getPayload().get(1))));
 		assertThat(
 				out.getPayload().get(0),
-				anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt")));
+				anyOf(equalTo("sftpTarget/localSource1.txt"), equalTo("sftpTarget/localSource2.txt")));
 		assertThat(
 				out.getPayload().get(1),
-				anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt")));
+				anyOf(equalTo("sftpTarget/localSource1.txt"), equalTo("sftpTarget/localSource2.txt")));
 	}
 
 	@Test
-	@IfProfileValue(name="spring.profiles.active", value="realSSH")
 	public void testInt3088MPutRecursive() {
 		String dir = "sftpSource/";
 		this.inboundMGetRecursive.send(new GenericMessage(dir + "*"));
 		while (output.receive(0) != null) { }
-		this.inboundMPutRecursive.send(new GenericMessage(new File("/tmp/sftpOutboundTests/sftpSource")));
+		this.inboundMPutRecursive.send(new GenericMessage(this.sftpServer.getSourceLocalDirectory()));
 		@SuppressWarnings("unchecked")
 		Message> out = (Message>) this.output.receive(1000);
 		assertNotNull(out);
@@ -406,25 +347,24 @@ public class SftpServerOutboundTests {
 				not(equalTo(out.getPayload().get(1))));
 		assertThat(
 				out.getPayload().get(0),
-				anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt"),
-						equalTo("sftpTarget/subSftpSource/subSlocalTarget1.txt")));
+				anyOf(equalTo("sftpTarget/localSource1.txt"), equalTo("sftpTarget/localSource2.txt"),
+						equalTo("sftpTarget/subLocalSource/subLocalSource1.txt")));
 		assertThat(
 				out.getPayload().get(1),
-				anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt"),
-						equalTo("sftpTarget/subSftpSource/subSlocalTarget1.txt")));
+				anyOf(equalTo("sftpTarget/localSource1.txt"), equalTo("sftpTarget/localSource2.txt"),
+						equalTo("sftpTarget/subLocalSource/subLocalSource1.txt")));
 		assertThat(
 				out.getPayload().get(2),
-				anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt"),
-						equalTo("sftpTarget/subSftpSource/subSlocalTarget1.txt")));
+				anyOf(equalTo("sftpTarget/localSource1.txt"), equalTo("sftpTarget/localSource2.txt"),
+						equalTo("sftpTarget/subLocalSource/subLocalSource1.txt")));
 	}
 
 	@Test
-	@IfProfileValue(name="spring.profiles.active", value="realSSH")
 	public void testInt3088MPutRecursiveFiltered() {
 		String dir = "sftpSource/";
 		this.inboundMGetRecursive.send(new GenericMessage(dir + "*"));
 		while (output.receive(0) != null) { }
-		this.inboundMPutRecursiveFiltered.send(new GenericMessage(new File("/tmp/sftpOutboundTests/sftpSource")));
+		this.inboundMPutRecursiveFiltered.send(new GenericMessage(this.sftpServer.getSourceLocalDirectory()));
 		@SuppressWarnings("unchecked")
 		Message> out = (Message>) this.output.receive(1000);
 		assertNotNull(out);
@@ -433,12 +373,47 @@ public class SftpServerOutboundTests {
 				not(equalTo(out.getPayload().get(1))));
 		assertThat(
 				out.getPayload().get(0),
-				anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt"),
-						equalTo("sftpTarget/subSftpSource/subSlocalTarget1.txt")));
+				anyOf(equalTo("sftpTarget/localSource1.txt"), equalTo("sftpTarget/localSource2.txt"),
+						equalTo("sftpTarget/subLocalSource/subLocalSource1.txt")));
 		assertThat(
 				out.getPayload().get(1),
-				anyOf(equalTo("sftpTarget/slocalTarget1.txt"), equalTo("sftpTarget/slocalTarget2.txt"),
-						equalTo("sftpTarget/subSftpSource/subSlocalTarget1.txt")));
+				anyOf(equalTo("sftpTarget/localSource1.txt"), equalTo("sftpTarget/localSource2.txt"),
+						equalTo("sftpTarget/subLocalSource/subLocalSource1.txt")));
+	}
+
+	@Test
+	public void testInt3412FileMode() {
+		Message m = MessageBuilder.withPayload("foo")
+				.setHeader(FileHeaders.FILENAME, "appending.txt")
+				.build();
+		appending.send(m);
+		appending.send(m);
+
+		SftpRemoteFileTemplate template = new SftpRemoteFileTemplate(sessionFactory);
+		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(SftpRemoteFileTemplate template) {
+		LsEntry[] files = template.execute(new SessionCallback() {
+
+			@Override
+			public LsEntry[] doInSession(Session session) throws IOException {
+				return session.list("sftpTarget/appending.txt");
+			}
+		});
+		assertEquals(1, files.length);
+		assertEquals(6, files[0].getAttrs().getSize());
 	}
 
 }
diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/session/SftpRemoteFileTemplateTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/session/SftpRemoteFileTemplateTests.java
new file mode 100644
index 0000000000..6279d1fb1c
--- /dev/null
+++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/session/SftpRemoteFileTemplateTests.java
@@ -0,0 +1,118 @@
+/*
+ * 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.sftp.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.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.sftp.TestSftpServer;
+import org.springframework.integration.sftp.TestSftpServerConfig;
+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 com.jcraft.jsch.ChannelSftp;
+import com.jcraft.jsch.ChannelSftp.LsEntry;
+import com.jcraft.jsch.SftpATTRS;
+import com.jcraft.jsch.SftpException;
+
+/**
+ * @author Gary Russell
+ * @since 4.1
+ *
+ */
+@ContextConfiguration(classes=TestSftpServerConfig.class)
+@RunWith(SpringJUnit4ClassRunner.class)
+@DirtiesContext
+public class SftpRemoteFileTemplateTests {
+
+	@Autowired
+	private TestSftpServer sftpServer;
+
+	@Autowired
+	private DefaultSftpSessionFactory sessionFactory;
+
+	@Before
+	@After
+	public void setup() {
+		this.sftpServer.recursiveDelete(sftpServer.getTargetLocalDirectory());
+		this.sftpServer.recursiveDelete(sftpServer.getTargetSftpDirectory());
+	}
+
+	@Test
+	public void testINT3412AppendStatRmdir() {
+		SftpRemoteFileTemplate template = new SftpRemoteFileTemplate(sessionFactory);
+		DefaultFileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
+		fileNameGenerator.setExpression("'foobar.txt'");
+		template.setFileNameGenerator(fileNameGenerator);
+		template.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
+		template.setUseTemporaryFileName(false);
+		template.execute(new SessionCallback() {
+
+			@Override
+			public Boolean doInSession(Session session) throws IOException {
+				session.mkdir("foo/");
+				return session.mkdir("foo/bar/");
+			}
+
+		});
+		template.append(new GenericMessage("foo"));
+		template.append(new GenericMessage("bar"));
+		assertTrue(template.exists("foo/foobar.txt"));
+		template.executeWithClient(new ClientCallbackWithoutResult() {
+
+			@Override
+			public void doWithClientWithoutResult(ChannelSftp client) {
+				try {
+					SftpATTRS file = client.lstat("foo/foobar.txt");
+					assertEquals(6, file.getSize());
+				}
+				catch (SftpException e) {
+					throw new RuntimeException(e);
+				}
+			}
+		});
+		template.execute(new SessionCallbackWithoutResult() {
+
+			@Override
+			public void doInSessionWithoutResult(Session session) throws IOException {
+				assertTrue(session.remove("foo/foobar.txt"));
+				assertTrue(session.rmdir("foo/bar/"));
+				LsEntry[] files = session.list("foo/");
+				assertEquals(0, files.length);
+				assertTrue(session.rmdir("foo/"));
+			}
+		});
+		assertFalse(template.exists("foo"));
+	}
+
+}
diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/session/SftpTestSessionFactory.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/session/SftpTestSessionFactory.java
index 5d7ee952c1..21ee17f44a 100644
--- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/session/SftpTestSessionFactory.java
+++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/session/SftpTestSessionFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2010 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.
@@ -15,17 +15,15 @@
  */
 package org.springframework.integration.sftp.session;
 
-import org.springframework.integration.file.remote.session.Session;
-
-import com.jcraft.jsch.ChannelSftp.LsEntry;
 
 /**
  * @author Oleg Zhurakousky
- * 
+ * @author Gary Russell
+ *
  */
 public class SftpTestSessionFactory {
 
-	public static Session createSftpSession(com.jcraft.jsch.Session jschSession) {
+	public static SftpSession createSftpSession(com.jcraft.jsch.Session jschSession) {
 		SftpSession sftpSession = new SftpSession(jschSession);
 		sftpSession.connect();
 		return sftpSession;
diff --git a/src/reference/docbook/ftp.xml b/src/reference/docbook/ftp.xml
index 75a392a637..9dd8f90a9e 100644
--- a/src/reference/docbook/ftp.xml
+++ b/src/reference/docbook/ftp.xml
@@ -329,7 +329,9 @@ protected void postProcessClientBeforeConnect(T client) throws IOException {
     auto-create-directory="true"
     remote-directory-expression="headers.['remote_dir']"
     temporary-remote-directory-expression="headers.['temp_remote_dir']"
-    filename-generator="fileNameGenerator"/>]]>
+    filename-generator="fileNameGenerator"
+    use-temporary-filename="true"
+    mode="REPLACE"/>]]>
 
 	As you can see from the configuration above you can configure an FTP Outbound Channel Adapter via the
 	outbound-channel-adapter element while also providing values for various attributes such as filename-generator
@@ -356,7 +358,14 @@ protected void postProcessClientBeforeConnect(T client) throws IOException {
   	instead of remote-directory="/foo/bar")
   	
   	
-
+    
+    Starting with version 4.1, you can specify the mode when transferring the file. By default,
+    an existing file will be overwritten; the modes are defined on enum
+    FileExistsMode, having values REPLACE (default), APPEND,
+    IGNORE, and FAIL. With IGNORE and FAIL, the file is not
+    transferred; FAIL causes an exception to be thrown whereas IGNORE silently
+    ignores the transfer (although a DEBUG log entry is produced).
+    
     
       Avoiding Partially Written Files
     
@@ -635,7 +644,15 @@ protected void postProcessClientBeforeConnect(T client) throws IOException {
 		InputStream), remove, and rename files. In addition an execute
 		method is provided allowing the caller to execute multiple operations on the session. In all cases,
 		the template takes care of reliably closing the session.
-		For more information, refer to the javadocs for RemoteFileTemplate.
+		For more information, refer to the javadocs
+		for RemoteFileTemplate There is a subclass for FTP:
+		FtpRemoteFileTemplate. 
+	
+	
+		Additional methods were added in version 4.1 including getClientInstance()
+		which provides access to the underlying FTPClient enabling access to low-level
+		APIs.
 	
   
 
diff --git a/src/reference/docbook/sftp.xml b/src/reference/docbook/sftp.xml
index 1ae435469d..e8e8607d16 100644
--- a/src/reference/docbook/sftp.xml
+++ b/src/reference/docbook/sftp.xml
@@ -255,12 +255,20 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp
    
RemoteFileTemplate - Starting with Spring Integration version 3.0 a new abstraction is provided over the + Starting with Spring Integration version 3.0, a new abstraction is provided over the SftpSession object. The template provides methods to send, retrieve (as an InputStream), remove, and rename files. In addition an execute method is provided allowing the caller to execute multiple operations on the session. In all cases, the template takes care of reliably closing the session. - For more information, refer to the javadocs for RemoteFileTemplate. + For more information, refer to the javadocs + for RemoteFileTemplate There is a subclass for SFTP: + SftpRemoteFileTemplate. + + + Additional methods were added in version 4.1 including getClientInstance() + which provides access to the underlying ChannelSftp enabling access to low-level + APIs.
@@ -411,11 +419,15 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp the file contents; 3) java.lang.String - text that represents the file contents. ]]> + session-factory="sftpSessionFactory" + channel="inputChannel" + charset="UTF-8" + remote-file-separator="/" + remote-directory="foo/bar" + remote-filename-generator-expression="payload.getName() + '-foo'" + filename-generator="fileNameGenerator" + use-temporary-filename="true" + mode="REPLACE"/>]]> As you can see from the configuration above you can configure the SFTP Outbound Channel Adapter via the outbound-channel-adapter element. @@ -434,6 +446,15 @@ xsi:schemaLocation="http://www.springframework.org/schema/integration/sftp value that computes the file name based on its original name while also appending a suffix: '-foo'. + + Starting with version 4.1, you can specify the mode when transferring the file. By default, + an existing file will be overwritten; the modes are defined on enum + FileExistsMode, having values REPLACE (default), APPEND, + IGNORE, and FAIL. With IGNORE and FAIL, the file is not + transferred; FAIL causes an exception to be thrown whereas IGNORE silently + ignores the transfer (although a DEBUG log entry is produced). + + Avoiding Partially Written Files diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 6d939a4a89..8b44fb1568 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -74,6 +74,19 @@ See for more information. +
+ FTP/SFTP Adapter Changes + + The FTP and SFTP outbound channel adapters now support appending to remote files, as + well as taking specific actions when a remote file already exists. The remote file + templates now also support this as well as rmdir() and exists(). + In addition, the remote file templates provide access to the underlying client object + enabling access to low-level APIs. + + + See and for more information. + +
Splitter and Iterator