Merge remote-tracking branch 'upstream/master' into 4.0.0-WIP

Conflicts:
	spring-integration-core/src/main/java/org/springframework/integration/gateway/MessagingGatewaySupport.java
	spring-integration-core/src/main/java/org/springframework/integration/support/channel/BeanFactoryChannelResolver.java
	spring-integration-core/src/main/java/org/springframework/integration/util/MessagingMethodInvokerHelper.java
	spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java
	spring-integration-core/src/test/java/org/springframework/integration/config/annotation/AggregatorAnnotationTests.java
	spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusTests.java
	spring-integration-file/src/main/java/org/springframework/integration/file/DefaultFileNameGenerator.java
	spring-integration-file/src/main/java/org/springframework/integration/file/remote/handler/FileTransferringMessageHandler.java
	spring-integration-file/src/test/java/org/springframework/integration/file/remote/gateway/RemoteFileOutboundGatewayTests.java
	spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests.java
	spring-integration-ftp/src/test/java/org/springframework/integration/ftp/inbound/FtpInboundRemoteFileSystemSynchronizerTests.java
	spring-integration-ftp/src/test/java/org/springframework/integration/ftp/outbound/FtpServerOutboundTests.java
	spring-integration-groovy/src/main/java/org/springframework/integration/groovy/GroovyScriptExecutingMessageProcessor.java
	spring-integration-http/src/test/java/org/springframework/integration/http/outbound/UriVariableExpressionTests.java
	spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/HelloWorldInterceptor.java
	spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests.java
	spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/MongoDbMessageGroupStoreTests.java
	spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/store/MongoDbMessageStoreTests.java
	spring-integration-redis/src/test/java/org/springframework/integration/redis/config/RedisOutboundChannelAdapterParserTests.java
	spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests.java
	spring-integration-sftp/src/test/java/org/springframework/integration/sftp/inbound/SftpInboundRemoteFileSystemSynchronizerTests.java
	spring-integration-sftp/src/test/java/org/springframework/integration/sftp/outbound/SftpServerOutboundTests.java

