INT-4204: Introduce ExpressionFileListFilter

JIRA: https://jira.spring.io/browse/INT-4204

Fix error message in the `AbstractRemoteFileOutboundGatewayParser`
This commit is contained in:
Artem Bilan
2017-01-16 09:54:45 -05:00
committed by Gary Russell
parent 33b60c9601
commit 9836828f54
23 changed files with 482 additions and 60 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractConsumerEndpointParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.file.filters.ExpressionFileListFilter;
import org.springframework.integration.file.filters.RegexPatternFileListFilter;
import org.springframework.integration.file.filters.SimplePatternFileListFilter;
import org.springframework.integration.file.remote.RemoteFileOperations;
@@ -92,21 +93,34 @@ public abstract class AbstractRemoteFileOutboundGatewayParser extends AbstractCo
protected void configureFilter(BeanDefinitionBuilder builder, Element element, ParserContext parserContext,
String filterAttribute, String patternPrefix, String propertyName) {
String filter = element.getAttribute(filterAttribute);
String filterExpression = element.getAttribute(filterAttribute + "-expression");
String fileNamePattern = element.getAttribute(patternPrefix + "-pattern");
String fileNameRegex = element.getAttribute(patternPrefix + "-regex");
boolean hasFilter = StringUtils.hasText(filter);
boolean hasFilterExpression = StringUtils.hasText(filterExpression);
boolean hasFileNamePattern = StringUtils.hasText(fileNamePattern);
boolean hasFileNameRegex = StringUtils.hasText(fileNameRegex);
int count = hasFilter ? 1 : 0;
count += hasFilterExpression ? 1 : 0;
count += hasFileNamePattern ? 1 : 0;
count += hasFileNameRegex ? 1 : 0;
if (count > 1) {
parserContext.getReaderContext().error("at most one of '" + patternPrefix + "-pattern', " +
"'" + patternPrefix + "-regex', or '" + filterAttribute + "' is allowed on a remote file outbound gateway", element);
parserContext.getReaderContext()
.error("at most one of '" + patternPrefix + "-pattern', " +
"'" + patternPrefix + "-regex', '" + filterAttribute +
"' or '" + filterAttribute + "-expression' is allowed on a remote file outbound gateway",
element);
}
else if (hasFilter) {
builder.addPropertyReference(propertyName, filter);
}
else if (hasFilterExpression) {
BeanDefinition expressionFilterBeanDefinition =
BeanDefinitionBuilder.genericBeanDefinition(ExpressionFileListFilter.class)
.addConstructorArgValue(filterExpression)
.getBeanDefinition();
builder.addPropertyValue(propertyName, expressionFilterBeanDefinition);
}
else if (hasFileNamePattern) {
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.genericBeanDefinition(
"filter".equals(filterAttribute) ?

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.file.filters.ExpressionFileListFilter;
import org.springframework.integration.file.locking.NioFileLocker;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
@@ -87,8 +88,13 @@ public class FileInboundChannelAdapterParser extends AbstractPollingInboundChann
String preventDuplicates = element.getAttribute("prevent-duplicates");
String ignoreHidden = element.getAttribute("ignore-hidden");
String filter = element.getAttribute("filter");
if (!StringUtils.hasText(filter) && !StringUtils.hasText(filenamePattern) && !StringUtils.hasText(filenameRegex)
&& !StringUtils.hasText(preventDuplicates) && !StringUtils.hasText(ignoreHidden)) {
String filterExpression = element.getAttribute("filter-expression");
if (!StringUtils.hasText(filter)
&& !StringUtils.hasText(filenamePattern)
&& !StringUtils.hasText(filenameRegex)
&& !StringUtils.hasText(preventDuplicates)
&& !StringUtils.hasText(ignoreHidden)
&& !StringUtils.hasText(filterExpression)) {
return null;
}
BeanDefinitionBuilder factoryBeanBuilder =
@@ -97,6 +103,17 @@ public class FileInboundChannelAdapterParser extends AbstractPollingInboundChann
if (StringUtils.hasText(filter)) {
factoryBeanBuilder.addPropertyReference("filter", filter);
}
if (StringUtils.hasText(filterExpression)) {
if (StringUtils.hasText(filter)) {
parserContext.getReaderContext()
.error("At most one of 'filter' or 'filter-expression' can be provided.", element);
}
BeanDefinition expressionFilterBeanDefinition =
BeanDefinitionBuilder.genericBeanDefinition(ExpressionFileListFilter.class)
.addConstructorArgValue(filterExpression)
.getBeanDefinition();
factoryBeanBuilder.addPropertyValue("filter", expressionFilterBeanDefinition);
}
if (StringUtils.hasText(filenamePattern)) {
if (StringUtils.hasText(filter)) {
parserContext.getReaderContext().error(
@@ -113,8 +130,8 @@ public class FileInboundChannelAdapterParser extends AbstractPollingInboundChann
}
IntegrationNamespaceUtils.setValueIfAttributeDefined(factoryBeanBuilder, element, "prevent-duplicates");
IntegrationNamespaceUtils.setValueIfAttributeDefined(factoryBeanBuilder, element, "ignore-hidden");
return BeanDefinitionReaderUtils.registerWithGeneratedName(
factoryBeanBuilder.getBeanDefinition(), parserContext.getRegistry());
return BeanDefinitionReaderUtils.registerWithGeneratedName(factoryBeanBuilder.getBeanDefinition(),
parserContext.getRegistry());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.filters.ExpressionFileListFilter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.file.remote.RemoteFileOperations;
import org.springframework.util.StringUtils;
@@ -32,6 +33,7 @@ import org.springframework.util.StringUtils;
* @author Mark Fisher
* @author David Turanski
* @author Gary Russell
* @author Artem Bilan
* @since 3.0
*
*/
@@ -93,12 +95,14 @@ public final class FileParserUtils {
static void configureFilter(BeanDefinitionBuilder synchronizerBuilder, Element element, ParserContext parserContext,
Class<? extends FileListFilter<?>> patternClass, Class<? extends FileListFilter<?>> regexClass) {
String filter = element.getAttribute("filter");
String filterExpression = element.getAttribute("filter-expression");
String fileNamePattern = element.getAttribute("filename-pattern");
String fileNameRegex = element.getAttribute("filename-regex");
boolean hasFilter = StringUtils.hasText(filter);
boolean hasFilterExpression = StringUtils.hasText(filterExpression);
boolean hasFileNamePattern = StringUtils.hasText(fileNamePattern);
boolean hasFileNameRegex = StringUtils.hasText(fileNameRegex);
if (hasFilter || hasFileNamePattern || hasFileNameRegex) {
if (hasFilter || hasFilterExpression || hasFileNamePattern || hasFileNameRegex) {
int count = 0;
if (hasFilter) {
count++;
@@ -109,13 +113,24 @@ public final class FileParserUtils {
if (hasFileNameRegex) {
count++;
}
if (hasFilterExpression) {
count++;
}
if (count != 1) {
parserContext.getReaderContext().error("at most one of 'filename-pattern', " +
"'filename-regex', or 'filter' is allowed on remote file inbound adapter", element);
"'filename-regex', 'filter' or 'filter-expression' is allowed on remote file inbound adapter",
element);
}
if (hasFilter) {
synchronizerBuilder.addPropertyReference("filter", filter);
}
else if (hasFilterExpression) {
BeanDefinition expressionFilterBeanDefinition =
BeanDefinitionBuilder.genericBeanDefinition(ExpressionFileListFilter.class)
.addConstructorArgValue(filterExpression)
.getBeanDefinition();
synchronizerBuilder.addPropertyValue("filter", expressionFilterBeanDefinition);
}
else if (hasFileNamePattern) {
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.genericBeanDefinition(patternClass);
filterBuilder.addConstructorArgValue(fileNamePattern);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,16 +17,22 @@
package org.springframework.integration.file.dsl;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.function.Function;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.integration.dsl.ComponentsRegistration;
import org.springframework.integration.dsl.MessageSourceSpec;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.file.DirectoryScanner;
import org.springframework.integration.file.FileLocker;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.config.FileListFilterFactoryBean;
import org.springframework.integration.file.filters.AcceptAllFileListFilter;
import org.springframework.integration.file.filters.AcceptOnceFileListFilter;
import org.springframework.integration.file.filters.ExpressionFileListFilter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.file.filters.IgnoreHiddenFileListFilter;
import org.springframework.integration.file.filters.RegexPatternFileListFilter;
@@ -42,12 +48,15 @@ import org.springframework.util.Assert;
* @since 5.0
*/
public class FileInboundChannelAdapterSpec
extends MessageSourceSpec<FileInboundChannelAdapterSpec, FileReadingMessageSource> {
extends MessageSourceSpec<FileInboundChannelAdapterSpec, FileReadingMessageSource>
implements ComponentsRegistration {
private final FileListFilterFactoryBean fileListFilterFactoryBean = new FileListFilterFactoryBean();
private FileLocker locker;
private ExpressionFileListFilter<File> expressionFileListFilter;
FileInboundChannelAdapterSpec() {
this.target = new FileReadingMessageSource();
}
@@ -117,6 +126,30 @@ public class FileInboundChannelAdapterSpec
return _this();
}
/**
* Configure the {@link ExpressionFileListFilter}.
* @param expression the SpEL expression for files filtering.
* @return the spec.
* @see FileReadingMessageSource#setFilter(FileListFilter)
* @see ExpressionFileListFilter
*/
public FileInboundChannelAdapterSpec filterExpression(String expression) {
this.expressionFileListFilter = new ExpressionFileListFilter<>(expression);
return filter(this.expressionFileListFilter);
}
/**
* Configure the {@link ExpressionFileListFilter}.
* @param filterFunction the {@link Function} for files filtering.
* @return the spec.
* @see FileReadingMessageSource#setFilter(FileListFilter)
* @see ExpressionFileListFilter
*/
public FileInboundChannelAdapterSpec filterFunction(Function<File, Boolean> filterFunction) {
this.expressionFileListFilter = new ExpressionFileListFilter<>(new FunctionExpression<>(filterFunction));
return filter(this.expressionFileListFilter);
}
/**
* Configure an {@link AcceptOnceFileListFilter} if {@code preventDuplicates == true},
* otherwise - {@link AcceptAllFileListFilter}.
@@ -226,4 +259,14 @@ public class FileInboundChannelAdapterSpec
return this;
}
@Override
public Collection<Object> getComponentsToRegister() {
if (this.expressionFileListFilter != null) {
return Collections.singleton(this.expressionFileListFilter);
}
else {
return null;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,15 +17,18 @@
package org.springframework.integration.file.dsl;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import org.springframework.expression.Expression;
import org.springframework.integration.dsl.ComponentsRegistration;
import org.springframework.integration.dsl.MessageSourceSpec;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.filters.CompositeFileListFilter;
import org.springframework.integration.file.filters.ExpressionFileListFilter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer;
import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizingMessageSource;
@@ -43,12 +46,15 @@ import org.springframework.integration.file.remote.synchronizer.AbstractInboundF
*/
public abstract class RemoteFileInboundChannelAdapterSpec<F, S extends RemoteFileInboundChannelAdapterSpec<F, S, MS>,
MS extends AbstractInboundFileSynchronizingMessageSource<F>>
extends MessageSourceSpec<S, MS> implements ComponentsRegistration {
extends MessageSourceSpec<S, MS>
implements ComponentsRegistration {
protected final AbstractInboundFileSynchronizer<F> synchronizer;
private CompositeFileListFilter<F> filter;
private ExpressionFileListFilter<F> expressionFileListFilter;
protected RemoteFileInboundChannelAdapterSpec(AbstractInboundFileSynchronizer<F> synchronizer) {
this.synchronizer = synchronizer;
}
@@ -171,7 +177,7 @@ public abstract class RemoteFileInboundChannelAdapterSpec<F, S extends RemoteFil
this.filter = (CompositeFileListFilter<F>) filter;
}
else {
this.filter = new CompositeFileListFilter<F>();
this.filter = new CompositeFileListFilter<>();
this.filter.addFilter(filter);
}
this.synchronizer.setFilter(this.filter);
@@ -182,6 +188,30 @@ public abstract class RemoteFileInboundChannelAdapterSpec<F, S extends RemoteFil
return _this();
}
/**
* Configure the {@link ExpressionFileListFilter}.
* @param expression the SpEL expression for files filtering.
* @return the spec.
* @see FileReadingMessageSource#setFilter(FileListFilter)
* @see ExpressionFileListFilter
*/
public S filterExpression(String expression) {
this.expressionFileListFilter = new ExpressionFileListFilter<>(expression);
return filter(this.expressionFileListFilter);
}
/**
* Configure the {@link ExpressionFileListFilter}.
* @param filterFunction the {@link Function} for files filtering.
* @return the spec.
* @see FileReadingMessageSource#setFilter(FileListFilter)
* @see ExpressionFileListFilter
*/
public S filterFunction(Function<F, Boolean> filterFunction) {
this.expressionFileListFilter = new ExpressionFileListFilter<>(new FunctionExpression<>(filterFunction));
return filter(this.expressionFileListFilter);
}
/**
* Configure a simple pattern filter (e.g. '*.txt').
* @param pattern the pattern.
@@ -220,7 +250,14 @@ public abstract class RemoteFileInboundChannelAdapterSpec<F, S extends RemoteFil
@Override
public Collection<Object> getComponentsToRegister() {
return Collections.singletonList(this.synchronizer);
List<Object> componentsToRegister = new ArrayList<>();
componentsToRegister.add(this.synchronizer);
if (this.expressionFileListFilter != null) {
componentsToRegister.add(this.expressionFileListFilter);
}
return componentsToRegister;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,18 @@
package org.springframework.integration.file.dsl;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import org.springframework.expression.Expression;
import org.springframework.integration.dsl.ComponentsRegistration;
import org.springframework.integration.dsl.MessageHandlerSpec;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.filters.CompositeFileListFilter;
import org.springframework.integration.file.filters.ExpressionFileListFilter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.file.filters.RegexPatternFileListFilter;
import org.springframework.integration.file.filters.SimplePatternFileListFilter;
@@ -40,10 +46,15 @@ import org.springframework.messaging.Message;
* @since 5.0
*/
public abstract class RemoteFileOutboundGatewaySpec<F, S extends RemoteFileOutboundGatewaySpec<F, S>>
extends MessageHandlerSpec<S, AbstractRemoteFileOutboundGateway<F>> {
extends MessageHandlerSpec<S, AbstractRemoteFileOutboundGateway<F>>
implements ComponentsRegistration {
private CompositeFileListFilter<F> filter;
private ExpressionFileListFilter<F> expressionFileListFilter;
private ExpressionFileListFilter<File> mputExpressionFileListFilter;
private CompositeFileListFilter<File> mputFilter;
protected RemoteFileOutboundGatewaySpec(AbstractRemoteFileOutboundGateway<F> outboundGateway) {
@@ -166,6 +177,30 @@ public abstract class RemoteFileOutboundGatewaySpec<F, S extends RemoteFileOutbo
return _this();
}
/**
* Configure the {@link ExpressionFileListFilter}.
* @param expression the SpEL expression for files filtering.
* @return the spec.
* @see FileReadingMessageSource#setFilter(FileListFilter)
* @see ExpressionFileListFilter
*/
public S filterExpression(String expression) {
this.expressionFileListFilter = new ExpressionFileListFilter<>(expression);
return filter(this.expressionFileListFilter);
}
/**
* Configure the {@link ExpressionFileListFilter}.
* @param filterFunction the {@link Function} for files filtering.
* @return the spec.
* @see FileReadingMessageSource#setFilter(FileListFilter)
* @see ExpressionFileListFilter
*/
public S filterFunction(Function<F, Boolean> filterFunction) {
this.expressionFileListFilter = new ExpressionFileListFilter<>(new FunctionExpression<>(filterFunction));
return filter(this.expressionFileListFilter);
}
/**
* A {@link FileListFilter} that runs against the <em>local</em> file system view when
* using {@code MPUT} command.
@@ -205,10 +240,34 @@ public abstract class RemoteFileOutboundGatewaySpec<F, S extends RemoteFileOutbo
* @param regex the {@link SimplePatternFileListFilter} for {@code MPUT} command.
* @return the Spec.
*/
public S regexMpuFilter(String regex) {
public S regexMputFilter(String regex) {
return mputFilter(new RegexPatternFileListFilter(regex));
}
/**
* Configure the {@link ExpressionFileListFilter}.
* @param expression the SpEL expression for files filtering.
* @return the spec.
* @see FileReadingMessageSource#setFilter(FileListFilter)
* @see ExpressionFileListFilter
*/
public S mputFilterExpression(String expression) {
this.mputExpressionFileListFilter = new ExpressionFileListFilter<>(expression);
return mputFilter(this.mputExpressionFileListFilter);
}
/**
* Configure the {@link ExpressionFileListFilter}.
* @param filterFunction the {@link Function} for files filtering.
* @return the spec.
* @see FileReadingMessageSource#setFilter(FileListFilter)
* @see ExpressionFileListFilter
*/
public S mputFilterFunction(Function<File, Boolean> filterFunction) {
this.mputExpressionFileListFilter = new ExpressionFileListFilter<>(new FunctionExpression<>(filterFunction));
return mputFilter(this.mputExpressionFileListFilter);
}
/**
* Specify a SpEL expression for files renaming during transfer.
* @param expression the String in SpEL syntax.
@@ -280,6 +339,20 @@ public abstract class RemoteFileOutboundGatewaySpec<F, S extends RemoteFileOutbo
return _this();
}
@Override
public Collection<Object> getComponentsToRegister() {
List<Object> componentsToRegister = new ArrayList<>();
if (this.expressionFileListFilter != null) {
componentsToRegister.add(this.expressionFileListFilter);
}
if (this.mputExpressionFileListFilter != null) {
componentsToRegister.add(this.expressionFileListFilter);
}
return componentsToRegister;
}
/**
* Specify a simple pattern to match remote files (e.g. '*.txt').
* @param pattern the pattern.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,12 +16,17 @@
package org.springframework.integration.file.dsl;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Function;
import org.springframework.expression.Expression;
import org.springframework.integration.dsl.ComponentsRegistration;
import org.springframework.integration.dsl.MessageSourceSpec;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.filters.CompositeFileListFilter;
import org.springframework.integration.file.filters.ExpressionFileListFilter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.file.remote.AbstractRemoteFileStreamingMessageSource;
import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizingMessageSource;
@@ -38,13 +43,16 @@ import org.springframework.messaging.Message;
*
* @since 5.0
*/
public abstract class
RemoteFileStreamingInboundChannelAdapterSpec<F, S extends RemoteFileStreamingInboundChannelAdapterSpec<F, S, MS>,
public abstract class RemoteFileStreamingInboundChannelAdapterSpec<F,
S extends RemoteFileStreamingInboundChannelAdapterSpec<F, S, MS>,
MS extends AbstractRemoteFileStreamingMessageSource<F>>
extends MessageSourceSpec<S, MS> {
extends MessageSourceSpec<S, MS>
implements ComponentsRegistration {
private CompositeFileListFilter<F> filter;
private ExpressionFileListFilter<F> expressionFileListFilter;
/**
* Configure the file name path separator used by the remote system. Defaults to '/'.
* @param remoteFileSeparator the remoteFileSeparator.
@@ -109,6 +117,40 @@ public abstract class
return _this();
}
/**
* Configure the {@link ExpressionFileListFilter}.
* @param expression the SpEL expression for files filtering.
* @return the spec.
* @see FileReadingMessageSource#setFilter(FileListFilter)
* @see ExpressionFileListFilter
*/
public S filterExpression(String expression) {
this.expressionFileListFilter = new ExpressionFileListFilter<>(expression);
return filter(this.expressionFileListFilter);
}
/**
* Configure the {@link ExpressionFileListFilter}.
* @param filterFunction the {@link Function} for files filtering.
* @return the spec.
* @see FileReadingMessageSource#setFilter(FileListFilter)
* @see ExpressionFileListFilter
*/
public S filterFunction(Function<F, Boolean> filterFunction) {
this.expressionFileListFilter = new ExpressionFileListFilter<>(new FunctionExpression<>(filterFunction));
return filter(this.expressionFileListFilter);
}
@Override
public Collection<Object> getComponentsToRegister() {
if (this.expressionFileListFilter != null) {
return Collections.singleton(this.expressionFileListFilter);
}
else {
return null;
}
}
/**
* Configure a simple pattern filter (e.g. '*.txt').
* @param pattern the pattern.

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.file.filters;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.util.Assert;
/**
* A SpEL expression based {@link AbstractFileListFilter} implementation.
*
* @author Artem Bilan
*
* @since 5.0
*/
public class ExpressionFileListFilter<F> extends AbstractFileListFilter<F>
implements BeanFactoryAware {
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
private final Expression expression;
private BeanFactory beanFactory;
private EvaluationContext evaluationContext;
public ExpressionFileListFilter(String expression) {
this(EXPRESSION_PARSER.parseExpression(expression));
Assert.hasText(expression, "'expression' must not be empty");
}
public ExpressionFileListFilter(Expression expression) {
Assert.notNull(expression, "'expression' must not be null");
this.expression = expression;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
protected boolean accept(F file) {
return this.expression.getValue(getEvaluationContext(), file, Boolean.class);
}
private EvaluationContext getEvaluationContext() {
if (this.evaluationContext == null) {
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.beanFactory);
}
return this.evaluationContext;
}
}

View File

@@ -76,6 +76,14 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="filter-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The SpEL expression to evaluate against file to accept it for processing or not.
Mutually exclusive with 'filter' attribute.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="filename-pattern" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[

View File

@@ -1,23 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
xmlns="http://www.springframework.org/schema/integration/file"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:integration="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
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
xmlns="http://www.springframework.org/schema/integration/file"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:integration="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/integration/file http://www.springframework.org/schema/integration/file/spring-integration-file.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
<inbound-channel-adapter id="inputDirPoller"
directory="${inputdir}"
auto-startup="false">
directory="${inputdir}"
filter-expression="true"
auto-startup="false">
<integration:poller fixed-rate="5000"/>
</inbound-channel-adapter>
<context:property-placeholder location="classpath:org/springframework/integration/file/config/test.properties" />
<context:property-placeholder location="classpath:org/springframework/integration/file/config/test.properties"/>
</beans:beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,30 +16,45 @@
package org.springframework.integration.file.config;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import java.io.File;
import java.util.Iterator;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.filters.CompositeFileListFilter;
import org.springframework.integration.file.filters.ExpressionFileListFilter;
import org.springframework.integration.file.filters.FileListFilter;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.File;
import static org.junit.Assert.assertEquals;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author Iwein Fuld
* @author Artem Bilan
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(SpringRunner.class)
public class FileInboundChannelAdapterWithClasspathInPropertiesTests {
@Autowired(required = true)
@Autowired
private FileReadingMessageSource source;
@Autowired
private BeanFactory beanFactory;
private DirectFieldAccessor accessor;
@Before
@@ -48,10 +63,25 @@ public class FileInboundChannelAdapterWithClasspathInPropertiesTests {
}
@Test
@SuppressWarnings("unchecked")
public void inputDirectory() throws Exception {
File expected = new ClassPathResource("").getFile();
File actual = (File) accessor.getPropertyValue("directory");
assertEquals("'directory' should be set", expected, actual);
FileListFilter<File> fileListFilter =
TestUtils.getPropertyValue(this.source, "scanner.filter", FileListFilter.class);
assertThat(fileListFilter, instanceOf(CompositeFileListFilter.class));
Set<FileListFilter<File>> fileFilters =
TestUtils.getPropertyValue(fileListFilter, "fileFilters", Set.class);
assertEquals(2, fileFilters.size());
Iterator<FileListFilter<File>> iterator = fileFilters.iterator();
iterator.next();
FileListFilter<File> expressionFilter = iterator.next();
assertThat(expressionFilter, instanceOf(ExpressionFileListFilter.class));
assertEquals("true",
TestUtils.getPropertyValue(expressionFilter, "expression.expression", String.class));
assertSame(this.beanFactory, TestUtils.getPropertyValue(expressionFilter, "beanFactory"));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -391,7 +391,7 @@ public class FileTests {
public IntegrationFlow fileSplitterFlow() {
return IntegrationFlows
.from(Files.inboundAdapter(tmpDir.getRoot())
.patternFilter("foo.tmp"),
.filterFunction(f -> "foo.tmp".equals(f.getName())),
e -> e.poller(p -> p.fixedDelay(100)))
.split(Files.splitter()
.markers()

View File

@@ -337,6 +337,14 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="filter-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The SpEL expression to evaluate against file to accept it for processing or not.
Mutually exclusive with 'filter' attribute.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="filename-pattern" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
@@ -376,6 +384,14 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mput-filter-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The SpEL expression to evaluate against file to accept it for processing or not.
Mutually exclusive with 'mput-filter' attribute.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mput-pattern" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
@@ -546,6 +562,14 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="filter-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The SpEL expression to evaluate against file to accept it for processing or not.
Mutually exclusive with 'filter' attribute.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="integration:maxFetchGroup" />
</xsd:extension>
</xsd:complexContent>

View File

@@ -34,7 +34,7 @@
expression="payload"
order="1"
mode="APPEND"
mput-regex=".*">
mput-filter-expression="name matches '.*'">
<int:poller fixed-delay="1000"/>
</int-ftp:outbound-gateway>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.Expression;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.file.filters.ExpressionFileListFilter;
import org.springframework.integration.file.filters.RegexPatternFileListFilter;
import org.springframework.integration.file.filters.SimplePatternFileListFilter;
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway.Command;
@@ -108,7 +109,7 @@ public class FtpOutboundGatewayParserTests {
assertEquals(Long.valueOf(777), sendTimeout);
assertTrue(TestUtils.getPropertyValue(gateway, "requiresReply", Boolean.class));
assertThat(TestUtils.getPropertyValue(gateway, "mputFilter"),
Matchers.instanceOf(RegexPatternFileListFilter.class));
Matchers.instanceOf(ExpressionFileListFilter.class));
assertEquals(FileExistsMode.APPEND, TestUtils.getPropertyValue(gateway, "fileExistsMode"));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -153,7 +153,7 @@ public class FtpTests extends FtpTestSupport {
.handle(Ftp.outboundGateway(sessionFactory(),
AbstractRemoteFileOutboundGateway.Command.MGET, "payload")
.options(AbstractRemoteFileOutboundGateway.Option.RECURSIVE)
.regexFileNameFilter("(subFtpSource|.*1.txt)")
.filterExpression("name matches 'subFtpSource|.*1.txt'")
.localDirectoryExpression("'" + getTargetLocalDirectoryName() + "' + #remoteDirectory")
.localFilenameExpression("#remoteFileName.replaceFirst('ftpSource', 'localTarget')"))
.channel(out);

View File

@@ -339,6 +339,14 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="filter-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The SpEL expression to evaluate against file to accept it for processing or not.
Mutually exclusive with 'filter' attribute.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="filename-pattern" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
@@ -378,6 +386,14 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mput-filter-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The SpEL expression to evaluate against file to accept it for processing or not.
Mutually exclusive with 'mput-filter' attribute.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mput-pattern" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
@@ -547,6 +563,14 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="filter-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The SpEL expression to evaluate against file to accept it for processing or not.
Mutually exclusive with 'filter' attribute.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="integration:maxFetchGroup" />
</xsd:extension>
</xsd:complexContent>

View File

@@ -40,7 +40,7 @@
session-factory="csf"
auto-startup="false"
phase="23"
filename-pattern="*.txt"
filter-expression="new org.springframework.util.AntPathMatcher().match('*.txt', filename)"
remote-file-separator="X"
remote-directory-expression="'foo/bar'">
<int:poller fixed-rate="1000" />

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.file.filters.ExpressionFileListFilter;
import org.springframework.integration.file.remote.session.CachingSessionFactory;
import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFilter;
import org.springframework.integration.sftp.inbound.SftpStreamingMessageSource;
@@ -45,6 +46,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Gary Russell
* @author Artem Bilan
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@@ -54,6 +56,9 @@ public class SftpStreamingInboundChannelAdapterParserTests {
@Autowired
private SourcePollingChannelAdapter sftpInbound;
@Autowired
private SourcePollingChannelAdapter contextLoadsWithNoComparator;
@Autowired
private MessageChannel sftpChannel;
@@ -74,6 +79,10 @@ public class SftpStreamingInboundChannelAdapterParserTests {
assertThat(TestUtils.getPropertyValue(source, "filter"), instanceOf(SftpSimplePatternFileListFilter.class));
assertSame(this.csf, TestUtils.getPropertyValue(source, "remoteFileTemplate.sessionFactory"));
assertEquals(31, TestUtils.getPropertyValue(source, "maxFetchSize"));
source = TestUtils.getPropertyValue(this.contextLoadsWithNoComparator, "source",
SftpStreamingMessageSource.class);
assertThat(TestUtils.getPropertyValue(source, "filter"), instanceOf(ExpressionFileListFilter.class));
}
public static class TestSessionFactoryBean implements FactoryBean<DefaultSftpSessionFactory> {

View File

@@ -69,7 +69,7 @@ metadata store on every update (if the store implements `Flushable`).
A common problem with reading files is that a file may be detected before it is ready.
The default `AcceptOnceFileListFilter` does not prevent this.
In most cases, this can be prevented if the file-writing process renames each file as soon as it is ready for reading.
A filename-pattern or filename-regex filter that accepts only files that are ready (e.g.
A `filename-pattern` or `filename-regex` filter that accepts only files that are ready (e.g.
based on a known suffix), composed with the default `AcceptOnceFileListFilter` allows for this.
The `CompositeFileListFilter` enables the composition.
[source,xml]
@@ -106,6 +106,16 @@ to, say, network glitches.
</bean>
----
Starting with _version 5.0_ an `ExpressionFileListFilter` has been introduced to allow to execute SpEL expression against file as a context evaluation root object.
For this purpose all the XML components for file handling (local and remote), alongside with an existing `filter` attribute, have been supplied with the `filter-expression` option:
[source, xml]
----
<int-file:inbound-channel-adapter
directory="${inputdir}"
filter-expression="name matches '.text'"
auto-startup="false"/>
----
*Message Headers*
Starting with _version 5.0_ the `FileReadingMessageSource`, in addition to the `payload` as a polled `File`, populates these headers to the outbound `Message`:

View File

@@ -516,6 +516,7 @@ See <<file-splitter>> and <<stream-transformer>> for more information about thes
filename-pattern="*.txt"
filename-regex=".*\.txt"
filter="filter"
filter-expression="@myFilterBean.check(#root)"
remote-file-separator="/"
comparator="comparator"
max-fetch-size="1"
@@ -524,7 +525,7 @@ See <<file-splitter>> and <<stream-transformer>> for more information about thes
</int-ftp:inbound-streaming-channel-adapter>
----
Only one of `filename-pattern`, `filename-regex` or `filter` is allowed.
Only one of `filename-pattern`, `filename-regex`, `filter` or `filter-expression` is allowed.
IMPORTANT: Unlike the non-streaming inbound channel adapter, this adapter does not prevent duplicates by default.
If you do not delete the remote file (e.g. using an outbound gateway with an rm command) and you wish to prevent the
@@ -911,7 +912,7 @@ _mput_ sends multiple files to the server and supports the following option:
The message payload must be a `java.io.File` representing a local directory.
The same attributes as the `put` command are supported.
In addition, files in the local directory can be filtered with one of `mput-pattern`, `mput-regex` or `mput-filter`.
In addition, files in the local directory can be filtered with one of `mput-pattern`, `mput-regex`, `mput-filter` or `mput-filter-expression`.
The filter works with recursion, as long as the subdirectories themselves pass the filter.
Subdirectories that do not pass the filter are not recursed.

View File

@@ -555,6 +555,7 @@ See <<file-splitter>> and <<stream-transformer>> for more information about thes
filename-pattern="*.txt"
filename-regex=".*\.txt"
filter="filter"
filter-expression="@myFilterBean.check(#root)"
remote-file-separator="/"
comparator="comparator"
max-fetch-size="1"
@@ -563,7 +564,7 @@ See <<file-splitter>> and <<stream-transformer>> for more information about thes
</int-sftp:inbound-streaming-channel-adapter>
----
Only one of `filename-pattern`, `filename-regex` or `filter` is allowed.
Only one of `filename-pattern`, `filename-regex`, `filter` or `filter-expression` is allowed.
IMPORTANT: Unlike the non-streaming inbound channel adapter, this adapter does not prevent duplicates by default.
If you do not delete the remote file (e.g. using an outbound gateway with an rm command) and you wish to prevent the
@@ -935,7 +936,7 @@ _mput_ sends multiple files to the server and supports the following option:
The message payload must be a `java.io.File` representing a local directory.
The same attributes as the `put` command are supported.
In addition, files in the local directory can be filtered with one of `mput-pattern`, `mput-regex` or `mput-filter`.
In addition, files in the local directory can be filtered with one of `mput-pattern`, `mput-regex`, `mput-filter` or `mput-filter-expression`.
The filter works with recursion, as long as the subdirectories themselves pass the filter.
Subdirectories that do not pass the filter are not recursed.