diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java index 8fc19e0bc2..2e90df8dd1 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/AbstractRemoteFileOutboundGatewayParser.java @@ -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()); } } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileParserUtils.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileParserUtils.java new file mode 100644 index 0000000000..e9ea713af8 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileParserUtils.java @@ -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(); + } + +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java index 919c79114a..2d4faa9ea6 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/RemoteFileOutboundChannelAdapterParser.java @@ -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); - } - } - } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractPersistentAcceptOnceFileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractPersistentAcceptOnceFileListFilter.java index 0f752473f5..3a44133ca7 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractPersistentAcceptOnceFileListFilter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractPersistentAcceptOnceFileListFilter.java @@ -69,8 +69,8 @@ public abstract class AbstractPersistentAcceptOnceFileListFilter extends Abst * 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 + * @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); diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileOperations.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileOperations.java index 3f5ca23732..56c49c82e4 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileOperations.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileOperations.java @@ -29,11 +29,24 @@ public interface RemoteFileOperations { /** * Send a file to a remote server, based on information in a message. * - * @param message The message + * @param message The message. + * @return The remote path, or null if no local file was found. * @throws Exception */ - void send(Message message); + 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. * diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java index 02e3918ee2..b11f503e36 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/RemoteFileTemplate.java @@ -99,6 +99,10 @@ public class RemoteFileTemplate implements RemoteFileOperations, Initializ 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(remoteDirectoryExpression, String.class); @@ -173,18 +177,32 @@ public class RemoteFileTemplate implements RemoteFileOperations, Initializ } @Override - public void send(final Message message) { + 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) { - this.execute(new SessionCallbackWithoutResult() { + return this.execute(new SessionCallback() { @Override - public void doInSessionWithoutResult(Session session) throws IOException { + public String doInSession(Session 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 @@ -193,6 +211,7 @@ public class RemoteFileTemplate implements RemoteFileOperations, Initializ 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() @@ -215,6 +234,7 @@ public class RemoteFileTemplate implements RemoteFileOperations, Initializ if (logger.isWarnEnabled()) { logger.warn("File " + message.getPayload() + " does not exist"); } + return null; } } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/SessionCallback.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/SessionCallback.java index 2f0fc06277..1970bb2a6d 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/SessionCallback.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/SessionCallback.java @@ -20,7 +20,7 @@ import java.io.IOException; import org.springframework.integration.file.remote.session.Session; /** - * Callback invoked by {@code RemoteFileOperations.execute()) - allows multiple operations + * Callback invoked by {@code RemoteFileOperations.execute()} - allows multiple operations * on a session. * * @author Gary Russell diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java index e1ece85a17..024a4c46eb 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/remote/gateway/AbstractRemoteFileOutboundGateway.java @@ -92,7 +92,17 @@ public abstract class AbstractRemoteFileOutboundGateway 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; @@ -188,19 +198,20 @@ public abstract class AbstractRemoteFileOutboundGateway extends AbstractReply protected volatile Set