Resolved.
This commit is contained in:
Gary Russell
2013-11-25 17:59:51 -05:00
191 changed files with 8774 additions and 2707 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -18,8 +18,11 @@ package org.springframework.integration.file;
import java.io.File;
import org.springframework.messaging.Message;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.util.AbstractExpressionEvaluator;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -34,13 +37,17 @@ import org.springframework.util.StringUtils;
* associated with the header if no expression has been provided), it checks if
* the Message payload is a File instance, and if so, it uses the same name.
* Finally, it falls back to the Message ID and adds the suffix '.msg'.
*
*
* @author Mark Fisher
* @author Gary Russell
*/
public class DefaultFileNameGenerator extends AbstractExpressionEvaluator implements FileNameGenerator {
private volatile String expression = "headers['" + FileHeaders.FILENAME + "']";
private static final String DEFAULT_EXPRESSION = "headers['" + FileHeaders.FILENAME + "']";
private final static ExpressionParser parser = new SpelExpressionParser();
private volatile Expression expression = parser.parseExpression(DEFAULT_EXPRESSION);
/**
* Specify an expression to be evaluated against the Message
@@ -48,7 +55,7 @@ public class DefaultFileNameGenerator extends AbstractExpressionEvaluator implem
*/
public void setExpression(String expression) {
Assert.hasText(expression, "expression must not be empty");
this.expression = expression;
this.expression = parser.parseExpression(expression);
}
/**
@@ -57,9 +64,10 @@ public class DefaultFileNameGenerator extends AbstractExpressionEvaluator implem
*/
public void setHeaderName(String headerName) {
Assert.notNull(headerName, "'headerName' must not be null");
this.expression = "headers['" + headerName + "']";
this.expression = parser.parseExpression("headers['" + headerName + "']");
}
@Override
public String generateFileName(Message<?> message) {
Object filenameProperty = this.evaluateExpression(this.expression, message);
if (filenameProperty instanceof String && StringUtils.hasText((String) filenameProperty)) {

View File

@@ -23,6 +23,8 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.ExpressionFactoryBean;
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.util.StringUtils;
/**
@@ -42,18 +44,20 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
BeanDefinition templateDefinition = FileParserUtils.parseRemoteFileTemplate(element, parserContext, false);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(getGatewayClassName());
builder.addConstructorArgReference(element.getAttribute("session-factory"));
builder.addConstructorArgValue(templateDefinition);
builder.addConstructorArgValue(element.getAttribute("command"));
builder.addConstructorArgValue(element.getAttribute(EXPRESSION_ATTRIBUTE));
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "command-options", "options");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "temporary-file-suffix");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout", "sendTimeout");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel");
this.configureFilter(builder, element, parserContext);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "remote-file-separator");
this.configureFilter(builder, element, parserContext, "filter", "filename", "filter");
this.configureFilter(builder, element, parserContext, "mput-filter", "mput", "mputFilter");
BeanDefinition localDirExpressionDef = IntegrationNamespaceUtils
.createExpressionDefinitionFromValueOrExpression("local-directory", "local-directory-expression",
@@ -74,10 +78,11 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo
return builder;
}
protected void configureFilter(BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
String filter = element.getAttribute("filter");
String fileNamePattern = element.getAttribute("filename-pattern");
String fileNameRegex = element.getAttribute("filename-regex");
protected void configureFilter(BeanDefinitionBuilder builder, Element element, ParserContext parserContext,
String filterAttribute, String patternPrefix, String propertyName) {
String filter = element.getAttribute(filterAttribute);
String fileNamePattern = element.getAttribute(patternPrefix + "-pattern");
String fileNameRegex = element.getAttribute(patternPrefix + "-regex");
boolean hasFilter = StringUtils.hasText(filter);
boolean hasFileNamePattern = StringUtils.hasText(fileNamePattern);
boolean hasFileNameRegex = StringUtils.hasText(fileNameRegex);
@@ -85,23 +90,27 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo
count += hasFileNamePattern ? 1 : 0;
count += hasFileNameRegex ? 1 : 0;
if (count > 1) {
parserContext.getReaderContext().error("at most one of 'filename-pattern', " +
"'filename-regex', or 'filter' is allowed on remote file inbound adapter", element);
parserContext.getReaderContext().error("at most one of '" + patternPrefix + "-pattern', " +
"'" + patternPrefix + "-regex', or '" + filterAttribute + "' is allowed on a remote file outbound gateway", element);
}
else if (hasFilter) {
builder.addPropertyReference("filter", filter);
builder.addPropertyReference(propertyName, filter);
}
else if (hasFileNamePattern) {
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.genericBeanDefinition(
this.getSimplePatternFileListFilterClassName());
"filter".equals(filterAttribute) ?
this.getSimplePatternFileListFilterClassName() :
SimplePatternFileListFilter.class.getName());
filterBuilder.addConstructorArgValue(fileNamePattern);
builder.addPropertyValue("filter", filterBuilder.getBeanDefinition());
builder.addPropertyValue(propertyName, filterBuilder.getBeanDefinition());
}
else if (hasFileNameRegex) {
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.genericBeanDefinition(
this.getRegexPatternFileListFilterClassName());
"filter".equals(filterAttribute) ?
this.getRegexPatternFileListFilterClassName() :
RegexPatternFileListFilter.class.getName());
filterBuilder.addConstructorArgValue(fileNameRegex);
builder.addPropertyValue("filter", filterBuilder.getBeanDefinition());
builder.addPropertyValue(propertyName, filterBuilder.getBeanDefinition());
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 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.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
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.util.StringUtils;
/**
* @author Oleg Zhurakousky
* @author Mark Fisher
* @author David Turanski
* @author Gary Russell
* @since 3.0
*
*/
public final class FileParserUtils {
private FileParserUtils() {
}
public static BeanDefinition parseRemoteFileTemplate(Element element, ParserContext parserContext,
boolean atLeastOneRemoteDirectoryAttributeRequired) {
BeanDefinitionBuilder templateBuilder = BeanDefinitionBuilder.genericBeanDefinition(RemoteFileTemplate.class);
templateBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
// configure MessageHandler properties
IntegrationNamespaceUtils.setValueIfAttributeDefined(templateBuilder, element, "temporary-file-suffix");
IntegrationNamespaceUtils.setValueIfAttributeDefined(templateBuilder, element, "use-temporary-file-name");
IntegrationNamespaceUtils.setValueIfAttributeDefined(templateBuilder, element, "auto-create-directory");
BeanDefinition expressionDef =
IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("remote-directory",
"remote-directory-expression", parserContext, element, atLeastOneRemoteDirectoryAttributeRequired);
if (expressionDef != null) {
templateBuilder.addPropertyValue("remoteDirectoryExpression", expressionDef);
}
expressionDef = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("temporary-remote-directory",
"temporary-remote-directory-expression", parserContext, element, false);
if (expressionDef != null) {
templateBuilder.addPropertyValue("temporaryRemoteDirectoryExpression", expressionDef);
}
// configure remote FileNameGenerator
String remoteFileNameGenerator = element.getAttribute("remote-filename-generator");
String remoteFileNameGeneratorExpression = element.getAttribute("remote-filename-generator-expression");
boolean hasRemoteFileNameGenerator = StringUtils.hasText(remoteFileNameGenerator);
boolean hasRemoteFileNameGeneratorExpression = StringUtils.hasText(remoteFileNameGeneratorExpression);
if (hasRemoteFileNameGenerator || hasRemoteFileNameGeneratorExpression) {
if (hasRemoteFileNameGenerator && hasRemoteFileNameGeneratorExpression) {
parserContext.getReaderContext().error(
"at most one of 'remote-filename-generator-expression' or 'remote-filename-generator' "
+ "is allowed on a remote file outbound adapter", element);
}
if (hasRemoteFileNameGenerator) {
templateBuilder.addPropertyReference("fileNameGenerator", remoteFileNameGenerator);
}
else {
BeanDefinitionBuilder fileNameGeneratorBuilder = BeanDefinitionBuilder
.genericBeanDefinition(DefaultFileNameGenerator.class);
fileNameGeneratorBuilder.addPropertyValue("expression", remoteFileNameGeneratorExpression);
templateBuilder.addPropertyValue("fileNameGenerator", fileNameGeneratorBuilder.getBeanDefinition());
}
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(templateBuilder, element, "charset");
templateBuilder.addPropertyValue("remoteFileSeparator", element.getAttribute("remote-file-separator"));
return templateBuilder.getBeanDefinition();
}
}

View File

@@ -18,19 +18,12 @@ package org.springframework.integration.file.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.config.ExpressionFactoryBean;
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
import org.springframework.util.StringUtils;
/**
* @author Oleg Zhurakousky
@@ -45,71 +38,10 @@ public class RemoteFileOutboundChannelAdapterParser extends AbstractOutboundChan
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
BeanDefinitionBuilder handlerBuilder = BeanDefinitionBuilder.genericBeanDefinition(FileTransferringMessageHandler.class);
handlerBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
// configure MessageHandler properties
BeanDefinition templateDefinition = FileParserUtils.parseRemoteFileTemplate(element, parserContext, true);
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "temporary-file-suffix");
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "use-temporary-file-name");
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "auto-create-directory");
this.configureRemoteDirectories(element, handlerBuilder);
// configure remote FileNameGenerator
String remoteFileNameGenerator = element.getAttribute("remote-filename-generator");
String remoteFileNameGeneratorExpression = element.getAttribute("remote-filename-generator-expression");
boolean hasRemoteFileNameGenerator = StringUtils.hasText(remoteFileNameGenerator);
boolean hasRemoteFileNameGeneratorExpression = StringUtils.hasText(remoteFileNameGeneratorExpression);
if (hasRemoteFileNameGenerator || hasRemoteFileNameGeneratorExpression) {
if (hasRemoteFileNameGenerator && hasRemoteFileNameGeneratorExpression) {
throw new BeanDefinitionStoreException("at most one of 'remote-filename-generator-expression' or 'remote-filename-generator' " +
"is allowed on a remote file outbound adapter");
}
if (hasRemoteFileNameGenerator) {
handlerBuilder.addPropertyReference("fileNameGenerator", remoteFileNameGenerator);
}
else {
BeanDefinitionBuilder fileNameGeneratorBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultFileNameGenerator.class);
fileNameGeneratorBuilder.addPropertyValue("expression", remoteFileNameGeneratorExpression);
handlerBuilder.addPropertyValue("fileNameGenerator", fileNameGeneratorBuilder.getBeanDefinition());
}
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(handlerBuilder, element, "charset");
handlerBuilder.addPropertyValue("remoteFileSeparator", element.getAttribute("remote-file-separator"));
handlerBuilder.addConstructorArgValue(templateDefinition);
return handlerBuilder.getBeanDefinition();
}
private void configureRemoteDirectories(Element element, BeanDefinitionBuilder handlerBuilder){
this.doConfigureRemoteDirectory(element, handlerBuilder, "remote-directory", "remote-directory-expression", "remoteDirectoryExpression", true);
this.doConfigureRemoteDirectory(element, handlerBuilder, "temporary-remote-directory", "temporary-remote-directory-expression", "temporaryRemoteDirectoryExpression", false);
}
private void doConfigureRemoteDirectory(Element element, BeanDefinitionBuilder handlerBuilder,
String directoryAttribute, String directoryExpressionAttribute,
String directoryExpressionPropertyName, boolean atLeastOneRequired){
String remoteDirectory = element.getAttribute(directoryAttribute);
String remoteDirectoryExpression = element.getAttribute(directoryExpressionAttribute);
boolean hasRemoteDirectory = StringUtils.hasText(remoteDirectory);
boolean hasRemoteDirectoryExpression = StringUtils.hasText(remoteDirectoryExpression);
if (atLeastOneRequired){
if (!(hasRemoteDirectory ^ hasRemoteDirectoryExpression)) {
throw new BeanDefinitionStoreException("exactly one of '" + directoryAttribute + "' or '" + directoryExpressionAttribute + "' " +
"is required on a remote file outbound adapter");
}
}
BeanDefinition remoteDirectoryExpressionDefinition = null;
if (hasRemoteDirectory) {
remoteDirectoryExpressionDefinition = new RootBeanDefinition(LiteralExpression.class);
remoteDirectoryExpressionDefinition.getConstructorArgumentValues().addGenericArgumentValue(remoteDirectory);
}
else if (hasRemoteDirectoryExpression) {
remoteDirectoryExpressionDefinition = new RootBeanDefinition(ExpressionFactoryBean.class);
remoteDirectoryExpressionDefinition.getConstructorArgumentValues().addGenericArgumentValue(remoteDirectoryExpression);
}
if (remoteDirectoryExpressionDefinition != null){
handlerBuilder.addPropertyValue(directoryExpressionPropertyName, remoteDirectoryExpressionDefinition);
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 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.filters;
import org.springframework.integration.metadata.MetadataStore;
import org.springframework.util.Assert;
/**
* Stores "seen" files in a MetadataStore to survive application restarts.
* The default key is 'prefix' plus the absolute file name; value is the timestamp of the file.
* Files are deemed as already 'seen' if they exist in the store and have the
* same modified time as the current file.
*
* @author Gary Russell
* @since 3.0
*
*/
public abstract class AbstractPersistentAcceptOnceFileListFilter<F> extends AbstractFileListFilter<F> {
protected final MetadataStore store;
protected final String prefix;
private final Object monitor = new Object();
public AbstractPersistentAcceptOnceFileListFilter(MetadataStore store, String prefix) {
Assert.notNull(store, "'store' cannot be null");
Assert.notNull(prefix, "'prefix' cannot be null");
this.store = store;
this.prefix = prefix;
}
@Override
protected boolean accept(F file) {
String key = buildKey(file);
synchronized(monitor) {
String value = store.get(key);
if (value != null && isEqual(file, value)) {
return false;
}
store.put(key, value(file));
}
return true;
}
/**
* The default value stored for the key is the last modified date.
* @param file The file.
* @return The value to store for the file.
*/
private String value(F file) {
return Long.toString(this.modified(file));
}
/**
* Override this method if you wish to use something other than the
* modified timestamp to determine equality.
* @param file The file.
* @param value The current value for the key in the store.
* @return true if equal.
*/
protected boolean isEqual(F file, String value) {
return Long.valueOf(value).longValue() == this.modified(file);
}
/**
* The default key is the {@link #prefix} plus the full filename.
* @param file The file.
* @return The key.
*/
protected String buildKey(F file) {
return this.prefix + this.fileName(file);
}
protected abstract long modified(F file);
protected abstract String fileName(F file);
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 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.filters;
import java.io.File;
import org.springframework.integration.metadata.MetadataStore;
/**
* @author Gary Russell
* @since 3.0
*
*/
public class FileSystemPersistentAcceptOnceFileListFilter extends AbstractPersistentAcceptOnceFileListFilter<File> {
public FileSystemPersistentAcceptOnceFileListFilter(MetadataStore store, String prefix) {
super(store, prefix);
}
@Override
protected long modified(File file) {
return file.lastModified();
}
@Override
protected String fileName(File file) {
return file.getAbsolutePath();
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 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;
import java.io.IOException;
import java.io.InputStream;
/**
* Callback for stream-based file retrieval using a RemoteFileOperations.
*
* @author Gary Russell
* @since 3.0
*
*/
public interface InputStreamCallback {
/**
* Called with the InputStream for the remote file. The caller will
* take care of closing the stream and finalizing the file retrieval operation after
* this method exits.
*
* @param stream The InputStream.
* @throws IOException
*/
void doWithInputStream(InputStream stream) throws IOException;
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 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;
import org.springframework.messaging.Message;
/**
* Strategy for performing operations on remote files.
*
* @author Gary Russell
* @since 3.0
*
*/
public interface RemoteFileOperations<F> {
/**
* Send a file to a remote server, based on information in a message.
*
* @param message The message.
* @return The remote path, or null if no local file was found.
* @throws Exception
*/
String send(Message<?> message);
/**
* Send a file to a remote server, based on information in a message.
* 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.
* @throws Exception
*/
String send(Message<?> message, String subDirectory);
/**
* Retrieve a remote file as an InputStream, based on information in a message.
*
* @param callback the callback.
* @return true if the operation was successful.
*/
boolean get(Message<?> message, InputStreamCallback callback);
/**
* Remove a remote file.
*
* @param path The full path to the file.
* @return true when successful
*/
boolean remove(String path);
/**
* Rename a remote file, creating directories if needed.
*
* @param fromPath The current path.
* @param toPath The new path.
*/
void rename(String fromPath, String toPath);
/**
* Execute the callback's doInSession method after obtaining a session.
* Reliably closes the session when the method exits.
*
* @param callback the SessionCallback.
* @return The result of the callback method.
*/
<T> T execute(SessionCallback<F, T> callback);
}

View File

@@ -0,0 +1,423 @@
/*
* Copyright 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;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.expression.Expression;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* @author Iwein Fuld
* @author Mark Fisher
* @author Josh Long
* @author Oleg Zhurakousky
* @author David Turanski
* @author Gary Russell
* @since 3.0
*
*/
public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, InitializingBean, BeanFactoryAware {
private final Log logger = LogFactory.getLog(this.getClass());
/**
* the {@link SessionFactory} for acquiring remote file Sessions.
*/
private final SessionFactory<F> sessionFactory;
private volatile String temporaryFileSuffix =".writing";
private volatile boolean autoCreateDirectory = false;
private volatile boolean useTemporaryFileName = true;
private volatile ExpressionEvaluatingMessageProcessor<String> directoryExpressionProcessor;
private volatile ExpressionEvaluatingMessageProcessor<String> temporaryDirectoryExpressionProcessor;
private volatile ExpressionEvaluatingMessageProcessor<String> fileNameProcessor;
private volatile FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
private volatile boolean fileNameGeneratorSet;
private volatile String charset = "UTF-8";
private volatile String remoteFileSeparator = "/";
private volatile boolean hasExplicitlySetSuffix;
private volatile BeanFactory beanFactory;
public RemoteFileTemplate(SessionFactory<F> sessionFactory) {
Assert.notNull(sessionFactory, "sessionFactory must not be null");
this.sessionFactory = sessionFactory;
}
public void setAutoCreateDirectory(boolean autoCreateDirectory) {
this.autoCreateDirectory = autoCreateDirectory;
}
public void setRemoteFileSeparator(String remoteFileSeparator) {
Assert.notNull(remoteFileSeparator, "'remoteFileSeparator' must not be null");
this.remoteFileSeparator = remoteFileSeparator;
}
public final String getRemoteFileSeparator() {
return remoteFileSeparator;
}
public void setRemoteDirectoryExpression(Expression remoteDirectoryExpression) {
Assert.notNull(remoteDirectoryExpression, "remoteDirectoryExpression must not be null");
this.directoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor<String>(remoteDirectoryExpression, String.class);
}
public void setTemporaryRemoteDirectoryExpression(Expression temporaryRemoteDirectoryExpression) {
Assert.notNull(temporaryRemoteDirectoryExpression, "temporaryRemoteDirectoryExpression must not be null");
this.temporaryDirectoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor<String>(temporaryRemoteDirectoryExpression, String.class);
}
public void setFileNameExpression(Expression fileNameExpression) {
Assert.notNull(fileNameExpression, "fileNameExpression must not be null");
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor<String>(fileNameExpression, String.class);
}
public String getTemporaryFileSuffix() {
return this.temporaryFileSuffix;
}
public boolean isUseTemporaryFileName() {
return useTemporaryFileName;
}
public void setUseTemporaryFileName(boolean useTemporaryFileName) {
this.useTemporaryFileName = useTemporaryFileName;
}
public void setFileNameGenerator(FileNameGenerator fileNameGenerator) {
this.fileNameGenerator = (fileNameGenerator != null) ? fileNameGenerator : new DefaultFileNameGenerator();
this.fileNameGeneratorSet = fileNameGenerator != null;
}
public void setCharset(String charset) {
this.charset = charset;
}
public void setTemporaryFileSuffix(String temporaryFileSuffix) {
Assert.notNull(temporaryFileSuffix, "'temporaryFileSuffix' must not be null");
this.hasExplicitlySetSuffix = true;
this.temporaryFileSuffix = temporaryFileSuffix;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
BeanFactory beanFactory = this.beanFactory;
if (beanFactory != null) {
if (this.directoryExpressionProcessor != null) {
this.directoryExpressionProcessor.setBeanFactory(beanFactory);
}
if (this.temporaryDirectoryExpressionProcessor != null) {
this.temporaryDirectoryExpressionProcessor.setBeanFactory(beanFactory);
}
if (!this.fileNameGeneratorSet && this.fileNameGenerator instanceof BeanFactoryAware) {
((BeanFactoryAware) this.fileNameGenerator).setBeanFactory(beanFactory);
}
if (this.fileNameProcessor != null) {
this.fileNameProcessor.setBeanFactory(beanFactory);
}
}
if (this.autoCreateDirectory){
Assert.hasText(this.remoteFileSeparator, "'remoteFileSeparator' must not be empty when 'autoCreateDirectory' is set to 'true'");
}
if (hasExplicitlySetSuffix && !useTemporaryFileName){
this.logger.warn("Since 'use-temporary-file-name' is set to 'false' the value of 'temporary-file-suffix' has no effect");
}
}
@Override
public String send(final Message<?> message) {
return this.send(message, null);
}
@Override
public String send(final Message<?> message, final String subDirectory) {
Assert.notNull(this.directoryExpressionProcessor, "'remoteDirectoryExpression' is required");
final StreamHolder inputStreamHolder = this.payloadToInputStream(message);
if (inputStreamHolder != null) {
return this.execute(new SessionCallback<F, String>() {
@Override
public String doInSession(Session<F> session) throws IOException {
String fileName = inputStreamHolder.getName();
try {
String remoteDirectory = RemoteFileTemplate.this.directoryExpressionProcessor
.processMessage(message);
remoteDirectory = RemoteFileTemplate.this.normalizeDirectoryPath(remoteDirectory);
if (StringUtils.hasText(subDirectory)) {
if (subDirectory.startsWith(RemoteFileTemplate.this.remoteFileSeparator)) {
remoteDirectory += subDirectory.substring(1);
}
else {
remoteDirectory += RemoteFileTemplate.this.normalizeDirectoryPath(subDirectory);
}
}
String temporaryRemoteDirectory = remoteDirectory;
if (RemoteFileTemplate.this.temporaryDirectoryExpressionProcessor != null) {
temporaryRemoteDirectory = RemoteFileTemplate.this.temporaryDirectoryExpressionProcessor
.processMessage(message);
}
fileName = RemoteFileTemplate.this.fileNameGenerator.generateFileName(message);
RemoteFileTemplate.this.sendFileToRemoteDirectory(inputStreamHolder.getStream(),
temporaryRemoteDirectory, remoteDirectory, fileName, session);
return remoteDirectory + fileName;
}
catch (FileNotFoundException e) {
throw new MessageDeliveryException(message, "File [" + inputStreamHolder.getName()
+ "] not found in local working directory; it was moved or deleted unexpectedly.", e);
}
catch (IOException e) {
throw new MessageDeliveryException(message, "Failed to transfer file ["
+ inputStreamHolder.getName() + " -> " + fileName
+ "] from local directory to remote directory.", e);
}
catch (Exception e) {
throw new MessageDeliveryException(message, "Error handling message for file ["
+ inputStreamHolder.getName() + " -> " + fileName + "]", e);
}
}
});
}
else {
// A null holder means a File payload that does not exist.
if (logger.isWarnEnabled()) {
logger.warn("File " + message.getPayload() + " does not exist");
}
return null;
}
}
@Override
public boolean remove(final String path) {
return this.execute(new SessionCallback<F, Boolean>() {
@Override
public Boolean doInSession(Session<F> session) throws IOException {
return session.remove(path);
}
});
}
@Override
public void rename(final String fromPath, final String toPath) {
Assert.hasText(fromPath, "Old filename cannot be null or empty");
Assert.hasText(toPath, "New filename cannot be null or empty");
this.execute(new SessionCallbackWithoutResult<F>() {
@Override
public void doInSessionWithoutResult(Session<F> session) throws IOException {
int lastSeparator = toPath.lastIndexOf(RemoteFileTemplate.this.remoteFileSeparator);
if (lastSeparator > 0) {
String remoteFileDirectory = toPath.substring(0, lastSeparator + 1);
RemoteFileUtils.makeDirectories(remoteFileDirectory, session,
RemoteFileTemplate.this.remoteFileSeparator, RemoteFileTemplate.this.logger);
}
session.rename(fromPath, toPath);
}
});
}
@Override
public boolean get(final Message<?> message, final InputStreamCallback callback) {
Assert.notNull(this.fileNameProcessor, "'fileNameProcessor' needed to use get");
return this.execute(new SessionCallback<F, Boolean>() {
@Override
public Boolean doInSession(Session<F> session) throws IOException {
final String remotePath = RemoteFileTemplate.this.fileNameProcessor.processMessage(message);
InputStream inputStream = session.readRaw(remotePath);
callback.doWithInputStream(inputStream);
inputStream.close();
return session.finalizeRaw();
}
});
}
@Override
public <T> T execute(SessionCallback<F, T> callback) {
Session<F> session = null;
try {
session = this.sessionFactory.getSession();
Assert.notNull(session, "failed to acquire a Session");
return callback.doInSession(session);
}
catch (IOException e) {
throw new MessagingException("Failed to execute on session", e);
}
finally {
if (session != null) {
try {
session.close();
}
catch (Exception ignored) {
if (logger.isDebugEnabled()) {
logger.debug("failed to close Session", ignored);
}
}
}
}
}
private StreamHolder payloadToInputStream(Message<?> message) throws MessageDeliveryException {
try {
Object payload = message.getPayload();
InputStream dataInputStream = null;
String name = null;
if (payload instanceof File) {
File inputFile = (File) payload;
if (inputFile.exists()) {
dataInputStream = new BufferedInputStream(new FileInputStream(inputFile));
name = inputFile.getAbsolutePath();
}
}
else if (payload instanceof byte[] || payload instanceof String) {
byte[] bytes = null;
if (payload instanceof String) {
bytes = ((String) payload).getBytes(this.charset);
name = "String payload";
}
else {
bytes = (byte[]) payload;
name = "byte[] payload";
}
dataInputStream = new ByteArrayInputStream(bytes);
}
else {
throw new IllegalArgumentException("Unsupported payload type. The only supported payloads are " +
"java.io.File, java.lang.String, and byte[]");
}
if (dataInputStream == null) {
return null;
}
else {
return new StreamHolder(dataInputStream, name);
}
}
catch (Exception e) {
throw new MessageDeliveryException(message, "Failed to create sendable file.", e);
}
}
private void sendFileToRemoteDirectory(InputStream inputStream, String temporaryRemoteDirectory,
String remoteDirectory, String fileName, Session<F> session) throws IOException {
remoteDirectory = this.normalizeDirectoryPath(remoteDirectory);
temporaryRemoteDirectory = this.normalizeDirectoryPath(temporaryRemoteDirectory);
String remoteFilePath = remoteDirectory + fileName;
String tempRemoteFilePath = temporaryRemoteDirectory + fileName;
// write remote file first with temporary file extension if enabled
String tempFilePath = tempRemoteFilePath + (useTemporaryFileName ? this.temporaryFileSuffix : "");
if (this.autoCreateDirectory) {
try {
RemoteFileUtils.makeDirectories(remoteDirectory, session, this.remoteFileSeparator, this.logger);
}
catch (IllegalStateException e) {
// Revert to old FTP behavior if recursive mkdir fails, for backwards compatibility
session.mkdir(remoteDirectory);
}
}
try {
session.write(inputStream, tempFilePath);
// then rename it to its final name if necessary
if (useTemporaryFileName){
session.rename(tempFilePath, remoteFilePath);
}
}
catch (Exception e) {
throw new MessagingException("Failed to write to '" + tempFilePath + "' while uploading the file", e);
}
finally {
inputStream.close();
}
}
private String normalizeDirectoryPath(String directoryPath){
if (!StringUtils.hasText(directoryPath)) {
directoryPath = "";
}
else if (!directoryPath.endsWith(this.remoteFileSeparator)) {
directoryPath += this.remoteFileSeparator;
}
return directoryPath;
}
private class StreamHolder {
private final InputStream stream;
private final String name;
private StreamHolder(InputStream stream, String name) {
this.stream = stream;
this.name = name;
}
public InputStream getStream() {
return stream;
}
public String getName() {
return name;
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 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;
import java.io.IOException;
import org.springframework.integration.file.remote.session.Session;
/**
* Callback invoked by {@code RemoteFileOperations.execute()} - allows multiple operations
* on a session.
*
* @author Gary Russell
* @since 3.0
*
*/
public interface SessionCallback<F, T> {
/**
* Called within the context of a session.
* Perform some operation(s) on the session. The caller will take
* care of closing the session after this method exits.
*
* @param session The session.
* @return The result of type T.
* @throws IOException
*/
T doInSession(Session<F> session) throws IOException;
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 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;
import java.io.IOException;
import org.springframework.integration.file.remote.session.Session;
/**
* Simple convenience implementation of {@link SessionCallback} for cases where
* no result is returned.
*
* @author Gary Russell
* @since 3.0
*
*/
public abstract class SessionCallbackWithoutResult<F> implements SessionCallback<F, Object> {
@Override
public Object doInSession(Session<F> session) throws IOException {
this.doInSessionWithoutResult(session);
return null;
}
/**
* Called within the context of a session.
* Perform some operation(s) on the session. The caller will take
* care of closing the session after this method exits.
*
* @param session The session.
* @throws IOException
*/
protected abstract void doInSessionWithoutResult(Session<F> session) throws IOException;
}

View File

@@ -38,7 +38,8 @@ import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.file.FileHeaders;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.file.remote.AbstractFileInfo;
import org.springframework.integration.file.remote.RemoteFileUtils;
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.handler.AbstractReplyProducingMessageHandler;
@@ -59,7 +60,7 @@ import org.springframework.util.StringUtils;
*/
public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReplyProducingMessageHandler {
protected final SessionFactory<F> sessionFactory;
private final RemoteFileTemplate<F> remoteFileTemplate;
protected final Command command;
@@ -91,7 +92,17 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
/**
* Move (rename) a remote file.
*/
MV("mv");
MV("mv"),
/**
* Put a local file to the remote system.
*/
PUT("put"),
/**
* Put multiple local files to the remote system.
*/
MPUT("mput");
private String command;
@@ -187,25 +198,27 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
protected volatile Set<Option> options = new HashSet<Option>();
private volatile String remoteFileSeparator = "/";
private volatile Expression localDirectoryExpression;
private volatile boolean autoCreateLocalDirectory = true;
private volatile String temporaryFileSuffix = ".writing";
/**
* An {@link FileListFilter} that runs against the <em>remote</em> file system view.
* A {@link FileListFilter} that runs against the <em>remote</em> file system view.
*/
private volatile FileListFilter<F> filter;
/**
* A {@link FileListFilter} that runs against the <em>local</em> file system view when
* using MPUT.
*/
private volatile FileListFilter<File> mputFilter;
private volatile Expression localFilenameGeneratorExpression;
public AbstractRemoteFileOutboundGateway(SessionFactory<F> sessionFactory, String command,
String expression) {
this.sessionFactory = sessionFactory;
Assert.notNull(sessionFactory, "'sessionFactory' cannot be null");
this.remoteFileTemplate = new RemoteFileTemplate<F>(sessionFactory);
this.command = Command.toCommand(command);
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
new SpelExpressionParser().parseExpression(expression));
@@ -213,12 +226,30 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
public AbstractRemoteFileOutboundGateway(SessionFactory<F> sessionFactory, Command command,
String expression) {
this.sessionFactory = sessionFactory;
Assert.notNull(sessionFactory, "'sessionFactory' cannot be null");
this.remoteFileTemplate = new RemoteFileTemplate<F>(sessionFactory);
this.command = command;
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
new SpelExpressionParser().parseExpression(expression));
}
public AbstractRemoteFileOutboundGateway(RemoteFileTemplate<F> remoteFileTemplate, String command,
String expression) {
Assert.notNull(remoteFileTemplate, "'remoteFileTemplate' cannot be null");
this.remoteFileTemplate = remoteFileTemplate;
this.command = Command.toCommand(command);
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
new SpelExpressionParser().parseExpression(expression));
}
public AbstractRemoteFileOutboundGateway(RemoteFileTemplate<F> remoteFileTemplate, Command command,
String expression) {
Assert.notNull(remoteFileTemplate, "'remoteFileTemplate' cannot be null");
this.remoteFileTemplate = remoteFileTemplate;
this.command = command;
this.fileNameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
new SpelExpressionParser().parseExpression(expression));
}
/**
* @param options the options to set
@@ -237,7 +268,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
* @param remoteFileSeparator the remoteFileSeparator to set
*/
public void setRemoteFileSeparator(String remoteFileSeparator) {
this.remoteFileSeparator = remoteFileSeparator;
this.remoteFileTemplate.setRemoteFileSeparator(remoteFileSeparator);
}
/**
@@ -264,7 +295,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
* @param temporaryFileSuffix the temporaryFileSuffix to set
*/
public void setTemporaryFileSuffix(String temporaryFileSuffix) {
this.temporaryFileSuffix = temporaryFileSuffix;
this.remoteFileTemplate.setTemporaryFileSuffix(temporaryFileSuffix);
}
/**
@@ -274,6 +305,13 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
this.filter = filter;
}
/**
* @param filter the filter to set
*/
public void setMputFilter(FileListFilter<File> filter) {
this.mputFilter = filter;
}
public void setRenameExpression(String expression) {
Assert.notNull(expression, "'expression' cannot be null");
this.renameProcessor = new ExpressionEvaluatingMessageProcessor<String>(
@@ -330,88 +368,105 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
if (this.getBeanFactory() != null) {
this.fileNameProcessor.setBeanFactory(this.getBeanFactory());
this.renameProcessor.setBeanFactory(this.getBeanFactory());
this.remoteFileTemplate.setBeanFactory(this.getBeanFactory());
}
}
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
Session<F> session = this.sessionFactory.getSession();
try {
switch (this.command) {
case LS:
return doLs(requestMessage, session);
case GET:
return doGet(requestMessage, session);
case MGET:
return doMget(requestMessage, session);
case RM:
return doRm(requestMessage, session);
case MV:
return doMv(requestMessage, session);
default:
return null;
}
}
catch (IOException e) {
throw new MessagingException(requestMessage, e);
}
finally {
session.close();
switch (this.command) {
case LS:
return doLs(requestMessage);
case GET:
return doGet(requestMessage);
case MGET:
return doMget(requestMessage);
case RM:
return doRm(requestMessage);
case MV:
return doMv(requestMessage);
case PUT:
return doPut(requestMessage);
case MPUT:
return doMput(requestMessage);
default:
return null;
}
}
private Object doLs(Message<?> requestMessage, Session<F> session) throws IOException {
private Object doLs(Message<?> requestMessage) {
String dir = this.fileNameProcessor.processMessage(requestMessage);
if (!dir.endsWith(this.remoteFileSeparator)) {
dir += this.remoteFileSeparator;
if (!dir.endsWith(this.remoteFileTemplate.getRemoteFileSeparator())) {
dir += this.remoteFileTemplate.getRemoteFileSeparator();
}
List<?> payload = ls(session, dir);
final String fullDir = dir;
List<?> payload = this.remoteFileTemplate.execute(new SessionCallback<F, List<?>>() {
@Override
public List<?> doInSession(Session<F> session) throws IOException {
return AbstractRemoteFileOutboundGateway.this.ls(session, fullDir);
}
});
return MessageBuilder.withPayload(payload)
.setHeader(FileHeaders.REMOTE_DIRECTORY, dir)
.build();
}
private Object doGet(Message<?> requestMessage, Session<F> session) throws IOException {
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
String remoteFilename = this.getRemoteFilename(remoteFilePath);
String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
File payload = this.get(requestMessage, session, remoteDir, remoteFilePath, remoteFilename, true);
private Object doGet(final Message<?> requestMessage) {
final String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
final String remoteFilename = this.getRemoteFilename(remoteFilePath);
final String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
File payload = this.remoteFileTemplate.execute(new SessionCallback<F, File>() {
@Override
public File doInSession(Session<F> session) throws IOException {
return AbstractRemoteFileOutboundGateway.this.get(requestMessage, session, remoteDir, remoteFilePath,
remoteFilename, true);
}
});
return MessageBuilder.withPayload(payload)
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
.build();
}
private Object doMget(Message<?> requestMessage, Session<F> session) throws IOException {
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
String remoteFilename = this.getRemoteFilename(remoteFilePath);
String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
List<File> payload = this.mGet(requestMessage, session, remoteDir, remoteFilename);
private Object doMget(final Message<?> requestMessage) {
final String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
final String remoteFilename = this.getRemoteFilename(remoteFilePath);
final String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
List<File> payload = this.remoteFileTemplate.execute(new SessionCallback<F, List<File>>() {
@Override
public List<File> doInSession(Session<F> session) throws IOException {
return AbstractRemoteFileOutboundGateway.this.mGet(requestMessage, session, remoteDir, remoteFilename);
}
});
return MessageBuilder.withPayload(payload)
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
.build();
}
private Object doRm(Message<?> requestMessage, Session<F> session) throws IOException {
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
private Object doRm(Message<?> requestMessage) {
final String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
String remoteFilename = this.getRemoteFilename(remoteFilePath);
String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
boolean payload = this.rm(session, remoteFilePath);
boolean payload = this.remoteFileTemplate.remove(remoteFilePath);
return MessageBuilder.withPayload(payload)
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
.build();
}
private Object doMv(Message<?> requestMessage, Session<F> session) throws IOException {
private Object doMv(Message<?> requestMessage) {
String remoteFilePath = this.fileNameProcessor.processMessage(requestMessage);
String remoteFilename = this.getRemoteFilename(remoteFilePath);
String remoteDir = this.getRemoteDirectory(remoteFilePath, remoteFilename);
String remoteFileNewPath = this.renameProcessor.processMessage(requestMessage);
Assert.hasLength(remoteFileNewPath, "New filename cannot be empty");
this.mv(session, remoteFilePath, remoteFileNewPath);
this.remoteFileTemplate.rename(remoteFilePath, remoteFileNewPath);
return MessageBuilder.withPayload(Boolean.TRUE)
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
@@ -419,6 +474,66 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
.build();
}
private String doPut(Message<?> requestMessage) {
return this.doPut(requestMessage, null);
}
private String doPut(Message<?> requestMessage, String subDirectory) {
String path = this.remoteFileTemplate.send(requestMessage, subDirectory);
if (path == null) {
throw new MessagingException(requestMessage, "No local file found for " + requestMessage);
}
return path;
}
private Object doMput(Message<?> requestMessage) {
File file = null;
if (requestMessage.getPayload() instanceof File) {
file = (File) requestMessage.getPayload();
}
else if (requestMessage.getPayload() instanceof String) {
file = new File((String) requestMessage.getPayload());
}
else {
throw new IllegalArgumentException("Only File or String payloads allowed for 'mput'");
}
if (!file.isDirectory()) {
return this.doPut(requestMessage);
}
else {
List<String> replies = this.putLocalDirectory(requestMessage, file, null);
return replies;
}
}
private List<String> putLocalDirectory(Message<?> requestMessage, File file, String subDirectory) {
File[] files = file.listFiles();
List<File> filteredFiles = this.filterMputFiles(files);
List<String> replies = new ArrayList<String>();
for (File filteredFile : filteredFiles) {
if (!filteredFile.isDirectory()) {
String path = this.doPut(MessageBuilder.withPayload(filteredFile)
.copyHeaders(requestMessage.getHeaders())
.build(), subDirectory);
if (path == null) {
if (logger.isDebugEnabled()) {
logger.debug("File " + filteredFile.getAbsolutePath() + " removed before transfer; ignoring");
}
}
else {
replies.add(path);
}
}
else if (this.options.contains(Option.RECURSIVE)){
String newSubDirectory = (StringUtils.hasText(subDirectory) ?
subDirectory + this.remoteFileTemplate.getRemoteFileSeparator() : "")
+ filteredFile.getName();
replies.addAll(this.putLocalDirectory(requestMessage, filteredFile, newSubDirectory));
}
}
return replies;
}
protected List<?> ls(Session<F> session, String dir) throws IOException {
List<F> lsFiles = listFilesInRemoteDir(session, dir, "");
if (!this.options.contains(Option.LINKS)) {
@@ -467,7 +582,8 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
}
}
if (recursion && this.isDirectory(file) && !(".".equals(fileName)) && !("..".equals(fileName))) {
lsFiles.addAll(listFilesInRemoteDir(session, directory, subDirectory + fileName + this.remoteFileSeparator));
lsFiles.addAll(listFilesInRemoteDir(session, directory, subDirectory + fileName
+ this.remoteFileTemplate.getRemoteFileSeparator()));
}
}
}
@@ -479,6 +595,13 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
return (this.filter != null) ? this.filter.filterFiles(files) : Arrays.asList(files);
}
protected final List<File> filterMputFiles(File[] files) {
if (files == null) {
return Collections.emptyList();
}
return (this.mputFilter != null) ? this.mputFilter.filterFiles(files) : Arrays.asList(files);
}
protected void purgeLinks(List<F> lsFiles) {
Iterator<F> iterator = lsFiles.iterator();
while (iterator.hasNext()) {
@@ -520,7 +643,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
}
File localFile = new File(this.generateLocalDirectory(message, remoteDir), this.generateLocalFileName(message, remoteFilename));
if (!localFile.exists()) {
String tempFileName = localFile.getAbsolutePath() + this.temporaryFileSuffix;
String tempFileName = localFile.getAbsolutePath() + this.remoteFileTemplate.getTemporaryFileSuffix();
File tempFile = new File(tempFileName);
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(tempFile));
try {
@@ -583,12 +706,13 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
+ " with pattern " + remoteFilename);
}
List<File> files = new ArrayList<File>();
String remoteFileSeparator = this.remoteFileTemplate.getRemoteFileSeparator();
for (String fileName : fileNames) {
File file;
if (fileName.contains(this.remoteFileSeparator) &&
if (fileName.contains(remoteFileSeparator) &&
fileName.startsWith(remoteDirectory)) { // the server returned the full path
file = this.get(message, session, remoteDirectory, fileName,
fileName.substring(fileName.lastIndexOf(this.remoteFileSeparator)), false);
fileName.substring(fileName.lastIndexOf(remoteFileSeparator)), false);
}
else {
file = this.get(message, session, remoteDirectory,
@@ -626,21 +750,22 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
private String getRemoteDirectory(String remoteFilePath, String remoteFilename) {
String remoteDir = remoteFilePath.substring(0, remoteFilePath.lastIndexOf(remoteFilename));
if (remoteDir.length() == 0) {
remoteDir = this.remoteFileSeparator;
remoteDir = this.remoteFileTemplate.getRemoteFileSeparator();
}
return remoteDir;
}
private String generateFullPath(String remoteDirectory, String remoteFilename) {
String path;
if (this.remoteFileSeparator.equals(remoteDirectory)) {
String remoteFileSeparator = this.remoteFileTemplate.getRemoteFileSeparator();
if (remoteFileSeparator.equals(remoteDirectory)) {
path = remoteFilename;
}
else if (remoteDirectory.endsWith(this.remoteFileSeparator)) {
else if (remoteDirectory.endsWith(remoteFileSeparator)) {
path = remoteDirectory + remoteFilename;
}
else {
path = remoteDirectory + this.remoteFileSeparator + remoteFilename;
path = remoteDirectory + remoteFileSeparator + remoteFilename;
}
return path;
}
@@ -650,7 +775,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
*/
protected String getRemoteFilename(String remoteFilePath) {
String remoteFileName;
int index = remoteFilePath.lastIndexOf(this.remoteFileSeparator);
int index = remoteFilePath.lastIndexOf(this.remoteFileTemplate.getRemoteFileSeparator());
if (index < 0) {
remoteFileName = remoteFilePath;
}
@@ -660,20 +785,6 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
return remoteFileName;
}
protected boolean rm(Session<?> session, String remoteFilePath)
throws IOException {
return session.remove(remoteFilePath);
}
protected void mv(Session<?> session, String remoteFilePath, String remoteFileNewPath) throws IOException {
int lastSeparator = remoteFileNewPath.lastIndexOf(this.remoteFileSeparator);
if (lastSeparator > 0) {
String remoteFileDirectory = remoteFileNewPath.substring(0, lastSeparator + 1);
RemoteFileUtils.makeDirectories(remoteFileDirectory, session, this.remoteFileSeparator, this.logger);
}
session.rename(remoteFilePath, remoteFileNewPath);
}
private File generateLocalDirectory(Message<?> message, String remoteDirectory) {
EvaluationContext evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
evaluationContext.setVariable("remoteDirectory", remoteDirectory);
@@ -707,4 +818,5 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
abstract protected List<AbstractFileInfo<F>> asFileInfoList(Collection<F> files);
abstract protected F enhanceNameWithSubDirectory(F file, String directory);
}

View File

@@ -16,29 +16,15 @@
package org.springframework.integration.file.remote.handler;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.expression.Expression;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.file.remote.RemoteFileUtils;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A {@link org.springframework.messaging.MessageHandler} implementation that transfers files to a remote server.
@@ -53,56 +39,37 @@ import org.springframework.util.StringUtils;
*/
public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
private volatile String temporaryFileSuffix =".writing";
private final SessionFactory<F> sessionFactory;
private volatile boolean autoCreateDirectory = false;
private volatile boolean useTemporaryFileName = true;
private volatile ExpressionEvaluatingMessageProcessor<String> directoryExpressionProcessor;
private volatile ExpressionEvaluatingMessageProcessor<String> temporaryDirectoryExpressionProcessor;
private volatile FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator();
private volatile boolean fileNameGeneratorSet;
private volatile String charset = "UTF-8";
private volatile String remoteFileSeparator = "/";
private volatile boolean hasExplicitlySetSuffix;
private final RemoteFileTemplate<F> remoteFileTemplate;
public FileTransferringMessageHandler(SessionFactory<F> sessionFactory) {
Assert.notNull(sessionFactory, "sessionFactory must not be null");
this.sessionFactory = sessionFactory;
this.remoteFileTemplate = new RemoteFileTemplate<F>(sessionFactory);
}
public FileTransferringMessageHandler(RemoteFileTemplate<F> remoteFileTemplate) {
Assert.notNull(remoteFileTemplate, "remoteFileTemplate must not be null");
this.remoteFileTemplate = remoteFileTemplate;
}
public void setAutoCreateDirectory(boolean autoCreateDirectory) {
this.autoCreateDirectory = autoCreateDirectory;
this.remoteFileTemplate.setAutoCreateDirectory(autoCreateDirectory);
}
public void setRemoteFileSeparator(String remoteFileSeparator) {
Assert.notNull(remoteFileSeparator, "'remoteFileSeparator' must not be null");
this.remoteFileSeparator = remoteFileSeparator;
this.remoteFileTemplate.setRemoteFileSeparator(remoteFileSeparator);
}
public void setRemoteDirectoryExpression(Expression remoteDirectoryExpression) {
Assert.notNull(remoteDirectoryExpression, "remoteDirectoryExpression must not be null");
this.directoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor<String>(remoteDirectoryExpression, String.class);
this.remoteFileTemplate.setRemoteDirectoryExpression(remoteDirectoryExpression);
}
public void setTemporaryRemoteDirectoryExpression(Expression temporaryRemoteDirectoryExpression) {
Assert.notNull(temporaryRemoteDirectoryExpression, "temporaryRemoteDirectoryExpression must not be null");
this.temporaryDirectoryExpressionProcessor = new ExpressionEvaluatingMessageProcessor<String>(temporaryRemoteDirectoryExpression, String.class);
this.remoteFileTemplate.setTemporaryRemoteDirectoryExpression(temporaryRemoteDirectoryExpression);
}
protected String getTemporaryFileSuffix() {
return this.temporaryFileSuffix;
return this.remoteFileTemplate.getTemporaryFileSuffix();
}
/**
@@ -113,198 +80,36 @@ public class FileTransferringMessageHandler<F> extends AbstractMessageHandler {
}
protected boolean isUseTemporaryFileName() {
return useTemporaryFileName;
return this.remoteFileTemplate.isUseTemporaryFileName();
}
public void setUseTemporaryFileName(boolean useTemporaryFileName) {
this.useTemporaryFileName = useTemporaryFileName;
this.remoteFileTemplate.setUseTemporaryFileName(useTemporaryFileName);
}
public void setFileNameGenerator(FileNameGenerator fileNameGenerator) {
this.fileNameGenerator = (fileNameGenerator != null) ? fileNameGenerator : new DefaultFileNameGenerator();
this.fileNameGeneratorSet = fileNameGenerator != null;
this.remoteFileTemplate.setFileNameGenerator(fileNameGenerator);
}
public void setCharset(String charset) {
this.charset = charset;
this.remoteFileTemplate.setCharset(charset);
}
public void setTemporaryFileSuffix(String temporaryFileSuffix) {
Assert.notNull(temporaryFileSuffix, "'temporaryFileSuffix' must not be null");
this.hasExplicitlySetSuffix = true;
this.temporaryFileSuffix = temporaryFileSuffix;
this.remoteFileTemplate.setTemporaryFileSuffix(temporaryFileSuffix);
}
@Override
protected void onInit() throws Exception {
Assert.notNull(this.directoryExpressionProcessor, "remoteDirectoryExpression is required");
BeanFactory beanFactory = this.getBeanFactory();
if (beanFactory != null) {
this.directoryExpressionProcessor.setBeanFactory(beanFactory);
if (this.temporaryDirectoryExpressionProcessor != null) {
this.temporaryDirectoryExpressionProcessor.setBeanFactory(beanFactory);
}
if (!this.fileNameGeneratorSet && this.fileNameGenerator instanceof BeanFactoryAware) {
((BeanFactoryAware) this.fileNameGenerator).setBeanFactory(beanFactory);
}
}
if (this.autoCreateDirectory){
Assert.hasText(this.remoteFileSeparator, "'remoteFileSeparator' must not be empty when 'autoCreateDirectory' is set to 'true'");
}
if (hasExplicitlySetSuffix && !useTemporaryFileName){
this.logger.warn("Since 'use-temporary-file-name' is set to 'false' the value of 'temporary-file-suffix' has no effect");
}
this.remoteFileTemplate.setBeanFactory(this.getBeanFactory());
this.remoteFileTemplate.afterPropertiesSet();
}
@Override
protected void handleMessageInternal(Message<?> message) throws Exception {
StreamHolder inputStreamHolder = this.payloadToInputStream(message);
if (inputStreamHolder != null) {
Session<F> session = this.sessionFactory.getSession();
String fileName = inputStreamHolder.getName();
try {
String remoteDirectory = this.directoryExpressionProcessor.processMessage(message);
String temporaryRemoteDirectory = remoteDirectory;
if (this.temporaryDirectoryExpressionProcessor != null){
temporaryRemoteDirectory = this.temporaryDirectoryExpressionProcessor.processMessage(message);
}
fileName = this.fileNameGenerator.generateFileName(message);
this.sendFileToRemoteDirectory(inputStreamHolder.getStream(), temporaryRemoteDirectory, remoteDirectory, fileName, session);
}
catch (FileNotFoundException e) {
throw new MessageDeliveryException(message,
"File [" + inputStreamHolder.getName() + "] not found in local working directory; it was moved or deleted unexpectedly.", e);
}
catch (IOException e) {
throw new MessageDeliveryException(message,
"Failed to transfer file [" + inputStreamHolder.getName() + " -> " + fileName + "] from local directory to remote directory.", e);
}
catch (Exception e) {
throw new MessageDeliveryException(message,
"Error handling message for file [" + inputStreamHolder.getName() + " -> " + fileName + "]", e);
}
finally {
if (session != null) {
session.close();
}
}
}
else {
// A null holder means a File payload that does not exist.
if (logger.isWarnEnabled()) {
logger.warn("File " + message.getPayload() + " does not exist");
}
}
}
private StreamHolder payloadToInputStream(Message<?> message) throws MessageDeliveryException {
try {
Object payload = message.getPayload();
InputStream dataInputStream = null;
String name = null;
if (payload instanceof File) {
File inputFile = (File) payload;
if (inputFile.exists()) {
dataInputStream = new BufferedInputStream(new FileInputStream(inputFile));
name = inputFile.getAbsolutePath();
}
}
else if (payload instanceof byte[] || payload instanceof String) {
byte[] bytes = null;
if (payload instanceof String) {
bytes = ((String) payload).getBytes(this.charset);
name = "String payload";
}
else {
bytes = (byte[]) payload;
name = "byte[] payload";
}
dataInputStream = new ByteArrayInputStream(bytes);
}
else {
throw new IllegalArgumentException("Unsupported payload type. The only supported payloads are " +
"java.io.File, java.lang.String, and byte[]");
}
if (dataInputStream == null) {
return null;
}
else {
return new StreamHolder(dataInputStream, name);
}
}
catch (Exception e) {
throw new MessageDeliveryException(message, "Failed to create sendable file.", e);
}
}
private void sendFileToRemoteDirectory(InputStream inputStream, String temporaryRemoteDirectory,
String remoteDirectory, String fileName, Session<F> session) throws FileNotFoundException, IOException {
remoteDirectory = this.normalizeDirectoryPath(remoteDirectory);
temporaryRemoteDirectory = this.normalizeDirectoryPath(temporaryRemoteDirectory);
String remoteFilePath = remoteDirectory + fileName;
String tempRemoteFilePath = temporaryRemoteDirectory + fileName;
// write remote file first with temporary file extension if enabled
String tempFilePath = tempRemoteFilePath + (useTemporaryFileName ? this.temporaryFileSuffix : "");
if (this.autoCreateDirectory) {
try {
RemoteFileUtils.makeDirectories(remoteDirectory, session, this.remoteFileSeparator, this.logger);
}
catch (IllegalStateException e) {
// Revert to old FTP behavior if recursive mkdir fails, for backwards compatibility
session.mkdir(remoteDirectory);
}
}
try {
session.write(inputStream, tempFilePath);
// then rename it to its final name if necessary
if (useTemporaryFileName){
session.rename(tempFilePath, remoteFilePath);
}
}
catch (Exception e) {
throw new MessagingException("Failed to write to '" + tempFilePath + "' while uploading the file", e);
}
finally {
inputStream.close();
}
}
private String normalizeDirectoryPath(String directoryPath){
if (!StringUtils.hasText(directoryPath)) {
directoryPath = "";
}
else if (!directoryPath.endsWith(this.remoteFileSeparator)) {
directoryPath += this.remoteFileSeparator;
}
return directoryPath;
}
private class StreamHolder {
private final InputStream stream;
private final String name;
private StreamHolder(InputStream stream, String name) {
this.stream = stream;
this.name = name;
}
public InputStream getStream() {
return stream;
}
public String getName() {
return name;
}
this.remoteFileTemplate.send(message);
}
}

View File

@@ -33,6 +33,8 @@ import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.integration.expression.IntegrationEvaluationContextAware;
import org.springframework.integration.file.filters.FileListFilter;
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;
@@ -59,6 +61,8 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
protected final Log logger = LogFactory.getLog(this.getClass());
private final RemoteFileTemplate<F> remoteFileTemplate;
private volatile EvaluationContext evaluationContext;
private volatile String remoteFileSeparator = "/";
@@ -75,11 +79,6 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
*/
private volatile String remoteDirectory;
/**
* the {@link SessionFactory} for acquiring remote file Sessions.
*/
private final SessionFactory<F> sessionFactory;
/**
* An {@link FileListFilter} that runs against the <em>remote</em> file system view.
*/
@@ -102,7 +101,7 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
*/
public AbstractInboundFileSynchronizer(SessionFactory<F> sessionFactory) {
Assert.notNull(sessionFactory, "sessionFactory must not be null");
this.sessionFactory = sessionFactory;
this.remoteFileTemplate = new RemoteFileTemplate<F>(sessionFactory);
}
@@ -144,6 +143,7 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
this.evaluationContext = evaluationContext;
}
@Override
public final void afterPropertiesSet() {
Assert.notNull(this.remoteDirectory, "remoteDirectory must not be null");
Assert.notNull(this.evaluationContext, "evaluationContext must not be null");
@@ -157,36 +157,37 @@ public abstract class AbstractInboundFileSynchronizer<F> implements InboundFileS
return temporaryFileSuffix;
}
public void synchronizeToLocalDirectory(File localDirectory) {
Session<F> session = null;
@Override
public void synchronizeToLocalDirectory(final File localDirectory) {
try {
session = this.sessionFactory.getSession();
Assert.notNull(session, "failed to acquire a Session");
F[] files = session.list(this.remoteDirectory);
if (!ObjectUtils.isEmpty(files)) {
Collection<F> filteredFiles = this.filterFiles(files);
for (F file : filteredFiles) {
if (file != null) {
this.copyFileToLocalDirectory(this.remoteDirectory, file, localDirectory, session);
int transferred = this.remoteFileTemplate.execute(new SessionCallback<F, Integer>() {
@Override
public Integer doInSession(Session<F> session) throws IOException {
F[] files = session.list(AbstractInboundFileSynchronizer.this.remoteDirectory);
if (!ObjectUtils.isEmpty(files)) {
Collection<F> filteredFiles = AbstractInboundFileSynchronizer.this.filterFiles(files);
for (F file : filteredFiles) {
if (file != null) {
AbstractInboundFileSynchronizer.this.copyFileToLocalDirectory(
AbstractInboundFileSynchronizer.this.remoteDirectory, file, localDirectory,
session);
}
}
return filteredFiles.size();
}
else {
return 0;
}
}
});
if (logger.isDebugEnabled()) {
logger.debug(transferred + " files transferred");
}
}
catch (IOException e) {
catch (Exception e) {
throw new MessagingException("Problem occurred while synchronizing remote to local directory", e);
}
finally {
if (session != null) {
try {
session.close();
}
catch (Exception ignored) {
if (logger.isDebugEnabled()) {
logger.debug("failed to close Session", ignored);
}
}
}
}
}
private void copyFileToLocalDirectory(String remoteDirectoryPath, F remoteFile, File localDirectory, Session<F> session) throws IOException {

View File

@@ -645,7 +645,79 @@ Only files matching this regular expression will be picked up by this adapter.
<xsd:enumeration value="rm"/>
<xsd:enumeration value="mget"/>
<xsd:enumeration value="mv"/>
<xsd:enumeration value="put"/>
<xsd:enumeration value="mput"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:attributeGroup name="remoteOutboundAttributeGroup">
<xsd:attribute name="remote-directory-expression"
type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide a SpEL expression which
will compute the directory
path where the files will be transferred to
(e.g., "headers.['remote_dir'] +
'/myTransfers'");
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="temporary-remote-directory-expression"
type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide a SpEL expression which
will compute the temporary directory
path where files will be transferred to before they are moved to the remote-directory
(e.g., "headers.['remote_dir'] +
'/temp/myTransfers'");
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="auto-create-directory" type="xsd:string"
default="false">
<xsd:annotation>
<xsd:documentation>
Specify whether to automatically create the
remote target directory if
it doesn't exist.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="remote-filename-generator" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to specify a reference to
[org.springframework.integration.file.FileNameGenerator] bean.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type
type="org.springframework.integration.file.FileNameGenerator" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="remote-filename-generator-expression"
type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Allows you to provide SpEL expression which
will compute file name of
the remote file (e.g., assuming payload
is java.io.File
"payload.getName() + '.transfered'");
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="use-temporary-file-name" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation>
Allows you to suppress using a temporary file name while writing the file.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
</xsd:schema>

View File

@@ -122,6 +122,17 @@ public class DefaultFileNameGeneratorTests {
assertEquals("bar", filename);
}
@Test
public void customExpressionTakesPrecedenceOverFilePayload() {
DefaultFileNameGenerator generator = new DefaultFileNameGenerator();
generator.setBeanFactory(mock(BeanFactory.class));
generator.setExpression("'foobar'");
File payload = new File("/some/path/ignore");
Message<?> message = MessageBuilder.withPayload(payload).build();
String filename = generator.generateFileName(message);
assertEquals("foobar", filename);
}
@Test
public void customHeaderNameTakesPrecedenceOverDefault() {
DefaultFileNameGenerator generator = new DefaultFileNameGenerator();

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!-- under test -->
<bean id="pollableFileSource" class="org.springframework.integration.file.FileReadingMessageSource"
p:directory="file:${java.io.tmpdir}/FileReadingMessageSourcePersistentFilterIntegrationTests"
p:filter-ref="persistentFilter"/>
<!-- persistent filter -->
<bean id="persistentFilter" class="org.springframework.integration.file.filters.FileSystemPersistentAcceptOnceFileListFilter">
<constructor-arg ref="ppms" />
<constructor-arg value="frmsPersistTest" />
</bean>
<bean id="ppms" class="org.springframework.integration.metadata.PropertiesPersistingMetadataStore">
<property name="baseDirectory"
value="#{T(System).getProperty('java.io.tmpdir') + T(java.io.File).separator + 'FileReadingMessageSourcePersistentFilterIntegrationTests.meta'}"/>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" />
</beans>

View File

@@ -0,0 +1,126 @@
/*
* Copyright 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import java.io.File;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
/**
* @author Iwein Fuld
* @author Gary Russell
*/
public class FileReadingMessageSourcePersistentFilterIntegrationTests {
AbstractApplicationContext context;
FileReadingMessageSource pollableFileSource;
private static File inputDir;
@AfterClass
public static void cleanUp() throws Throwable {
if(inputDir.exists()) {
inputDir.delete();
}
}
@BeforeClass
public static void setupInputDir() {
inputDir = new File(System.getProperty("java.io.tmpdir") + "/"
+ FileReadingMessageSourcePersistentFilterIntegrationTests.class.getSimpleName());
inputDir.mkdir();
}
@Before
public void generateTestFiles() throws Exception {
File.createTempFile("test", null, inputDir).setLastModified(System.currentTimeMillis() - 1000);
File.createTempFile("test", null, inputDir).setLastModified(System.currentTimeMillis() - 1000);
File.createTempFile("test", null, inputDir).setLastModified(System.currentTimeMillis() - 1000);
this.loadContextAndGetMessageSource();
}
private void loadContextAndGetMessageSource() {
this.context = new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-context.xml",
this.getClass());
this.pollableFileSource = context.getBean(FileReadingMessageSource.class);
}
@After
public void cleanoutInputDir() throws Exception {
File[] listFiles = inputDir.listFiles();
for (int i = 0; i < listFiles.length; i++) {
listFiles[i].delete();
}
}
@AfterClass
public static void removeInputDir() throws Exception {
inputDir.delete();
File persistDir = new File(System.getProperty("java.io.tmpdir") + "/"
+ FileReadingMessageSourcePersistentFilterIntegrationTests.class.getSimpleName()
+ ".meta");
File persist = new File(persistDir, "metadata-store.properties");
persist.delete();
persistDir.delete();
}
@Test
public void configured() throws Exception {
DirectFieldAccessor accessor = new DirectFieldAccessor(pollableFileSource);
assertEquals(inputDir, accessor.getPropertyValue("directory"));
}
@Test
public void getFiles() throws Exception {
Message<File> received1 = pollableFileSource.receive();
System.out.println("receive files round 1");
assertNotNull("This should return the first message", received1);
pollableFileSource.onSend(received1);
Message<File> received2 = pollableFileSource.receive();
assertNotNull(received2);
pollableFileSource.onSend(received2);
Message<File> received3 = pollableFileSource.receive();
assertNotNull(received3);
pollableFileSource.onSend(received3);
assertNotSame(received1 + " == " + received2, received1.getPayload(), received2.getPayload());
assertNotSame(received1 + " == " + received3, received1.getPayload(), received3.getPayload());
assertNotSame(received2 + " == " + received3, received2.getPayload(), received3.getPayload());
this.context.destroy();
this.loadContextAndGetMessageSource();
Message<File> received4 = pollableFileSource.receive();
assertNull(received4);
this.context.destroy();
}
}

View File

@@ -106,9 +106,9 @@ public class FileOutboundChannelAdapterParserTests {
assertThat(actual, is(expected));
DefaultFileNameGenerator fileNameGenerator = (DefaultFileNameGenerator) handlerAccessor.getPropertyValue("fileNameGenerator");
assertNotNull(fileNameGenerator);
String expression = (String) TestUtils.getPropertyValue(fileNameGenerator, "expression");
Expression expression = TestUtils.getPropertyValue(fileNameGenerator, "expression", Expression.class);
assertNotNull(expression);
assertEquals("'foo.txt'", expression);
assertEquals("'foo.txt'", expression.getExpressionString());
assertEquals(Boolean.FALSE, handlerAccessor.getPropertyValue("deleteSourceFiles"));
}

View File

@@ -26,6 +26,7 @@ import java.io.File;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -93,9 +94,9 @@ public class FileOutboundGatewayParserTests {
assertEquals(Boolean.TRUE, handlerAccessor.getPropertyValue("requiresReply"));
DefaultFileNameGenerator fileNameGenerator = (DefaultFileNameGenerator) handlerAccessor.getPropertyValue("fileNameGenerator");
assertNotNull(fileNameGenerator);
String expression = (String) TestUtils.getPropertyValue(fileNameGenerator, "expression");
Expression expression = TestUtils.getPropertyValue(fileNameGenerator, "expression", Expression.class);
assertNotNull(expression);
assertEquals("'foo.txt'", expression);
assertEquals("'foo.txt'", expression.getExpressionString());
Long sendTimeout = TestUtils.getPropertyValue(handler, "messagingTemplate.sendTimeout", Long.class);
assertEquals(Long.valueOf(777), sendTimeout);

View File

@@ -0,0 +1,47 @@
/*
* Copyright 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.filters;
import static org.junit.Assert.assertTrue;
import java.io.File;
import org.junit.Test;
import org.springframework.integration.metadata.MetadataStore;
import org.springframework.integration.metadata.SimpleMetadataStore;
/**
* @author Gary Russell
* @since 3.0
*
*/
public class PersistentAcceptOnceFileListFilterTests {
@Test
public void testFileSystem() throws Exception {
MetadataStore store = new SimpleMetadataStore();
FileSystemPersistentAcceptOnceFileListFilter filter = new FileSystemPersistentAcceptOnceFileListFilter(store, "foo:");
File file = File.createTempFile("foo", ".txt");
assertTrue(filter.filterFiles(new File[] {file}).size() == 1);
assertTrue(filter.filterFiles(new File[] {file}).size() == 0);
file.setLastModified(27L);
assertTrue(filter.filterFiles(new File[] {file}).size() == 1);
assertTrue(filter.filterFiles(new File[] {file}).size() == 0);
file.delete();
}
}

View File

@@ -15,10 +15,15 @@
*/
package org.springframework.integration.file.remote.gateway;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
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.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
@@ -36,20 +41,25 @@ import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.file.FileHeaders;
import org.springframework.integration.file.filters.AbstractSimplePatternFileListFilter;
import org.springframework.integration.file.remote.AbstractFileInfo;
import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.GenericMessage;
/**
@@ -61,6 +71,9 @@ public class RemoteFileOutboundGatewayTests {
private final String tmpDir = System.getProperty("java.io.tmpdir");
@Rule
public final TemporaryFolder tempFolder = new TemporaryFolder();
@Test(expected = IllegalArgumentException.class)
public void testBad() throws Exception {
@@ -987,6 +1000,120 @@ public class RemoteFileOutboundGatewayTests {
out.getHeaders().get(FileHeaders.REMOTE_FILE));
}
@Test
public void testPut() throws Exception {
@SuppressWarnings("unchecked")
SessionFactory<TestLsEntry> sessionFactory = mock(SessionFactory.class);
@SuppressWarnings("unchecked")
Session<TestLsEntry> session = mock(Session.class);
RemoteFileTemplate<TestLsEntry> template = new RemoteFileTemplate<TestLsEntry>(sessionFactory);
template.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
template.setBeanFactory(mock(BeanFactory.class));
template.afterPropertiesSet();
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway(template, "put", null);
FileTransferringMessageHandler<TestLsEntry> handler = new FileTransferringMessageHandler<TestLsEntry>(sessionFactory);
handler.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
gw.afterPropertiesSet();
when(sessionFactory.getSession()).thenReturn(session);
final AtomicReference<String> written = new AtomicReference<String>();
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
written.set((String) invocation.getArguments()[1]);
return null;
}
}).when(session).write(any(InputStream.class), anyString());
Message<String> requestMessage = MessageBuilder.withPayload("hello")
.setHeader(FileHeaders.FILENAME, "bar.txt")
.build();
String path = (String) gw.handleRequestMessage(requestMessage);
assertEquals("foo/bar.txt", path);
verify(session).rename("foo/bar.txt.writing", "foo/bar.txt");
}
@Test
public void testMput() throws Exception {
@SuppressWarnings("unchecked")
SessionFactory<TestLsEntry> sessionFactory = mock(SessionFactory.class);
@SuppressWarnings("unchecked")
Session<TestLsEntry> session = mock(Session.class);
RemoteFileTemplate<TestLsEntry> template = new RemoteFileTemplate<TestLsEntry>(sessionFactory);
template.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
template.setBeanFactory(mock(BeanFactory.class));
template.afterPropertiesSet();
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway(template, "mput", null);
gw.afterPropertiesSet();
when(sessionFactory.getSession()).thenReturn(session);
final AtomicReference<String> written = new AtomicReference<String>();
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
written.set((String) invocation.getArguments()[1]);
return null;
}
}).when(session).write(any(InputStream.class), anyString());
tempFolder.newFile("baz.txt");
tempFolder.newFile("qux.txt");
Message<File> requestMessage = MessageBuilder.withPayload(tempFolder.getRoot())
.build();
@SuppressWarnings("unchecked")
List<String> out = (List<String>) gw.handleRequestMessage(requestMessage);
assertEquals(2, out.size());
assertThat(out.get(0),
not(equalTo(out.get(1))));
assertThat(out.get(0), anyOf(
equalTo("foo/baz.txt"), equalTo("foo/qux.txt")));
assertThat(out.get(1), anyOf(
equalTo("foo/baz.txt"), equalTo("foo/qux.txt")));
}
@Test
public void testMputRecursive() throws Exception {
@SuppressWarnings("unchecked")
SessionFactory<TestLsEntry> sessionFactory = mock(SessionFactory.class);
@SuppressWarnings("unchecked")
Session<TestLsEntry> session = mock(Session.class);
RemoteFileTemplate<TestLsEntry> template = new RemoteFileTemplate<TestLsEntry>(sessionFactory);
template.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
template.setBeanFactory(mock(BeanFactory.class));
template.afterPropertiesSet();
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway(template, "mput", null);
gw.setOptions("-R");
gw.afterPropertiesSet();
when(sessionFactory.getSession()).thenReturn(session);
final AtomicReference<String> written = new AtomicReference<String>();
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
written.set((String) invocation.getArguments()[1]);
return null;
}
}).when(session).write(any(InputStream.class), anyString());
tempFolder.newFile("baz.txt");
tempFolder.newFile("qux.txt");
File dir1 = tempFolder.newFolder();
File file3 = File.createTempFile("foo", ".txt", dir1);
Message<File> requestMessage = MessageBuilder.withPayload(tempFolder.getRoot())
.build();
@SuppressWarnings("unchecked")
List<String> out = (List<String>) gw.handleRequestMessage(requestMessage);
assertEquals(3, out.size());
assertThat(out.get(0),
not(equalTo(out.get(1))));
assertThat(out.get(0), anyOf(
equalTo("foo/baz.txt"), equalTo("foo/qux.txt"), equalTo("foo/" + dir1.getName() + "/" + file3.getName())));
assertThat(out.get(1), anyOf(
equalTo("foo/baz.txt"), equalTo("foo/qux.txt"), equalTo("foo/" + dir1.getName() + "/" + file3.getName())));
assertThat(out.get(2), anyOf(
equalTo("foo/baz.txt"), equalTo("foo/qux.txt"), equalTo("foo/" + dir1.getName() + "/" + file3.getName())));
}
}
class TestRemoteFileOutboundGateway extends AbstractRemoteFileOutboundGateway<TestLsEntry> {
@@ -998,6 +1125,13 @@ class TestRemoteFileOutboundGateway extends AbstractRemoteFileOutboundGateway<Te
this.setBeanFactory(mock(BeanFactory.class));
}
public TestRemoteFileOutboundGateway(RemoteFileTemplate<TestLsEntry> remoteFileTemplate, String command,
String expression) {
super(remoteFileTemplate, command, expression);
this.setBeanFactory(mock(BeanFactory.class));
}
@Override
protected boolean isDirectory(TestLsEntry file) {
return file.isDirectory();