Added PatternMatchingFileListFilter and namespace support for the "filename-pattern" attribute. AbstractFilePayloadTransformer now sets the "filename" Message header. Renamed the filters to include FileListFilter (rather than just FileFilter) to avoid confusion with the FileFilter interface.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2002-2008 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 java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A convenience base class for any {@link FileListFilter} whose criteria can
|
||||
* be evaluated against each File in isolation. If the entire List of files is
|
||||
* required for evaluation, implement the FileListFilter interface directly.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public abstract class AbstractFileListFilter implements FileListFilter {
|
||||
|
||||
/**
|
||||
* Returns the list of files that are accepted by this filter.
|
||||
*/
|
||||
public final List<File> filterFiles(File[] files) {
|
||||
List<File> accepted = new ArrayList<File>();
|
||||
for (File file : files) {
|
||||
if (this.accept(file)) {
|
||||
accepted.add(file);
|
||||
}
|
||||
}
|
||||
return accepted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses must implement this method.
|
||||
*/
|
||||
protected abstract boolean accept(File file);
|
||||
|
||||
}
|
||||
@@ -17,8 +17,6 @@
|
||||
package org.springframework.integration.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
@@ -28,9 +26,8 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
* {@link PollableFileSource}.
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
*
|
||||
*/
|
||||
public class AcceptOnceFileFilter implements FileListFilter {
|
||||
public class AcceptOnceFileListFilter extends AbstractFileListFilter {
|
||||
|
||||
private final Queue<File> seen;
|
||||
|
||||
@@ -45,34 +42,20 @@ public class AcceptOnceFileFilter implements FileListFilter {
|
||||
* @param maxCapacity the maximum number of Files to maintain in the 'seen'
|
||||
* queue.
|
||||
*/
|
||||
public AcceptOnceFileFilter(int maxCapacity) {
|
||||
public AcceptOnceFileListFilter(int maxCapacity) {
|
||||
this.seen = new LinkedBlockingQueue<File>(maxCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AcceptOnceFileFilter based on an unbounded queue.
|
||||
*/
|
||||
public AcceptOnceFileFilter() {
|
||||
public AcceptOnceFileListFilter() {
|
||||
this.seen = new LinkedBlockingQueue<File>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the list of files that have not already been filtered by this
|
||||
* instance.
|
||||
*/
|
||||
public List<File> filterFiles(File[] files) {
|
||||
List<File> accepted = new ArrayList<File>();
|
||||
for (File file : files) {
|
||||
if (accept(file)) {
|
||||
accepted.add(file);
|
||||
}
|
||||
}
|
||||
return accepted;
|
||||
}
|
||||
|
||||
private boolean accept(File pathname) {
|
||||
synchronized (monitor) {
|
||||
protected boolean accept(File pathname) {
|
||||
synchronized (this.monitor) {
|
||||
if (seen.contains(pathname)) {
|
||||
return false;
|
||||
}
|
||||
@@ -26,23 +26,25 @@ import java.util.Set;
|
||||
|
||||
/**
|
||||
* Composition that delegates to multiple {@link FileFilter}s. The composition
|
||||
* is AND based, meaning that all filters must {@link #filterFiles(File)} in
|
||||
* order for a file to be accepted by the composite.
|
||||
* is AND based, meaning that a file must pass through each filter's
|
||||
* {@link #filterFiles(File)} method in order to be accepted by the composite.
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
*/
|
||||
public class CompositeFileFilter implements FileListFilter {
|
||||
public class CompositeFileListFilter implements FileListFilter {
|
||||
|
||||
private final Set<FileListFilter> fileFilters;
|
||||
|
||||
public CompositeFileFilter(FileListFilter... fileFilters) {
|
||||
|
||||
public CompositeFileListFilter(FileListFilter... fileFilters) {
|
||||
this.fileFilters = new HashSet<FileListFilter>(Arrays.asList(fileFilters));
|
||||
}
|
||||
|
||||
public CompositeFileFilter(Collection<FileListFilter> fileFilters) {
|
||||
public CompositeFileListFilter(Collection<FileListFilter> fileFilters) {
|
||||
this.fileFilters = new HashSet<FileListFilter>(fileFilters);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -59,7 +61,7 @@ public class CompositeFileFilter implements FileListFilter {
|
||||
* @param filters one or more new filters to be used
|
||||
* @return a new CompositeFileFilter with the additional filters
|
||||
*/
|
||||
public CompositeFileFilter addFilter(FileListFilter... filters) {
|
||||
public CompositeFileListFilter addFilter(FileListFilter... filters) {
|
||||
return addFilters(Arrays.asList(filters));
|
||||
}
|
||||
|
||||
@@ -70,9 +72,10 @@ public class CompositeFileFilter implements FileListFilter {
|
||||
* @param filtersToAdd
|
||||
* @return a new CompositeFileFilter with the added filters
|
||||
*/
|
||||
public CompositeFileFilter addFilters(Collection<FileListFilter> filtersToAdd) {
|
||||
public CompositeFileListFilter addFilters(Collection<FileListFilter> filtersToAdd) {
|
||||
HashSet<FileListFilter> newFilterSet = new HashSet<FileListFilter>(filtersToAdd);
|
||||
newFilterSet.addAll(fileFilters);
|
||||
return new CompositeFileFilter(newFilterSet);
|
||||
return new CompositeFileListFilter(newFilterSet);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,29 @@
|
||||
/*
|
||||
* Copyright 2002-2008 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 java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Strategy interface for filtering a group of files.
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
*/
|
||||
public interface FileListFilter {
|
||||
|
||||
List<File> filterFiles(File[] files);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2002-2008 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 java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link FileListFilter} implementation that matches against a {@link Pattern}.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class PatternMatchingFileListFilter extends AbstractFileListFilter {
|
||||
|
||||
private final Pattern pattern;
|
||||
|
||||
|
||||
/**
|
||||
* Create a filter for the given pattern.
|
||||
*/
|
||||
public PatternMatchingFileListFilter(Pattern pattern) {
|
||||
Assert.notNull(pattern, "pattern must not be null");
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
|
||||
protected boolean accept(File file) {
|
||||
return (file != null)
|
||||
&& this.pattern.matcher(file.getName()).matches();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.file;
|
||||
|
||||
import java.io.File;
|
||||
@@ -26,6 +27,7 @@ import java.util.concurrent.PriorityBlockingQueue;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.integration.message.Message;
|
||||
@@ -36,16 +38,17 @@ import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* PollableSource that creates messages from a file system directory. To prevent
|
||||
* messages from showing up on the source you can supply a FileFilter to it. By
|
||||
* default an {@link AcceptOnceFileFilter} is used that ensures files are picked
|
||||
* messages for certain files, you may supply a {@link FileListFilter}. By
|
||||
* default, an {@link AcceptOnceFileListFilter} is used. It ensures files are picked
|
||||
* up only once from the directory.
|
||||
*
|
||||
* A common problem with reading files is that files are picked up that are not
|
||||
* ready. The default {@link AcceptOnceFileFilter} does not prevent this. In
|
||||
* most cases this can be prevented by renaming the files as soon as they are
|
||||
* ready. A FileFilter that accepts only files that are ready, composed with the
|
||||
* default {@link AcceptOnceFileFilter} would allow for this.
|
||||
* @see CompositeFileFilter for a way to do this.
|
||||
* <p>
|
||||
* A common problem with reading files is that a file may be detected before it
|
||||
* is ready. The default {@link 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 pattern-matching filter that
|
||||
* accepts only files that are ready (e.g. based on a known suffix), composed
|
||||
* with the default {@link AcceptOnceFileListFilter} would allow for this.
|
||||
* See {@ link CompositeFileFilter} for a way to do this.
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
*/
|
||||
@@ -57,7 +60,8 @@ public class PollableFileSource implements PollableSource<File>, MessageDelivery
|
||||
|
||||
private final Queue<File> toBeReceived = new PriorityBlockingQueue<File>();
|
||||
|
||||
private volatile FileListFilter filter = new AcceptOnceFileFilter();
|
||||
private volatile FileListFilter filter = new AcceptOnceFileListFilter();
|
||||
|
||||
|
||||
public void setInputDirectory(Resource inputDirectory) {
|
||||
Assert.notNull(inputDirectory, "inputDirectory cannot be null");
|
||||
@@ -73,10 +77,10 @@ public class PollableFileSource implements PollableSource<File>, MessageDelivery
|
||||
|
||||
/**
|
||||
* Sets a {@link FileFilter} on the {@link PollableSource}. By default a
|
||||
* {@link AcceptOnceFileFilter} with no bounds is used. In most cases a
|
||||
* {@link AcceptOnceFileListFilter} with no bounds is used. In most cases a
|
||||
* customized {@link FileFilter} will be needed to deal with modification
|
||||
* and duplication concerns. If multiple filters are required a
|
||||
* {@link CompositeFileFilter} can be used to group them together <p/>
|
||||
* {@link CompositeFileListFilter} can be used to group them together <p/>
|
||||
* <b>Note that the supplied filter must be thread safe</b>.
|
||||
*/
|
||||
public void setFilter(FileListFilter filter) {
|
||||
|
||||
@@ -16,12 +16,18 @@
|
||||
|
||||
package org.springframework.integration.file.config;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.integration.ConfigurationException;
|
||||
import org.springframework.integration.config.AbstractPollingInboundChannelAdapterParser;
|
||||
import org.springframework.integration.file.AcceptOnceFileListFilter;
|
||||
import org.springframework.integration.file.CompositeFileListFilter;
|
||||
import org.springframework.integration.file.PatternMatchingFileListFilter;
|
||||
import org.springframework.integration.file.PollableFileSource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -29,6 +35,7 @@ import org.springframework.util.StringUtils;
|
||||
* Parser for the <inbound-channel-adapter> element of the 'file' namespace.
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class FileInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser {
|
||||
|
||||
@@ -43,6 +50,17 @@ public class FileInboundChannelAdapterParser extends AbstractPollingInboundChann
|
||||
if (StringUtils.hasText(filter)){
|
||||
builder.addPropertyReference("filter", filter);
|
||||
}
|
||||
String filenamePattern = element.getAttribute("filename-pattern");
|
||||
if (StringUtils.hasText(filenamePattern)) {
|
||||
if (StringUtils.hasText(filter)) {
|
||||
throw new ConfigurationException("at most one of 'filter' and 'filename-pattern' may be provided");
|
||||
}
|
||||
AcceptOnceFileListFilter acceptOnceFilter = new AcceptOnceFileListFilter();
|
||||
Pattern pattern = Pattern.compile(filenamePattern);
|
||||
PatternMatchingFileListFilter patternFilter = new PatternMatchingFileListFilter(pattern);
|
||||
CompositeFileListFilter compositeFilter = new CompositeFileListFilter(acceptOnceFilter, patternFilter);
|
||||
builder.addPropertyValue("filter", compositeFilter);
|
||||
}
|
||||
return BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), parserContext.getRegistry());
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<xsd:attribute name="directory" type="xsd:string"/>
|
||||
<xsd:attribute name="channel" type="xsd:string"/>
|
||||
<xsd:attribute name="filter" type="xsd:string"/>
|
||||
<xsd:attribute name="filename-pattern" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
|
||||
@@ -18,8 +18,13 @@ package org.springframework.integration.file.transformer;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.integration.message.Message;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
import org.springframework.integration.message.MessagingException;
|
||||
import org.springframework.integration.transformer.AbstractPayloadTransformer;
|
||||
import org.springframework.integration.transformer.Transformer;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -27,32 +32,41 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public abstract class AbstractFilePayloadTransformer<T> extends AbstractPayloadTransformer<File, T> {
|
||||
public abstract class AbstractFilePayloadTransformer<T> implements Transformer {
|
||||
|
||||
private volatile boolean deleteFile;
|
||||
private final Log logger = LogFactory.getLog(this.getClass());
|
||||
|
||||
private volatile boolean deleteFileAfterTransformation;
|
||||
|
||||
|
||||
/**
|
||||
* Specify whether to delete the File after transformation.
|
||||
*/
|
||||
public void setDeleteFile(boolean deleteFile) {
|
||||
this.deleteFile = deleteFile;
|
||||
public void setDeleteFileAfterTransformation(boolean deleteFileAfterTransformation) {
|
||||
this.deleteFileAfterTransformation = deleteFileAfterTransformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final T transformPayload(File file) throws Exception {
|
||||
Assert.notNull(file, "File must not be null");
|
||||
if (!file.exists()) {
|
||||
throw new MessagingException("File '" + file + "' no longer exists.");
|
||||
}
|
||||
if (!file.canRead()) {
|
||||
throw new MessagingException("Unable to read File '" + file + "'");
|
||||
}
|
||||
T result = transformFile(file);
|
||||
if (this.deleteFile) {
|
||||
file.delete();
|
||||
}
|
||||
return result;
|
||||
public final Message<?> transform(Message<?> message) {
|
||||
try {
|
||||
Assert.notNull(message, "Message must not be null");
|
||||
Object payload = message.getPayload();
|
||||
Assert.notNull(payload, "Mesasge payload must not be null");
|
||||
Assert.isInstanceOf(File.class, payload, "Message payload must be of type [java.io.File]");
|
||||
File file = (File) payload;
|
||||
T result = this.transformFile(file);
|
||||
Message<?> transformedMessage = MessageBuilder.withPayload(result)
|
||||
.copyHeaders(message.getHeaders())
|
||||
.setHeaderIfAbsent("filename", file.getName())
|
||||
.build();
|
||||
if (this.deleteFileAfterTransformation) {
|
||||
if (!file.delete() && this.logger.isWarnEnabled()) {
|
||||
this.logger.warn("failed to delete File '" + file + "'");
|
||||
}
|
||||
}
|
||||
return transformedMessage;
|
||||
} catch (Exception e) {
|
||||
throw new MessagingException(message, "failed to transform File Message", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user