merging from the branch for the changes made to support EntryListFilters instead of FileListFilters.

This commit is contained in:
Josh Long
2010-08-21 01:42:55 +00:00
91 changed files with 2860 additions and 2786 deletions

View File

@@ -0,0 +1,161 @@
package org.springframework.integration.file;
import org.springframework.core.io.Resource;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.file.entries.AcceptAllEntryListFilter;
import org.springframework.integration.file.entries.EntryListFilter;
import org.springframework.scheduling.Trigger;
import org.springframework.util.Assert;
import java.util.concurrent.ScheduledFuture;
/**
* Strategy class charged with knowing how to connect to a remote file system, scan it for new files and then downloading the file.
* <p/>
* The implementation should run through any configured {@link org.springframework.integration.file.entries.EntryListFilter}s
* to ensure the entry is worth downloading.
*
* @author Josh Long
*/
public abstract class AbstractInboundRemoteFileSystemSychronizer<T> extends AbstractEndpoint {
/**
* Should we <emphasis>delete</emphasis> the <b>source</b> file?
* For an FTP server, for example, this would delete the original FTPFile instance
* <p/>
* At the moment I can simply see this triggering an implementation specific {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy}
* implementation that knows how to delete an entry on the remote file system.
*/
protected boolean shouldDeleteSourceFile;
/**
* the directory we're writing our synchronizations to
*/
protected volatile Resource localDirectory;
/**
* a {@link org.springframework.integration.file.entries.EntryListFilter} that we're running against the <emphasis>remote</emphasis> file system view!
*/
protected volatile EntryListFilter<T> filter = new AcceptAllEntryListFilter<T>();
/**
* the {@link java.util.concurrent.ScheduledFuture} instance we get when we schedule our {@link AbstractInboundRemoteFileSystemSychronizer.SynchronizeTask}
*/
protected ScheduledFuture<?> scheduledFuture;
/**
* Used to store the {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy} implementation
*/
protected EntryAcknowledgmentStrategy<T> entryAcknowledgmentStrategy;
/**
* Obviously thread safe - simply provides a NOOP impl so we don't have to keep dancing around NPE's
*/
private EntryAcknowledgmentStrategy<T> noOpEntryAcknowledgmentStrategy = new EntryAcknowledgmentStrategy<T>() {
public void acknowledge(Object o, T msg) {
}
};
public void setEntryAcknowledgmentStrategy(EntryAcknowledgmentStrategy<T> entryAcknowledgmentStrategy) {
this.entryAcknowledgmentStrategy = entryAcknowledgmentStrategy;
}
public void setShouldDeleteSourceFile(boolean shouldDeleteSourceFile) {
this.shouldDeleteSourceFile = shouldDeleteSourceFile;
}
public void setLocalDirectory(Resource localDirectory) {
this.localDirectory = localDirectory;
}
public void setFilter(EntryListFilter<T> filter) {
this.filter = filter;
}
/**
* @param usefulContextOrClientData this is context information to be passed to the individual {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy} implementation.
* {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy#acknowledge(Object, Object)} will be called
* in line with the {@link org.springframework.integration.core.MessageSource#receive()} call so this could conceivably be a 'live' stateful
* client (a connection?) that is inappropriate to cache as it has per-request state.
* @param t leverages strategy implementations to enable different behavior. It's a hook to the entry ({@link T}) after it's been successfully downloaded.
* Conceptually, you might delete the remote one or rename it or something
* @throws Throwable escape hatch exception, let the adapter deal with it.
*/
protected void acknowledge(Object usefulContextOrClientData, T t)
throws Throwable {
Assert.notNull(this.entryAcknowledgmentStrategy != null, "entryAcknowledgmentStrategy can't be null!");
this.entryAcknowledgmentStrategy.acknowledge(usefulContextOrClientData, t);
}
/**
* This is the callback where we need the implementation to do some specific work
*
* @throws Exception thrown if anything goes wrong
*/
protected abstract void syncRemoteToLocalFileSystem()
throws Exception;
/**
* {@inheritDoc}
*/
protected void doStop() {
Assert.notNull(this.scheduledFuture, "the 'scheduledFuture' can't be null!");
this.scheduledFuture.cancel(true);
}
/**
* Returns a value in millis dictating how frequently the trigger should fire
*
* @return a {@link org.springframework.scheduling.Trigger} implementation (likely,
* {@link org.springframework.scheduling.support.PeriodicTrigger})
*/
protected abstract Trigger getTrigger();
/**
* {@inheritDoc}
*/
protected void doStart() {
if (this.entryAcknowledgmentStrategy == null) {
this.entryAcknowledgmentStrategy = noOpEntryAcknowledgmentStrategy;
}
this.scheduledFuture = this.getTaskScheduler().schedule(new SynchronizeTask(), this.getTrigger());
}
/**
* Strategy interface to expose a hook for dispatching, moving, or deleting the file once it's been delivered.
* This will typically be a NOOP for the implementation. Adapters should (for consistency) expose an attribute
* dictating whether the adapter will delete the <emphasis>source</emphasis> entry on the remote file system.
* This is the file-system version of an <code>ack-mode</code>. Future implementations should consider
* exposing a custom attribute that plugs a custom {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy}
* into the pipeline and also some more advanced scenarios (i.e., 'move file to another folder on delete ', or 'rename on delete')
*
* @param <T> the entry type (file, sftp, ftp, ...)
*/
public static interface EntryAcknowledgmentStrategy<T> {
/**
* Semantics are simple. You get a pointer to the entry just processed and any kind of helper data you could ask for. Since the strategy is a
* singleton and the clients you might ask for as context data are pooled, it's not recommended that you try to cache them.
*
* @param useful any context data
* @param msg the data / file / entry you want to process -- specific to sublcasses
* @throws Exception thrown for any old reason
*/
void acknowledge(Object useful, T msg) throws Exception;
}
/**
* This {@link Runnable} is launched as a background thread and is used to babysit the
* {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer#localDirectory},
* queueing and delivering accumulated files as possible.
*/
class SynchronizeTask implements Runnable {
public void run() {
try {
syncRemoteToLocalFileSystem();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}

View File

@@ -0,0 +1,115 @@
package org.springframework.integration.file;
import org.springframework.core.io.Resource;
import org.springframework.integration.Message;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.file.entries.*;
import java.io.File;
import java.util.regex.Pattern;
/**
* Ultimately, this factors out a lot of the common logic between the FTP and SFTP adapters. Designed to be extendable to handle
* adapters whose task it is to synchronize a remote file system with a local file system (NB: this does *NOT* handle pushing files TO the remote
* file system that exist uniquely in the local file system. It only handles bringing down the remote file system - as you'd expect
* an 'inbound' adapter would).
* <p/>
* The base class supports configuration of whether the remote file system and local file system's directories should
* be created on start (what 'creating a directory' means to the specific adapter is of course implementaton specific).
* <p/>
* This class is to be used as a pair with an implementation of
* {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer<T>}. This synchronizer
* must handle the work of actually connecting to the remote file system and delivering new {@link java.io.File}s.
* The synchronizer is designed to be
*
* @author Josh Long
*/
public abstract class AbstractInboundRemoteFileSystemSynchronizingMessageSource<Y, T extends AbstractInboundRemoteFileSystemSychronizer<Y>> extends AbstractEndpoint implements MessageSource<File> {
/**
* Extension used when downloading files. We change it right after we know it's downloaded
*/
public static final String INCOMPLETE_EXTENSION = ".INCOMPLETE";
/**
* Should the endpoint attempt to create the local directory and / or the remote directory?
*/
protected volatile boolean autoCreateDirectories = true;
/**
* An implementation that will handle the chores of actually connecting to and syncing up the remote FS with the local one, in an inbound direction
*/
protected volatile T synchronizer;
/**
* What directory should things be synced to locally ?
*/
protected volatile Resource localDirectory;
/**
* The actual {@link FileReadingMessageSource} that we continue to trust to do the job monitoring the filesystem once files are moved down
*/
protected volatile FileReadingMessageSource fileSource;
/**
* The predicate to use in scanning the remote Fs for downloads
*/
protected EntryListFilter<Y> remotePredicate;
public void setAutoCreateDirectories(boolean autoCreateDirectories) {
this.autoCreateDirectories = autoCreateDirectories;
}
public void setSynchronizer(T synchronizer) {
this.synchronizer = synchronizer;
}
public void setLocalDirectory(Resource localDirectory) {
this.localDirectory = localDirectory;
}
public void setRemotePredicate(EntryListFilter<Y> remotePredicate) {
this.remotePredicate = remotePredicate;
}
private EntryListFilter<File> buildFilter() {
FileEntryNamer fileEntryNamer = new FileEntryNamer();
Pattern completePattern = Pattern.compile("^.*(?<!" + INCOMPLETE_EXTENSION + ")$");
return new CompositeEntryListFilter<File>(new AcceptOnceEntryFileListFilter<File>(), new PatternMatchingEntryListFilter<File>(fileEntryNamer, completePattern));
}
@Override
protected void onInit() throws Exception {
if (this.remotePredicate != null) {
this.synchronizer.setFilter(this.remotePredicate);
}
if (this.autoCreateDirectories) {
if ((this.localDirectory != null) && !this.localDirectory.exists() && this.localDirectory.getFile().mkdirs())
logger.debug("the localDirectory " + this.localDirectory + " doesn't exist");
}
/**
* Handles making sure the remote files get here in one piece
*/
this.synchronizer.setLocalDirectory(this.localDirectory);
this.synchronizer.setTaskScheduler(this.getTaskScheduler());
this.synchronizer.setBeanFactory(this.getBeanFactory());
this.synchronizer.setPhase(this.getPhase());
this.synchronizer.setBeanName(this.getComponentName());
/**
* Handles forwarding files once they ultimately appear in the {@link #localDirectory}
*/
this.fileSource = new FileReadingMessageSource();
this.fileSource.setFilter(buildFilter());
this.fileSource.setDirectory(this.localDirectory.getFile());
this.fileSource.afterPropertiesSet();
this.synchronizer.afterPropertiesSet();
}
public Message<File> receive() {
return this.fileSource.receive();
}
}

View File

@@ -1,73 +0,0 @@
/*
* Copyright 2002-2010 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.Queue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* {@link FileListFilter} that passes files only one time. This can
* conveniently be used to prevent duplication of files, as is done in
* {@link FileReadingMessageSource}.
* <p/>
* This implementation is thread safe.
*
* @author Iwein Fuld
* @since 1.0.0
*/
public class AcceptOnceFileListFilter extends AbstractFileListFilter {
private final Queue<File> seen;
private final Object monitor = new Object();
/**
* Creates an AcceptOnceFileFilter that is based on a bounded queue. If the
* queue overflows, files that fall out will be passed through this filter
* again if passed to the {@link #filterFiles(File[])} method.
*
* @param maxCapacity the maximum number of Files to maintain in the 'seen'
* queue.
*/
public AcceptOnceFileListFilter(int maxCapacity) {
this.seen = new LinkedBlockingQueue<File>(maxCapacity);
}
/**
* Creates an AcceptOnceFileFilter based on an unbounded queue.
*/
public AcceptOnceFileListFilter() {
this.seen = new LinkedBlockingQueue<File>();
}
protected boolean accept(File pathname) {
synchronized (this.monitor) {
if (seen.contains(pathname)) {
return false;
}
if (!seen.offer(pathname)) {
seen.poll();
seen.add(pathname);
}
return true;
}
}
}

View File

@@ -13,14 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.file;
import org.springframework.integration.MessagingException;
import org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter;
import org.springframework.integration.file.entries.EntryListFilter;
import java.io.File;
import java.util.List;
/**
* Default directory scanner and base class for other directory scanners. It takes care of the default interrelations
* between filtering, scanning and locking.
@@ -29,16 +32,17 @@ import java.util.List;
* @since 2.0
*/
public class DefaultDirectoryScanner implements DirectoryScanner {
private FileListFilter filter = new AcceptOnceFileListFilter();
private EntryListFilter<File> filter = new AcceptOnceEntryFileListFilter<File>();
private FileLocker locker;
public final List<File> listFiles(File directory) throws IllegalArgumentException {
File[] files = listEligibleFiles(directory);
if (files == null) {
throw new MessagingException("The path [" + directory
+ "] does not denote a properly accessible directory.");
throw new MessagingException("The path [" + directory + "] does not denote a properly accessible directory.");
}
return this.filter.filterFiles(files);
return this.filter.filterEntries(files);
}
/**
@@ -52,10 +56,7 @@ public class DefaultDirectoryScanner implements DirectoryScanner {
return directory.listFiles();
}
/**
* {@inheritDoc}
*/
public final void setFilter(FileListFilter filter) {
public void setFilter(EntryListFilter<File> filter) {
this.filter = filter;
}
@@ -65,7 +66,7 @@ public class DefaultDirectoryScanner implements DirectoryScanner {
* This class takes the minimal implementation and merely delegates to the locker if set.
*/
public final boolean tryClaim(File file) {
return locker != null ? locker.lock(file) : true;
return (locker == null) || locker.lock(file);
}
/**

View File

@@ -16,13 +16,15 @@
package org.springframework.integration.file;
import org.springframework.integration.file.entries.EntryListFilter;
import java.io.File;
import java.util.List;
/**
* Strategy for scanning directories. Implementations may select all children and grandchildren of the scanned directory
* in any order. This interface is intended to enable the customization of selection, locking and ordering of files in a
* directory like RecursiveDirectoryScanner. If the only requirement is to ignore certain files a FileListFilter
* directory like RecursiveDirectoryScanner. If the only requirement is to ignore certain files a EntryListFilter
* implementation should suffice.
*
*
@@ -37,6 +39,7 @@ public interface DirectoryScanner {
*
* @param directory the directory to scan for files
* @return a list of files representing the content of the directory
* @throws IllegalArgumentException thrown if the input is incorrect
*/
List<File> listFiles(File directory) throws IllegalArgumentException;
@@ -47,7 +50,7 @@ public interface DirectoryScanner {
*
* @param filter the custom filter to be used
*/
void setFilter(FileListFilter filter);
void setFilter(EntryListFilter<File> filter);
/**

View File

@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.file;
import org.apache.commons.logging.Log;
@@ -24,22 +23,25 @@ import org.springframework.integration.MessagingException;
import org.springframework.integration.aggregator.ResequencingMessageGroupProcessor;
import org.springframework.integration.core.MessageBuilder;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.file.entries.EntryListFilter;
import org.springframework.util.Assert;
import java.io.File;
import java.util.*;
import java.util.concurrent.PriorityBlockingQueue;
/**
* {@link MessageSource} that creates messages from a file system directory. To prevent messages for certain files, you
* may supply a {@link FileListFilter}. By default, an {@link AcceptOnceFileListFilter} is used. It ensures files are
* may supply a {@link org.springframework.integration.file.entries.EntryListFilter}. By default,
* an {@link org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter} is used. It ensures files are
* picked up only once from the directory.
* <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
* org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter} 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 org.springframework.integration.file.CompositeFileListFilter} for a way to do this.
* ready (e.g. based on a known suffix), composed with the default {@link org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter} would allow for
* this. See {@link org.springframework.integration.file.entries.CompositeEntryListFilter} for a way to do this.
* <p/>
* A {@link Comparator} can be used to ensure internal ordering of the Files in a {@link PriorityBlockingQueue}. This
* does not provide the same guarantees as a {@link ResequencingMessageGroupProcessor}, but in cases where writing files and failure
@@ -51,18 +53,11 @@ import java.util.concurrent.PriorityBlockingQueue;
* @author Iwein Fuld
* @author Mark Fisher
*/
public class FileReadingMessageSource implements MessageSource<File>,
InitializingBean {
public class FileReadingMessageSource implements MessageSource<File>, InitializingBean {
private static final int DEFAULT_INTERNAL_QUEUE_CAPACITY = 5;
private static final Log logger = LogFactory
.getLog(FileReadingMessageSource.class);
private static final Log logger = LogFactory.getLog(FileReadingMessageSource.class);
private volatile File directory;
private volatile DirectoryScanner scanner = new DefaultDirectoryScanner();
private volatile boolean autoCreateDirectory = true;
/*
@@ -70,7 +65,6 @@ public class FileReadingMessageSource implements MessageSource<File>,
* There is no locking around the queue, so there is also no iteration.
*/
private final Queue<File> toBeReceived;
private boolean scanEachPoll = false;
/**
@@ -92,7 +86,7 @@ public class FileReadingMessageSource implements MessageSource<File>,
*/
public FileReadingMessageSource(int internalQueueCapacity) {
this(null);
Assert.isTrue(internalQueueCapacity>0, "Cannot create a queue with non positive capacity");
Assert.isTrue(internalQueueCapacity > 0, "Cannot create a queue with non positive capacity");
this.setScanner(new HeadDirectoryScanner(internalQueueCapacity));
}
@@ -108,12 +102,13 @@ public class FileReadingMessageSource implements MessageSource<File>,
* @param receptionOrderComparator the comparator to be used to order the files in the internal queue
*/
public FileReadingMessageSource(Comparator<File> receptionOrderComparator) {
toBeReceived = new PriorityBlockingQueue<File>(DEFAULT_INTERNAL_QUEUE_CAPACITY,
receptionOrderComparator);
toBeReceived = new PriorityBlockingQueue<File>(DEFAULT_INTERNAL_QUEUE_CAPACITY, receptionOrderComparator);
}
/**
* Specify the input directory.
*
* @param directory to monitor
*/
public void setDirectory(File directory) {
Assert.notNull(directory, "directory must not be null");
@@ -122,6 +117,8 @@ public class FileReadingMessageSource implements MessageSource<File>,
/**
* Optionally specify a custom scanner, for example the {@link org.springframework.integration.file.RecursiveLeafOnlyDirectoryScanner}
*
* @param scanner scanner impl
*/
public void setScanner(DirectoryScanner scanner) {
this.scanner = scanner;
@@ -131,19 +128,23 @@ public class FileReadingMessageSource implements MessageSource<File>,
* Specify whether to create the source directory automatically if it does not yet exist upon initialization. By
* default, this value is <emphasis>true</emphasis>. If set to <emphasis>false</emphasis> and the source directory
* does not exist, an Exception will be thrown upon initialization.
*
* @param autoCreateDirectory should the directory to be monitored be created when this component starts up?
*/
public void setAutoCreateDirectory(boolean autoCreateDirectory) {
this.autoCreateDirectory = autoCreateDirectory;
}
/**
* Sets a {@link FileListFilter}. By default a {@link AcceptOnceFileListFilter} with no bounds is used. In most
* cases a customized {@link FileListFilter} will be needed to deal with modification and duplication concerns. If
* multiple filters are required a {@link CompositeFileListFilter} can be used to group them together.
* Sets a {@link org.springframework.integration.file.entries.EntryListFilter}. By default a {@link org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter} with no bounds is used. In most
* cases a customized {@link org.springframework.integration.file.entries.EntryListFilter} will be needed to deal with modification and duplication concerns. If
* multiple filters are required a {@link org.springframework.integration.file.entries.CompositeEntryListFilter} can be used to group them together.
* <p/>
* <b>The supplied filter must be thread safe.</b>.
*
* @param filter a filter
*/
public void setFilter(FileListFilter filter) {
public void setFilter(EntryListFilter<File> filter) {
Assert.notNull(filter, "'filter' must not be null");
this.scanner.setFilter(filter);
}
@@ -153,6 +154,8 @@ public class FileReadingMessageSource implements MessageSource<File>,
* against duplicate processing.
* <p/>
* <b>The supplied FileLocker must be thread safe</b>
*
* @param locker a locker
*/
public void setLocker(FileLocker locker) {
Assert.notNull(locker, "'fileLocker' must not be null.");
@@ -168,50 +171,60 @@ public class FileReadingMessageSource implements MessageSource<File>,
* java.util.concurrent.BlockingQueue} that this class is keeping will more likely be out of sync with the file
* system if this flag is set to <code>false</code>, but it will change more often (causing expensive reordering) if
* it is set to <code>true</code>.
*
* @param scanEachPoll whether or not the component should re-scan (as opposed to not rescanning until the entire backlog has been delivered)
*/
public void setScanEachPoll(boolean scanEachPoll) {
this.scanEachPoll = scanEachPoll;
}
@SuppressWarnings({"ResultOfMethodCallIgnored"})
public final void afterPropertiesSet() {
Assert.notNull(directory, "'directory' must not be set before initialization");
if (!this.directory.exists() && this.autoCreateDirectory) {
this.directory.mkdirs();
}
Assert.isTrue(this.directory.exists(), "Source directory ["
+ directory + "] does not exist.");
Assert.isTrue(this.directory.isDirectory(), "Source path ["
+ this.directory + "] does not point to a directory.");
Assert.isTrue(this.directory.canRead(), "Source directory ["
+ this.directory + "] is not readable.");
Assert.isTrue(this.directory.exists(), "Source directory [" + directory + "] does not exist.");
Assert.isTrue(this.directory.isDirectory(), "Source path [" + this.directory + "] does not point to a directory.");
Assert.isTrue(this.directory.canRead(), "Source directory [" + this.directory + "] is not readable.");
}
public Message<File> receive() throws MessagingException {
Message<File> message = null;
// rescan only if needed or explicitly configured
if (scanEachPoll || toBeReceived.isEmpty()) {
scanInputDirectory();
}
File file = toBeReceived.poll();
// file == null means the queue was empty
// we can't rely on isEmpty for concurrency reasons
while (file != null && !scanner.tryClaim(file)) {
while ((file != null) && !scanner.tryClaim(file)) {
file = toBeReceived.poll();
}
if (file != null) {
message = MessageBuilder.withPayload(file).build();
if (logger.isInfoEnabled()) {
logger.info("Created message: [" + message + "]");
}
}
return message;
}
private void scanInputDirectory() {
List<File> filteredFiles = scanner.listFiles(directory);
Set<File> freshFiles = new HashSet<File>(filteredFiles);
if (!freshFiles.isEmpty()) {
toBeReceived.addAll(freshFiles);
if (logger.isDebugEnabled()) {
logger.debug("Added to queue: " + freshFiles);
}
@@ -220,16 +233,21 @@ public class FileReadingMessageSource implements MessageSource<File>,
/**
* Adds the failed message back to the 'toBeReceived' queue if there is room.
*
* @param failedMessage the {@link org.springframework.integration.Message} that blew up
*/
public void onFailure(Message<File> failedMessage, Throwable t) {
public void onFailure(Message<File> failedMessage) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to send: " + failedMessage);
}
toBeReceived.offer(failedMessage.getPayload());
}
/**
* The message is just logged. It was already removed from the queue during the call to <code>receive()</code>
*
* @param sentMessage the message that was successfully delivered
*/
public void onSend(Message<File> sentMessage) {
if (logger.isDebugEnabled()) {

View File

@@ -13,13 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.file;
import org.springframework.integration.file.entries.EntryListFilter;
import java.io.File;
import java.util.Arrays;
import java.util.List;
/**
* A custom scanner that only returns the first <code>maxNumberOfFiles</code> elements from a directory listing. This is
* useful to limit the number of File objects in memory and therefore mutually exclusive with AcceptOnceFileListFilter.
@@ -28,19 +31,18 @@ import java.util.List;
* @since 2.0.0
*/
public class HeadDirectoryScanner extends DefaultDirectoryScanner {
public HeadDirectoryScanner(int maxNumberOfFiles) {
this.setFilter(new HeadFilter(maxNumberOfFiles));
}
private class HeadFilter implements FileListFilter {
private class HeadFilter implements EntryListFilter<File> {
private final int maxNumberOfFiles;
public HeadFilter(int maxNumberOfFiles) {
this.maxNumberOfFiles = maxNumberOfFiles;
}
public List<File> filterFiles(File[] files) {
public List<File> filterEntries(File[] files) {
return Arrays.asList(files).subList(0, Math.min(files.length, maxNumberOfFiles));
}
}

View File

@@ -13,110 +13,115 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.file.config;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.integration.file.entries.*;
import java.io.File;
import java.util.Collection;
import java.util.regex.Pattern;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.integration.file.AbstractFileListFilter;
import org.springframework.integration.file.AcceptOnceFileListFilter;
import org.springframework.integration.file.CompositeFileListFilter;
import org.springframework.integration.file.FileListFilter;
import org.springframework.integration.file.PatternMatchingFileListFilter;
/**
* @author Mark Fisher
* @since 1.0.3
*/
public class FileListFilterFactoryBean implements FactoryBean<FileListFilter> {
public class FileListFilterFactoryBean implements FactoryBean<EntryListFilter<File>> {
private volatile EntryListFilter<File> fileListFilter;
private volatile EntryListFilter<File> filterReference;
private volatile Pattern filenamePattern;
private volatile Boolean preventDuplicates;
private final Object monitor = new Object();
private volatile Collection<EntryListFilter<File>> filterReferences;
private FileEntryNamer fileNamer = new FileEntryNamer();
private volatile FileListFilter fileListFilter;
public void setFilterReferences(Collection<EntryListFilter<File>> filterReferences) {
this.filterReferences = filterReferences;
}
private volatile FileListFilter filterReference;
public void setFilterReference(EntryListFilter<File> filterReference) {
this.filterReference = filterReference;
}
private volatile Pattern filenamePattern;
public void setFilenamePattern(Pattern filenamePattern) {
this.filenamePattern = filenamePattern;
}
private volatile Boolean preventDuplicates;
public void setPreventDuplicates(Boolean preventDuplicates) {
this.preventDuplicates = preventDuplicates;
}
private final Object monitor = new Object();
public EntryListFilter<File> getObject() throws Exception {
if (this.fileListFilter == null) {
synchronized (this.monitor) {
this.intializeFileListFilter();
}
}
return this.fileListFilter;
}
public void setFilterReference(FileListFilter filterReference) {
this.filterReference = filterReference;
}
public Class<?> getObjectType() {
return (this.fileListFilter != null) ? this.fileListFilter.getClass() : EntryListFilter.class;
}
public void setFilenamePattern(Pattern filenamePattern) {
this.filenamePattern = filenamePattern;
}
public boolean isSingleton() {
return true;
}
public void setPreventDuplicates(Boolean preventDuplicates) {
this.preventDuplicates = preventDuplicates;
}
private void intializeFileListFilter() {
if (this.fileListFilter != null) {
return;
}
public FileListFilter getObject() throws Exception {
if (this.fileListFilter == null) {
synchronized (this.monitor) {
this.intializeFileListFilter();
}
}
return this.fileListFilter;
}
EntryListFilter<File> flf=null;
public Class<?> getObjectType() {
return (this.fileListFilter != null)
? this.fileListFilter.getClass() : FileListFilter.class;
}
if ((this.filterReference != null) && (this.filenamePattern != null)) {
throw new IllegalArgumentException("The 'filter' reference and " + "'filename-pattern' attributes are mutually exclusive.");
}
public boolean isSingleton() {
return true;
}
if (this.filterReference != null) {
if (Boolean.TRUE.equals(this.preventDuplicates)) {
flf = this.createCompositeWithAcceptOnceFilter(this.filterReference);
} else { // preventDuplicates is either FALSE or NULL
flf = this.filterReference;
}
} else if (this.filenamePattern != null) {
PatternMatchingEntryListFilter<File> patternFilter = new PatternMatchingEntryListFilter<File>(fileNamer, this.filenamePattern);
private void intializeFileListFilter() {
if (this.fileListFilter != null) {
return;
}
FileListFilter flf = null;
if (this.filterReference != null && this.filenamePattern != null) {
throw new IllegalArgumentException("The 'filter' reference and " +
"'filename-pattern' attributes are mutually exclusive.");
}
if (this.filterReference != null) {
if (Boolean.TRUE.equals(this.preventDuplicates)) {
flf = this.createCompositeWithAcceptOnceFilter(this.filterReference);
}
else { // preventDuplicates is either FALSE or NULL
flf = this.filterReference;
}
}
else if (this.filenamePattern != null) {
PatternMatchingFileListFilter patternFilter = new PatternMatchingFileListFilter(this.filenamePattern);
if (Boolean.FALSE.equals(this.preventDuplicates)) {
flf = patternFilter;
}
else { // preventDuplicates is either TRUE or NULL
flf = this.createCompositeWithAcceptOnceFilter(patternFilter);
}
}
else if (Boolean.FALSE.equals(this.preventDuplicates)) {
flf = new AbstractFileListFilter() {
@Override
protected boolean accept(File file) {
return true;
}
};
}
else { // preventDuplicates is either TRUE or NULL
flf = new AcceptOnceFileListFilter();
}
this.fileListFilter = flf;
}
if (Boolean.FALSE.equals(this.preventDuplicates)) {
flf = patternFilter;
} else { // preventDuplicates is either TRUE or NULL
flf = this.createCompositeWithAcceptOnceFilter(patternFilter);
}
} else if (Boolean.FALSE.equals(this.preventDuplicates)) {
flf = new AcceptAllEntryListFilter<File>();
} else { // preventDuplicates is either TRUE or NULL
flf = new AcceptOnceEntryFileListFilter<File>();
}
private FileListFilter createCompositeWithAcceptOnceFilter(FileListFilter otherFilter) {
CompositeFileListFilter compositeFilter = new CompositeFileListFilter();
compositeFilter.addFilter(new AcceptOnceFileListFilter(), otherFilter);
return compositeFilter;
}
// finally, it might be that they simply want a {@link CompositeEntryListFilter}
if ((this.filterReferences != null) && (this.filterReferences.size() > 0) ) {
CompositeEntryListFilter<File> flfc = new CompositeEntryListFilter<File>();
for (EntryListFilter<File> ff : filterReferences)
flfc.addFilter(ff);
flf = flfc;
}
if( flf== null)flf =new CompositeEntryListFilter<File>();
this.fileListFilter = flf;
}
private CompositeEntryListFilter<File> createCompositeWithAcceptOnceFilter(EntryListFilter<File> otherFilter) {
CompositeEntryListFilter<File> compositeFilter = new CompositeEntryListFilter<File>();
compositeFilter.addFilter(new AcceptOnceEntryFileListFilter<File>() );
compositeFilter.addFilter(otherFilter);
return compositeFilter;
}
}

View File

@@ -19,10 +19,10 @@ package org.springframework.integration.file.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.integration.file.CompositeFileListFilter;
import org.springframework.integration.file.DirectoryScanner;
import org.springframework.integration.file.FileListFilter;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.entries.CompositeEntryListFilter;
import org.springframework.integration.file.entries.EntryListFilter;
import org.springframework.integration.file.locking.AbstractFileLockerFilter;
import java.io.File;
@@ -41,7 +41,7 @@ public class FileReadingMessageSourceFactoryBean implements FactoryBean<FileRead
private volatile File directory;
private volatile FileListFilter filter;
private volatile EntryListFilter<File> filter;
private volatile AbstractFileLockerFilter locker;
@@ -69,7 +69,7 @@ public class FileReadingMessageSourceFactoryBean implements FactoryBean<FileRead
this.scanner = scanner;
}
public void setFilter(FileListFilter filter) {
public void setFilter(EntryListFilter<File> filter) {
if (filter instanceof AbstractFileLockerFilter && this.locker == null) {
this.setLocker((AbstractFileLockerFilter) filter);
}
@@ -133,7 +133,7 @@ public class FileReadingMessageSourceFactoryBean implements FactoryBean<FileRead
if (this.locker == null) {
this.source.setFilter(this.filter);
} else {
this.source.setFilter(new CompositeFileListFilter(this.filter, this.locker));
this.source.setFilter(new CompositeEntryListFilter<File>(this.filter, this.locker));
this.source.setLocker(locker);
}
}

View File

@@ -13,16 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.file.entries;
import org.springframework.beans.factory.InitializingBean;
import java.util.ArrayList;
import java.util.List;
public abstract class AbstractEntryListFilter<T> implements EntryListFilter<T> {
protected abstract boolean accept(T t);
/**
* A convenience base class for any {@link EntryListFilter} whose criteria can be
* evaluated against each File in isolation. If the entire List of files is
* required for evaluation, implement the {@link EntryListFilter} interface directly.
*
* @author Mark Fisher
* @author Iwein Fuld
* @author Josh Long
*/
public abstract class AbstractEntryListFilter<T> implements InitializingBean, EntryListFilter<T> {
public abstract boolean accept(T t);
public List<T> filterEntries(T[] entries) {
List<T> accepted = new ArrayList<T>();
@@ -37,4 +47,8 @@ public abstract class AbstractEntryListFilter<T> implements EntryListFilter<T> {
return accepted;
}
public void afterPropertiesSet() throws Exception {
// its all you!
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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.entries;
/**
* Simple NOOP implementation for {@link org.springframework.integration.file.entries.EntryListFilter} implementation.
* Suitable as a default in implementations.
*
* @author Josh Long
* @param <T>
*/
public class AcceptAllEntryListFilter<T> extends AbstractEntryListFilter<T> {
@Override
public boolean accept(T t) {
return true;
}
}

View File

@@ -13,20 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.file.entries;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* {@link EntryListFilter} that passes files only one time. This can
* conveniently be used to prevent duplication of files, as is done in
* {@link org.springframework.integration.file.FileReadingMessageSource}.
* <p/>
* This implementation is thread safe.
*
* @author Iwein Fuld
* @since 1.0.0
*/
public class AcceptOnceEntryFileListFilter<T> extends AbstractEntryListFilter<T> {
private final Queue<T> seen;
private final Object monitor = new Object();
/**
* Creates an AcceptOnceFileFilter that is based on a bounded queue. If the
* Creates an {@link org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter} that is based on a bounded queue. If the
* queue overflows, files that fall out will be passed through this filter
* again if passed to the {@link #filterEntries(Object[])} method.
*
@@ -44,7 +53,7 @@ public class AcceptOnceEntryFileListFilter<T> extends AbstractEntryListFilter<T>
this.seen = new LinkedBlockingQueue<T>();
}
protected boolean accept(T pathname) {
public boolean accept(T pathname) {
synchronized (this.monitor) {
if (seen.contains(pathname)) {
return false;

View File

@@ -13,41 +13,51 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.file.entries;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import java.util.*;
public class CompositeEntryListFilter<T> implements EntryListFilter<T> {
private final Set<EntryListFilter> fileFilters;
private final Set<EntryListFilter<T>> fileFilters;
public CompositeEntryListFilter(EntryListFilter... fileFilters) {
this.fileFilters = new LinkedHashSet<EntryListFilter>(Arrays.asList(fileFilters));
public CompositeEntryListFilter(EntryListFilter<T>... fileFilters) {
this.fileFilters = new LinkedHashSet<EntryListFilter<T>>(Arrays.asList(fileFilters));
}
public CompositeEntryListFilter(Collection<EntryListFilter> fileFilters) {
this.fileFilters = new LinkedHashSet<EntryListFilter>(fileFilters);
public CompositeEntryListFilter(Collection<?extends EntryListFilter<T>> fileFilters) {
this.fileFilters = new LinkedHashSet<EntryListFilter<T>>(fileFilters);
}
@SuppressWarnings("unchecked")
public List<T> filterEntries(T[] entries) {
Assert.notNull(entries, "'files' should not be null");
List<T> leftOver = Arrays.asList(entries);
for (EntryListFilter fileFilter : this.fileFilters) {
T[] ts =(T[]) leftOver.toArray();
List<T> leftOver = Arrays.asList(entries);
for (EntryListFilter<T> fileFilter : this.fileFilters) {
T[] ts = (T[]) leftOver.toArray();
leftOver = fileFilter.filterEntries(ts);
}
return leftOver;
}
public CompositeEntryListFilter<T> addFilter(EntryListFilter<T> filter) {
return this.addFilters(Arrays.asList(filter));
}
/**
* @param filters one or more new filters to add
* @return this CompositeFileFilter instance with the added filters
* @see #addFilters(Collection)
*/
public CompositeEntryListFilter addFilter(EntryListFilter... filters) {
@SuppressWarnings("unused")
public CompositeEntryListFilter<T> addFilters(EntryListFilter<T>[] filters) {
return addFilters(Arrays.asList(filters));
}
@@ -59,7 +69,16 @@ public class CompositeEntryListFilter<T> implements EntryListFilter<T> {
* @param filtersToAdd a list of filters to add
* @return this CompositeEntryListFilter instance with the added filters
*/
public CompositeEntryListFilter addFilters(Collection<EntryListFilter> filtersToAdd) {
public CompositeEntryListFilter<T> addFilters(Collection<EntryListFilter<T>> filtersToAdd) {
for (EntryListFilter<T> elf : filtersToAdd)
if (elf instanceof InitializingBean) {
try {
((InitializingBean) elf).afterPropertiesSet();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
this.fileFilters.addAll(filtersToAdd);
return this;

View File

@@ -18,6 +18,19 @@ package org.springframework.integration.file.entries;
import java.util.List;
public interface EntryListFilter <T> {
List<T> filterEntries(T [] entries );
/**
* Strategy interface for filtering a group of entries / files.
* <p/>
* {@link EntryListFilter} that passes file entries only one time. This can
* conveniently be used to prevent duplication of files, as is done in
* {@link org.springframework.integration.file.FileReadingMessageSource}.
* <p/>
* This implementation is thread safe.
*
* @author Iwein Fuld
* @author Josh Long
* @since 1.0.0
*/
public interface EntryListFilter<T> {
List<T> filterEntries(T[] entries);
}

View File

@@ -15,6 +15,21 @@
*/
package org.springframework.integration.file.entries;
/**
* Responsible for coercing a String identification out of the {@link T} entry.
* @param <T> the type of entry (there's an implementation for FTP, SFTP, and plain-old java.io.Files)
*
* @author Josh Long
*/
public interface EntryNamer<T> {
/**
* This is the one place I couldn't spackle over the interface differences between an FTPFile (FTP adapter), File (File adapter), and LsEntry (SFTP adapter)
* with generics alone. So we have a typed strategy implementation for accessing a property ....
*
*
* @param entry the entry in a file system listing
* @return the String name that might be used to reference that entry or to do regular expression checks against
*/
String nameOf(T entry);
}

View File

@@ -0,0 +1,34 @@
/*
* 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.entries;
import java.io.File;
/**
* {@link java.io.File} implementation of the {@link org.springframework.integration.file.entries.EntryNamer} strategy.
*
* This part feels a little over-engineered...
*
* @author Josh Long
*
*/
public class FileEntryNamer implements EntryNamer<File> {
public String nameOf(File entry) {
return (entry != null) ? entry.getName() : null;
}
}

View File

@@ -24,16 +24,29 @@ import java.util.regex.Pattern;
/**
* experimental
* <emphasis>experimental</emphasis>
* <p/>
* Filters a listing of entries (T) by qualifying their 'name' (as determined by {@link org.springframework.integration.file.entries.EntryNamer})
* against a regular expression (an instance of {@link java.util.regex.Pattern})
*
* @author Josh Long
* @param <T> the type of entry
*/
public abstract class PatternMatchingEntryListFilter<T> extends AbstractEntryListFilter<T> implements InitializingBean {
public class PatternMatchingEntryListFilter<T> extends AbstractEntryListFilter<T> implements InitializingBean {
private Pattern pattern;
private String patternExpression;
private EntryNamer<T> entryNamer;
public PatternMatchingEntryListFilter(EntryNamer<T> en, String p) {
this.entryNamer = en;
this.patternExpression = p;
}
public PatternMatchingEntryListFilter(EntryNamer<T> en, Pattern p) {
this.entryNamer = en;
this.pattern = p;
}
public void setPattern(Pattern pattern) {
this.pattern = pattern;
}
@@ -46,13 +59,12 @@ public abstract class PatternMatchingEntryListFilter<T> extends AbstractEntryLis
if (StringUtils.hasText(this.patternExpression) && (pattern == null)) {
this.pattern = Pattern.compile(this.patternExpression);
}
Assert.notNull(this.entryNamer,"'entryNamer' must not be null!");
Assert.notNull(this.entryNamer, "'entryNamer' must not be null!");
Assert.notNull(this.pattern, "'pattern' mustn't be null!");
}
@Override
protected boolean accept(T t) {
public boolean accept(T t) {
return (t != null) && this.pattern.matcher(this.entryNamer.nameOf(t)).matches();
}

View File

@@ -0,0 +1,46 @@
/*
* 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.entries;
import org.springframework.util.Assert;
/**
* this simply takes an {@link org.springframework.integration.file.entries.EntryListFilter}
* and produces an object that can field just <b>one</b> argument instea of an array
*
* @author Josh Long
*/
public class SingleEntryAdaptingEntryListFilter<T> extends AbstractEntryListFilter<T> {
/**
* the {@link org.springframework.integration.file.entries.EntryListFilter} that you'd like to delegate to
*/
private volatile EntryListFilter<T> entryFilter;
public SingleEntryAdaptingEntryListFilter(EntryListFilter<T> ef) {
this.entryFilter = ef;
Assert.notNull(this.entryFilter, "the entryFilter can't be null");
}
@Override
@SuppressWarnings("unchecked")
public boolean accept(T t) {
T[] ts = (T[]) new Object[] { t };
return this.entryFilter.filterEntries(ts).size() == 1;
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.integration.file;
package org.springframework.integration.file.filters;
import java.io.File;
import java.util.ArrayList;
@@ -24,30 +24,31 @@ 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
* @author Iwein Fuld
*/
@Deprecated
public abstract class AbstractFileListFilter implements FileListFilter {
/**
* {@inheritDoc}
*/
public final List<File> filterFiles(File[] files) {
List<File> accepted = new ArrayList<File>();
if (files != null) {
for (File file : files) {
if (this.accept(file)) {
accepted.add(file);
}
}
}
return accepted;
}
/**
* {@inheritDoc}
*/
public final List<File> filterFiles(File[] files) {
List<File> accepted = new ArrayList<File>();
if (files != null) {
for (File file : files) {
if (this.accept(file)) {
accepted.add(file);
}
}
}
return accepted;
}
/**
* Subclasses must implement this method.
*/
protected abstract boolean accept(File file);
/**
* Subclasses must implement this method.
*/
protected abstract boolean accept(File file);
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2002-2010 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 java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* {@link FileListFilter} that passes files only one time. This can
* conveniently be used to prevent duplication of files, as is done in
* {@link org.springframework.integration.file.FileReadingMessageSource}.
* <p/>
* This implementation is thread safe.
*
* @author Iwein Fuld
* @since 1.0.0
*/
@Deprecated
public class AcceptOnceFileListFilter extends AbstractFileListFilter {
private final Queue<File> seen;
private final Object monitor = new Object();
/**
* Creates an AcceptOnceFileFilter that is based on a bounded queue. If the
* queue overflows, files that fall out will be passed through this filter
* again if passed to the {@link #filterFiles(File[])} method.
*
* @param maxCapacity the maximum number of Files to maintain in the 'seen'
* queue.
*/
public AcceptOnceFileListFilter(int maxCapacity) {
this.seen = new LinkedBlockingQueue<File>(maxCapacity);
}
/**
* Creates an AcceptOnceFileFilter based on an unbounded queue.
*/
public AcceptOnceFileListFilter() {
this.seen = new LinkedBlockingQueue<File>();
}
protected boolean accept(File pathname) {
synchronized (this.monitor) {
if (seen.contains(pathname)) {
return false;
}
if (!seen.offer(pathname)) {
seen.poll();
seen.add(pathname);
}
return true;
}
}
}

View File

@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.file;
package org.springframework.integration.file.filters;
import org.springframework.util.Assert;
@@ -22,6 +21,7 @@ import java.io.File;
import java.io.FileFilter;
import java.util.*;
/**
* Composition that delegates to multiple {@link FileFilter}s. The composition is AND based, meaning that a file must
* pass through each filter's {@link #filterFiles(java.io.File[])} method in order to be accepted by the composite.
@@ -29,11 +29,10 @@ import java.util.*;
* @author Iwein Fuld
* @author Mark Fisher
*/
@Deprecated
public class CompositeFileListFilter implements FileListFilter {
private final Set<FileListFilter> fileFilters;
public CompositeFileListFilter(FileListFilter... fileFilters) {
this.fileFilters = new LinkedHashSet<FileListFilter>(Arrays.asList(fileFilters));
}
@@ -42,7 +41,6 @@ public class CompositeFileListFilter implements FileListFilter {
this.fileFilters = new LinkedHashSet<FileListFilter>(fileFilters);
}
/**
* {@inheritDoc}
* <p/>
@@ -50,33 +48,35 @@ public class CompositeFileListFilter implements FileListFilter {
*/
public List<File> filterFiles(File[] files) {
Assert.notNull(files, "'files' should not be null");
List<File> leftOver = Arrays.asList(files);
for (FileListFilter fileFilter : this.fileFilters) {
leftOver = fileFilter.filterFiles(leftOver.toArray(new File[]{}));
}
return leftOver;
}
/**
* @param filters one or more new filters to add
* @return this CompositeFileFilter instance with the added filters
* @see #addFilters(Collection)
*/
public CompositeFileListFilter addFilter(FileListFilter... filters) {
/* public CompositeFileListFilter addFilter(FileListFilter... filters) {
return addFilters(Arrays.asList(filters));
}
}*/
/**
* Not thread safe. Only a single thread may add filters at a time.
*
* <p/>
* Add the new filters to this CompositeFileFilter while maintaining the existing filters.
*
* @param filtersToAdd a list of filters to add
* @return this CompositeFileFilter instance with the added filters
*/
public CompositeFileListFilter addFilters(Collection<FileListFilter> filtersToAdd) {
/* public CompositeFileListFilter addFilters(Collection<FileListFilter> filtersToAdd) {
this.fileFilters.addAll(filtersToAdd);
return this;
}
return this;
}*/
}

View File

@@ -14,22 +14,23 @@
* limitations under the License.
*/
package org.springframework.integration.file;
package org.springframework.integration.file.filters;
import java.io.File;
import java.util.List;
/**
* Strategy interface for filtering a group of files.
*
*
* @author Iwein Fuld
*/
@Deprecated
public interface FileListFilter {
/**
* Filters out files and returns the files that are left in a list, or an
* empty list when a null is passed in.
*/
List<File> filterFiles(File[] files);
/**
* Filters out files and returns the files that are left in a list, or an
* empty list when a null is passed in.
*/
List<File> filterFiles(File[] files);
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.integration.file;
package org.springframework.integration.file.filters;
import java.io.File;
import java.util.regex.Pattern;
@@ -26,6 +26,7 @@ import org.springframework.util.Assert;
*
* @author Mark Fisher
*/
@Deprecated
public class PatternMatchingFileListFilter extends AbstractFileListFilter {
private final Pattern pattern;

View File

@@ -16,8 +16,8 @@
package org.springframework.integration.file.locking;
import org.springframework.integration.file.AbstractFileListFilter;
import org.springframework.integration.file.FileLocker;
import org.springframework.integration.file.entries.AbstractEntryListFilter;
import java.io.File;
@@ -29,9 +29,10 @@ import java.io.File;
* @since 2.0
*
*/
public abstract class AbstractFileLockerFilter extends AbstractFileListFilter implements FileLocker {
public abstract class AbstractFileLockerFilter extends AbstractEntryListFilter<File> implements FileLocker {
protected final boolean accept(File file) {
return isLockable(file);
@Override
public boolean accept(File file) {
return this.isLockable(file);
}
}
}

View File

@@ -0,0 +1,261 @@
package org.springframework.integration.file.monitors;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.file.entries.*;
import org.springframework.util.Assert;
import java.io.File;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
/**
* This component will support event-based (not poller based) notifications of files from a file system.
* Immediate implementations will center around supporting other adapters's delivery of files once they've been synced from a remote system.
* Potential future expansions for consumer consumption might be a push / event-based file adapter based on either native code
* or Java 7's NIO.2 WaterService or other third party implementations.
* <p/>
* In the meantime, this provides us with a base class for building event driven file adapters quickly. The two cases I see are:
* <p/>
* <ol>
* <li></li>
* <li></li>
* </ol>
*
* @author Josh Long
*/
public abstract class AbstractEventDrivenFileMonitor extends IntegrationObjectSupport implements EventDrivenDirectoryMonitor {
/**
* when this component starts up, we can perform a scan of the folder this first time and to pre-seed the #additions queue
*/
private boolean scanDirectoryOnLoad;
/**
* How many files we'll support in the backlog at a time
*/
private volatile int maxQueueSize = 100;
/**
* the backlog
*/
private volatile LinkedBlockingQueue<File> additions;
/**
* An {@link java.util.concurrent.Executor} implementation. Default is {@link org.springframework.core.task.SimpleAsyncTaskExecutor}
*/
private volatile Executor executor;
/**
* Should the director be automatically created?
*/
private volatile boolean autoCreateDirectory;
/**
* The directory to monitor (a {@link java.io.File})
*/
private volatile File directoryToMonitorCached;
/**
* A {@link org.springframework.integration.file.entries.EntryListFilter} reference
*/
private volatile SingleEntryAdaptingEntryListFilter<File> filter;
/**
* state guard (extra for post-init state)
*/
private final Object guard = new Object();
public void setScanDirectoryOnLoad(boolean scanDirectoryOnLoad) {
this.scanDirectoryOnLoad = scanDirectoryOnLoad;
}
/**
* installs directory, and then kicks of an event pump
*
* @param directory the directory to start watching from. Unspecified if this implies recursion or not.
* @param fileAdditionListener the callback
* @throws Exception
*/
public void monitor(File directory, FileAdditionListener fileAdditionListener)
throws Exception {
this.installDirectoryIfRequired(directory);
this.prescan();
this.executor.execute(new FileDeliveryPump(fileAdditionListener));
}
public void setMaxQueueSize(int maxQueueSize) {
this.maxQueueSize = maxQueueSize;
}
public void setFilter(EntryListFilter<File> filter) {
this.filter = new SingleEntryAdaptingEntryListFilter<File>(filter);
}
public void setExecutor(Executor executor) {
this.executor = executor;
}
public void setAutoCreateDirectory(boolean autoCreateDirectory) {
this.autoCreateDirectory = autoCreateDirectory;
}
protected void publishNewFileReceived(String path) {
this.publishNewFileReceived(new File(path));
}
protected void publishNewFileReceived(File file) {
this.additions.add(file);
}
/**
* Obviously, we're trying to keep away from scanning, but this may be necessary at startup to catch up the backlog
*/
protected void prescan() {
synchronized (this.guard) {
if (this.scanDirectoryOnLoad) {
for (File f : this.directoryToMonitorCached.listFiles())
this.publishNewFileReceived(f);
}
}
}
/**
* Handles ensuring that the directory we're monitroring exists or can be created
*
* @param directoryToMonitor the directory to monitor
* @throws Exception
*/
protected void installDirectoryIfRequired(File directoryToMonitor)
throws Exception {
synchronized (this.guard) {
this.directoryToMonitorCached = directoryToMonitor;
Assert.state(null != this.directoryToMonitorCached, "the directory to monitor can't be null");
boolean directoryIsReady = this.directoryToMonitorCached.exists();
if (!directoryIsReady) {
if (!directoryToMonitorCached.exists()) {
if (this.autoCreateDirectory) {
Assert.state(directoryToMonitorCached.mkdirs() && directoryToMonitorCached.exists(),
String.format("Couldn't create the directory %s", directoryToMonitorCached.getAbsolutePath()));
}
}
}
}
}
/**
* Custom initialization hook - override at your discretion
*
* @throws Exception
*/
protected void start() throws Exception{
// noop
}
@Override
protected void onInit() throws Exception {
additions = new LinkedBlockingQueue<File>(this.maxQueueSize);
if (this.executor == null) {
this.executor = new SimpleAsyncTaskExecutor();
}
if (this.filter == null) {
this.filter = new SingleEntryAdaptingEntryListFilter<File>(new AcceptAllEntryListFilter<File>());
}
Assert.notNull(this.filter, "the filter can't be null");
this.start();
}
/**
* a way to remove the responsibility of reacting to the file system from implementations while still being thread safe and handling backlog
*
* @author Josh Long
*/
class FileDeliveryPump implements Runnable {
private volatile FileAdditionListener fileAdditionListener;
public FileDeliveryPump(FileAdditionListener fileAdditionListener) {
this.fileAdditionListener = fileAdditionListener;
Assert.notNull(this.fileAdditionListener, "the FileAdditionListener can't be null");
}
public void run() {
do {
try {
File taken = additions.take();
if (filter.accept(taken)) {
fileAdditionListener.fileAdded(taken);
}
} catch (Throwable th) {
throw new RuntimeException(th);
}
} while (true);
}
}
}
/*
class MyEDFRM extends AbstractEventDrivenFileMonitor {
@Override
protected void start() throws Exception {
System.out.println("start()");
}
public void addToHeap(File file) {
this.publishNewFileReceived(file);
}
public static void main(String[] args) throws Throwable {
SimpleAsyncTaskExecutor simpleAsyncTaskExecutor = new SimpleAsyncTaskExecutor();
final MyEDFRM myEDFRM = new MyEDFRM();
myEDFRM.setExecutor(simpleAsyncTaskExecutor);
myEDFRM.setAutoCreateDirectory(true);
myEDFRM.afterPropertiesSet();
AcceptOnceEntryFileListFilter <File> acceptOnceEntryFileListFilter=new AcceptOnceEntryFileListFilter<File>() ;
acceptOnceEntryFileListFilter.afterPropertiesSet();
PatternMatchingEntryListFilter <File> patternMatchingEntryListFilter=
new PatternMatchingEntryListFilter<File>(new FileEntryNamer(), ".*?jpg");
patternMatchingEntryListFilter.afterPropertiesSet();
Collection<? extends EntryListFilter<File>> l=Arrays.asList( acceptOnceEntryFileListFilter, patternMatchingEntryListFilter);
CompositeEntryListFilter compositeEntryListFilter = new CompositeEntryListFilter<File>(l );
myEDFRM.setFilter(compositeEntryListFilter);
final File desktop = new File(System.getProperty("user.home"), "Desktop");
myEDFRM.monitor(desktop, new FileAdditionListener() {
public void fileAdded(File f) {
System.out.println("Got one! " + f.getAbsolutePath());
}
});
System.out.println("enjoying the world");
simpleAsyncTaskExecutor.execute(new Runnable() {
public void run() {
while (true) {
try {
Thread.sleep(1000 * 10);
for (File f : desktop.listFiles())
myEDFRM.addToHeap(f);
} catch (InterruptedException e) {
//
}
}
}
});
}
}
*/

View File

@@ -0,0 +1,18 @@
package org.springframework.integration.file.monitors;
import java.io.File;
/**
* simply takes a cue / hint (something <emphasis>tells</emphasis> it outright that something has
* been added to a directory, and it and publishes an event as appropriate). This is useful for adapters
* that know when the file's been downloaded and want to deliver data as soon as its downloaded, but to poll the
* remote system only at a certain interval.
*
* @author Josh Long
*/
public class DirectedEventDrivenFileMonitor extends AbstractEventDrivenFileMonitor {
public void directlyNotifyOfNewFile(File file) {
this.publishNewFileReceived(file);
}
}

View File

@@ -0,0 +1,21 @@
package org.springframework.integration.file.monitors;
import java.io.File;
/**
* Defines an interface for a component that reacts to file system events
*
* @author Josh Long
*/
public interface EventDrivenDirectoryMonitor {
/**
* the implementation should know how to publish events on the {@link FileAdditionListener}
* for a given #directory
*
* @param directory the directory to start watching from. Unspecified if this implies recursion or not.
* @param fileAdditionListener the callback
* @throws Exception if anything should go wrong
*/
void monitor(File directory, FileAdditionListener fileAdditionListener) throws Exception;
}

View File

@@ -0,0 +1,19 @@
package org.springframework.integration.file.monitors;
import java.io.File;
/**
* A generic hook into the arrival of a new {@link java.io.File}
*
* @author Josh Long
* @see org.springframework.integration.file.monitors.MessageSendingFileAdditionListener
*/
public interface FileAdditionListener {
/**
* a callback method that's invoked when a new {@link java.io.File} is detected.
*
* @param f the {@link java.io.File} that was detected
*/
void fileAdded(File f);
}

View File

@@ -0,0 +1,47 @@
package org.springframework.integration.file.monitors;
import org.springframework.integration.Message;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.core.MessageBuilder;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;
import java.io.File;
/**
* Supports propagating a {@link org.springframework.integration.Message} on the receipt of a new {@link java.io.File}
*
* @author Josh Long
*/
public class MessageSendingFileAdditionListener extends IntegrationObjectSupport implements FileAdditionListener {
private MessagingTemplate messagingTemplate = new MessagingTemplate();
private MessageChannel channel;
private PlatformTransactionManager platformTransactionManager;
public void setChannel(MessageChannel channel) {
this.channel = channel;
}
@Override
protected void onInit() throws Exception {
Assert.notNull(this.channel, "'channel' can't be null!");
if (this.platformTransactionManager != null) {
this.messagingTemplate.setTransactionManager(platformTransactionManager);
}
}
public void setPlatformTransactionManager(PlatformTransactionManager platformTransactionManager) {
this.platformTransactionManager = platformTransactionManager;
}
public void fileAdded(File f) {
Message<File> fileMsg = MessageBuilder.withPayload(f).build();
this.messagingTemplate.send(fileMsg);
}
}

View File

@@ -53,7 +53,7 @@
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.file.FileListFilter"/>
<tool:expected-type type="org.springframework.integration.file.filters.FileListFilter"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>

View File

@@ -56,7 +56,7 @@
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.file.FileListFilter"/>
<tool:expected-type type="org.springframework.integration.file.entries.EntryListFilter"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>

View File

@@ -16,57 +16,64 @@
package org.springframework.integration.file;
import static org.mockito.Mockito.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.springframework.integration.file.entries.CompositeEntryListFilter;
import org.springframework.integration.file.entries.EntryListFilter;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
/**
* @author Iwein Fuld
*/
public class CompositeFileListFilterTests {
private FileListFilter fileFilterMock1 = mock(FileListFilter.class);
@SuppressWarnings("unchecked")
private EntryListFilter<File> fileFilterMock1 = mock(EntryListFilter.class);
private FileListFilter fileFilterMock2 = mock(FileListFilter.class);
@SuppressWarnings("unchecked")
private EntryListFilter<File> fileFilterMock2 = mock(EntryListFilter.class);
private File fileMock = mock(File.class);
private File fileMock = mock(File.class);
@Test
public void forwardedToFilters() throws Exception {
CompositeFileListFilter compositeFileFilter = new CompositeFileListFilter(fileFilterMock1, fileFilterMock2);
List<File> returnedFiles = Arrays.asList(new File[] { fileMock });
when(fileFilterMock1.filterFiles(isA(File[].class))).thenReturn(returnedFiles);
when(fileFilterMock2.filterFiles(isA(File[].class))).thenReturn(returnedFiles);
assertEquals(returnedFiles, compositeFileFilter.filterFiles(new File[]{fileMock}));
verify(fileFilterMock1).filterFiles(isA(File[].class));
verify(fileFilterMock2).filterFiles(isA(File[].class));
}
@Test
public void forwardedToFilters() throws Exception {
CompositeEntryListFilter<File> compositeFileFilter = new CompositeEntryListFilter<File>(fileFilterMock1, fileFilterMock2);
List<File> returnedFiles = Arrays.asList( fileMock);
when(fileFilterMock1.filterEntries(isA(File[].class))).thenReturn(returnedFiles);
when(fileFilterMock2.filterEntries(isA(File[].class))).thenReturn(returnedFiles);
assertEquals(returnedFiles, compositeFileFilter.filterEntries(new File[]{fileMock}));
verify(fileFilterMock1).filterEntries(isA(File[].class));
verify(fileFilterMock2).filterEntries(isA(File[].class));
}
@Test
public void forwardedToAddedFilters() throws Exception {
CompositeFileListFilter compositeFileFilter = new CompositeFileListFilter().addFilter(fileFilterMock1, fileFilterMock2);
List<File> returnedFiles = Arrays.asList(new File[] { fileMock });
when(fileFilterMock1.filterFiles(isA(File[].class))).thenReturn(returnedFiles);
when(fileFilterMock2.filterFiles(isA(File[].class))).thenReturn(returnedFiles);
assertEquals(returnedFiles, compositeFileFilter.filterFiles(new File[]{fileMock}));
verify(fileFilterMock1).filterFiles(isA(File[].class));
verify(fileFilterMock2).filterFiles(isA(File[].class));
}
@Test
public void forwardedToAddedFilters() throws Exception {
CompositeEntryListFilter<File> compositeFileFilter = new CompositeEntryListFilter<File>();
compositeFileFilter.addFilter(fileFilterMock1);
compositeFileFilter.addFilter( fileFilterMock2);
List<File> returnedFiles = Arrays.asList(fileMock);
when(fileFilterMock1.filterEntries(isA(File[].class))).thenReturn(returnedFiles);
when(fileFilterMock2.filterEntries(isA(File[].class))).thenReturn(returnedFiles);
assertEquals(returnedFiles, compositeFileFilter.filterEntries(new File[]{fileMock}));
verify(fileFilterMock1).filterEntries(isA(File[].class));
verify(fileFilterMock2).filterEntries(isA(File[].class));
}
@Test
public void negative() throws Exception {
CompositeFileListFilter compositeFileFilter = new CompositeFileListFilter(fileFilterMock1, fileFilterMock2);
when(fileFilterMock2.filterFiles(isA(File[].class))).thenReturn(new ArrayList<File>());
when(fileFilterMock1.filterFiles(isA(File[].class))).thenReturn(new ArrayList<File>());
assertTrue(compositeFileFilter.filterFiles(new File[]{fileMock}).isEmpty());
verify(fileFilterMock1).filterFiles(isA(File[].class));
verify(fileFilterMock2).filterFiles(isA(File[].class));
}
@Test
public void negative() throws Exception {
CompositeEntryListFilter<File> compositeFileFilter = new CompositeEntryListFilter<File>();
compositeFileFilter.addFilter(fileFilterMock1);
compositeFileFilter.addFilter(fileFilterMock2);
when(fileFilterMock2.filterEntries(isA(File[].class))).thenReturn(new ArrayList<File>());
when(fileFilterMock1.filterEntries(isA(File[].class))).thenReturn(new ArrayList<File>());
assertTrue(compositeFileFilter.filterEntries(new File[]{fileMock}).isEmpty());
}
}

View File

@@ -1,28 +1,49 @@
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
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-2.0.xsd">
<!-- under test -->
<bean id="pollableFileSource" class="org.springframework.integration.file.FileReadingMessageSource"
p:directory="file:${java.io.tmpdir}/FileReadingMessageSourceIntegrationTests"
p:filter-ref="compositeFilter"/>
<!-- under test -->
<bean id="pollableFileSource" class="org.springframework.integration.file.FileReadingMessageSource"
p:directory="file:${java.io.tmpdir}/FileReadingMessageSourceIntegrationTests"
p:filter-ref="compositeFilter"/>
<!-- customized filter -->
<bean id="compositeFilter" class="org.springframework.integration.file.CompositeFileListFilter">
<constructor-arg>
<list>
<bean class="org.springframework.integration.file.AcceptOnceFileListFilter" />
<bean class="org.springframework.integration.file.TestFileListFilter" />
<bean class="org.springframework.integration.file.PatternMatchingFileListFilter">
<constructor-arg value="^test.*$"/>
</bean>
</list>
</constructor-arg>
</bean>
<bean class="org.springframework.integration.file.entries.FileEntryNamer" id="entryNamer"/>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" />
<!-- customized filter -->
<!--
<bean id="compositeFilter" class="org.springframework.integration.file.entries.CompositeEntryListFilter">
<constructor-arg>
<list>
<bean class="org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter"/>
<bean class="org.springframework.integration.file.TestFileListFilter"/>
<bean class="org.springframework.integration.file.entries.PatternMatchingEntryListFilter">
<constructor-arg ref="entryNamer"/>
<constructor-arg value="^test.*$"/>
</bean>
</list>
</constructor-arg>
</bean>
-->
</beans>
<bean class="org.springframework.integration.file.config.FileListFilterFactoryBean" id="compositeFilter">
<property name="filterReferences">
<util:list>
<bean class="org.springframework.integration.file.config.FileListFilterFactoryBean" p:preventDuplicates="true"/>
<bean class="org.springframework.integration.file.config.FileListFilterFactoryBean" p:preventDuplicates="false"/>
<bean class="org.springframework.integration.file.config.FileListFilterFactoryBean" p:filenamePattern="^test.*$"/>
</util:list>
</property>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>
</beans>

View File

@@ -37,148 +37,155 @@ import static org.junit.Assert.*;
@ContextConfiguration
public class FileReadingMessageSourceIntegrationTests {
@Autowired
FileReadingMessageSource pollableFileSource;
@Autowired
FileReadingMessageSource pollableFileSource;
private static File inputDir;
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") + "/"
+ FileReadingMessageSourceIntegrationTests.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);
}
@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();
}
@BeforeClass
public static void setupInputDir() {
inputDir = new File(System.getProperty("java.io.tmpdir") + "/"
+ FileReadingMessageSourceIntegrationTests.class.getSimpleName());
inputDir.mkdir();
}
@Test
public void configured() throws Exception {
DirectFieldAccessor accessor = new DirectFieldAccessor(pollableFileSource);
assertEquals(inputDir, accessor.getPropertyValue("directory"));
}
@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);
}
@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());
}
@After
public void cleanoutInputDir() throws Exception {
File[] listFiles = inputDir.listFiles();
for (int i = 0; i < listFiles.length; i++) {
listFiles[i].delete();
}
}
@Test
public void parallelRetrieval() throws Exception {
Message<File> received1 = pollableFileSource.receive();
Message<File> received2 = pollableFileSource.receive();
Message<File> received3 = pollableFileSource.receive();
assertNotSame(received1 + " == " + received2, received1, received2);
assertNotSame(received1 + " == " + received3, received1, received3);
assertNotSame(received2 + " == " + received3, received2, received3);
}
@AfterClass
public static void removeInputDir() throws Exception {
inputDir.delete();
}
@Test
public void inputDirExhausted() throws Exception {
assertNotNull(pollableFileSource.receive());
assertNotNull(pollableFileSource.receive());
assertNotNull(pollableFileSource.receive());
assertNull(pollableFileSource.receive());
}
@Test(timeout = 6000)
@Repeat(5)
public void concurrentProcessing() throws Exception {
CountDownLatch go = new CountDownLatch(1);
Runnable succesfulConsumer = new Runnable() {
public void run() {
Message<File> received = pollableFileSource.receive();
while (received == null) {
Thread.yield();
received = pollableFileSource.receive();
}
pollableFileSource.onSend(received);
}
};
Runnable failingConsumer = new Runnable() {
public void run() {
Message<File> received = pollableFileSource.receive();
if (received != null) {
pollableFileSource.onFailure(received);
}
}
};
CountDownLatch succesfulDone = doConcurrently(3, succesfulConsumer, go);
CountDownLatch failingDone = doConcurrently(10, failingConsumer, go);
go.countDown();
try {
succesfulDone.await();
failingDone.await();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// make sure three different files were taken
Message<File> received = pollableFileSource.receive();
if (received != null) {
pollableFileSource.onSend(received);
}
assertNull(received);
}
@Test
public void configured() throws Exception {
DirectFieldAccessor accessor = new DirectFieldAccessor(pollableFileSource);
assertEquals(inputDir, accessor.getPropertyValue("directory"));
}
/**
* Convenience method to run part of a test concurrently in multiple threads
*
* @param numberOfThreads how many threads to spawn
* @param runnable the runnable that should be run by all the threads
* @param start the {@link java.util.concurrent.CountDownLatch} instance telling it when to assume everything works
* @return a latch that will be counted down once all threads have run their
* runnable.
*/
private CountDownLatch doConcurrently(int numberOfThreads, final Runnable runnable, final CountDownLatch start) {
final CountDownLatch started = new CountDownLatch(numberOfThreads);
final CountDownLatch done = new CountDownLatch(numberOfThreads);
for (int i = 0; i < numberOfThreads; i++) {
new Thread(new Runnable() {
@Test
public void getFiles() throws Exception {
Message<File> received1 = pollableFileSource.receive();
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());
}
@Test
public void parallelRetrieval() throws Exception {
Message<File> received1 = pollableFileSource.receive();
Message<File> received2 = pollableFileSource.receive();
Message<File> received3 = pollableFileSource.receive();
assertNotSame(received1 + " == " + received2, received1, received2);
assertNotSame(received1 + " == " + received3, received1, received3);
assertNotSame(received2 + " == " + received3, received2, received3);
}
@Test
public void inputDirExhausted() throws Exception {
assertNotNull(pollableFileSource.receive());
assertNotNull(pollableFileSource.receive());
assertNotNull(pollableFileSource.receive());
assertNull(pollableFileSource.receive());
}
@Test(timeout = 6000)
@Repeat(10)
public void concurrentProcessing() throws Exception {
CountDownLatch go = new CountDownLatch(1);
Runnable succesfulConsumer = new Runnable() {
public void run() {
Message<File> received = pollableFileSource.receive();
while (received == null) {
Thread.yield();
received = pollableFileSource.receive();
}
pollableFileSource.onSend(received);
}
};
Runnable failingConsumer = new Runnable() {
public void run() {
Message<File> received = pollableFileSource.receive();
if (received != null) {
pollableFileSource.onFailure(received, new RuntimeException("nothing"));
}
}
};
CountDownLatch succesfulDone = doConcurrently(3, succesfulConsumer, go);
CountDownLatch failingDone = doConcurrently(10, failingConsumer, go);
go.countDown();
try {
succesfulDone.await();
failingDone.await();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// make sure three different files were taken
Message<File> received = pollableFileSource.receive();
if (received != null) {
pollableFileSource.onSend(received);
}
assertNull(received);
}
/**
* Convenience method to run part of a test concurrently in multiple threads
*
* @param numberOfThreads
* @param todo the runnable that should be run by all the threads
* @return a latch that will be counted down once all threads have run their
* runnable.
*/
private CountDownLatch doConcurrently(int numberOfThreads, final Runnable todo, final CountDownLatch start) {
final CountDownLatch started = new CountDownLatch(numberOfThreads);
final CountDownLatch done = new CountDownLatch(numberOfThreads);
for (int i = 0; i < numberOfThreads; i++) {
new Thread(new Runnable() {
public void run() {
started.countDown();
try {
started.await();
start.await();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
todo.run();
done.countDown();
}
}).start();
}
return done;
}
public void run() {
started.countDown();
try {
started.await();
start.await();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
runnable.run();
done.countDown();
}
}).start();
}
return done;
}
}

View File

@@ -78,7 +78,7 @@ public class FileReadingMessageSourceTests {
when(inputDirectoryMock.listFiles()).thenReturn(new File[]{fileMock});
Message received = source.receive();
assertNotNull(received);
source.onFailure(received, new RuntimeException("failed"));
source.onFailure(received);
assertEquals(received.getPayload(), source.receive().getPayload());
verify(inputDirectoryMock, times(1)).listFiles();
}

View File

@@ -1,46 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:file="http://www.springframework.org/schema/integration/file"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans"
xmlns:si="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p" xmlns:si="http://www.springframework.org/schema/integration" 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/integration
http://www.springframework.org/schema/integration/spring-integration.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/file/spring-integration-file.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd">
<!-- under test -->
<file:inbound-channel-adapter
directory="#{inputDirectory.path}"
channel="fileMessages" filter="compositeFilter"/>
<!-- under test -->
<file:inbound-channel-adapter
directory="#{inputDirectory.path}"
channel="fileMessages" filter="compositeFilter"/>
<bean id="temp" class="org.junit.rules.TemporaryFolder"
init-method="create" destroy-method="delete"/>
<bean id="temp" class="org.junit.rules.TemporaryFolder"
init-method="create" destroy-method="delete"/>
<bean id="inputDirectory" class="java.io.File">
<constructor-arg value="#{temp.newFolder('FileToChannelIntegrationTests').path}"/>
</bean>
<bean id="inputDirectory" class="java.io.File">
<constructor-arg value="#{temp.newFolder('FileToChannelIntegrationTests').path}"/>
</bean>
<si:channel id="fileMessages">
<si:queue capacity="10" />
</si:channel>
<si:channel id="fileMessages">
<si:queue capacity="10"/>
</si:channel>
<!-- customized filter -->
<bean id="compositeFilter"
class="org.springframework.integration.file.CompositeFileListFilter">
<constructor-arg>
<list>
<bean class="org.springframework.integration.file.AcceptOnceFileListFilter"/>
<bean class="org.springframework.integration.file.TestFileListFilter"/>
<bean class="org.springframework.integration.file.PatternMatchingFileListFilter">
<constructor-arg value="^test.*$"/>
</bean>
</list>
</constructor-arg>
</bean>
<si:poller default="true" fixed-rate="10"/>
<bean class="org.springframework.integration.file.config.FileListFilterFactoryBean" id="compositeFilter">
<property name="filterReferences">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>
<util:list>
</beans>
<bean class="org.springframework.integration.file.config.FileListFilterFactoryBean" p:preventDuplicates="true"/>
<bean class="org.springframework.integration.file.config.FileListFilterFactoryBean" p:preventDuplicates="false"/>
<bean class="org.springframework.integration.file.config.FileListFilterFactoryBean" p:filenamePattern="^test.*$"/>
</util:list>
</property>
</bean>
<si:poller default="true" fixed-rate="10"/>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>
</beans>

View File

@@ -16,75 +16,75 @@
package org.springframework.integration.file;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.file.entries.EntryListFilter;
import org.springframework.integration.file.entries.FileEntryNamer;
import org.springframework.integration.file.entries.PatternMatchingEntryListFilter;
import java.io.File;
import java.util.List;
import java.util.regex.Pattern;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Mark Fisher
*/
public class PatternMatchingFileListFilterTests {
@Test
public void matchSingleFile() {
File[] files = new File[] { new File("/some/path/test.txt") };
Pattern pattern = Pattern.compile("[a-z]+\\.txt");
PatternMatchingFileListFilter filter = new PatternMatchingFileListFilter(pattern);
List<File> accepted = filter.filterFiles(files);
assertEquals(1, accepted.size());
}
private FileEntryNamer fileEntryNamer = new FileEntryNamer();
@Test
public void noMatchWithSingleFile() {
File[] files = new File[] { new File("/some/path/Test.txt") };
Pattern pattern = Pattern.compile("[a-z]+\\.txt");
PatternMatchingFileListFilter filter = new PatternMatchingFileListFilter(pattern);
List<File> accepted = filter.filterFiles(files);
assertEquals(0, accepted.size());
}
@Test
public void matchSingleFile() {
File[] files = new File[]{new File("/some/path/test.txt")};
Pattern pattern = Pattern.compile("[a-z]+\\.txt");
PatternMatchingEntryListFilter<File> filter = new PatternMatchingEntryListFilter<File>(fileEntryNamer, pattern);
List<File> accepted = filter.filterEntries(files);
assertEquals(1, accepted.size());
}
@Test
public void matchSubset() {
File[] files = new File[] {
new File("/some/path/foo.txt"),
new File("/some/path/foo.not"),
new File("/some/path/bar.txt"),
new File("/some/path/bar.not")
};
Pattern pattern = Pattern.compile("[a-z]+\\.txt");
PatternMatchingFileListFilter filter = new PatternMatchingFileListFilter(pattern);
List<File> accepted = filter.filterFiles(files);
assertEquals(2, accepted.size());
assertTrue(accepted.contains(new File("/some/path/foo.txt")));
assertTrue(accepted.contains(new File("/some/path/bar.txt")));
}
@Test
public void noMatchWithSingleFile() {
File[] files = new File[]{new File("/some/path/Test.txt")};
Pattern pattern = Pattern.compile("[a-z]+\\.txt");
PatternMatchingEntryListFilter<File> filter = new PatternMatchingEntryListFilter<File>(fileEntryNamer, pattern);
List<File> accepted = filter.filterEntries(files);
assertEquals(0, accepted.size());
}
@Test(expected = IllegalArgumentException.class)
public void nullPattern() {
new PatternMatchingFileListFilter(null);
}
@Test
public void matchSubset() {
File[] files = new File[]{
new File("/some/path/foo.txt"),
new File("/some/path/foo.not"),
new File("/some/path/bar.txt"),
new File("/some/path/bar.not")
};
Pattern pattern = Pattern.compile("[a-z]+\\.txt");
PatternMatchingEntryListFilter<File> filter = new PatternMatchingEntryListFilter<File>(this.fileEntryNamer, pattern);
List<File> accepted = filter.filterEntries(files);
assertEquals(2, accepted.size());
assertTrue(accepted.contains(new File("/some/path/foo.txt")));
assertTrue(accepted.contains(new File("/some/path/bar.txt")));
}
@Test
public void patternEditorInContext() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"patternMatchingFileListFilterTests.xml", this.getClass());
FileListFilter filter = (FileListFilter) context.getBean("filter");
File[] files = new File[] { new File("/some/path/foo.txt") };
List<File> accepted = filter.filterFiles(files);
assertEquals(1, accepted.size());
}
@Test(expected = BeanCreationException.class)
public void invalidPatternSyntax() throws Throwable {
new ClassPathXmlApplicationContext("invalidPatternMatchingFileListFilterTests.xml", this.getClass());
}
@Test
public void patternEditorInContext() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"patternMatchingFileListFilterTests.xml", this.getClass());
EntryListFilter<File> filter = (EntryListFilter<File>) context.getBean("filter");
File[] files = new File[]{new File("/some/path/foo.txt")};
List<File> accepted = filter.filterEntries(files);
assertEquals(1, accepted.size());
}
@Test(expected = BeanCreationException.class)
public void invalidPatternSyntax() throws Throwable {
new ClassPathXmlApplicationContext("invalidPatternMatchingFileListFilterTests.xml", this.getClass());
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.integration.file;
import org.springframework.integration.file.entries.EntryListFilter;
import java.io.File;
import java.util.Arrays;
import java.util.List;
@@ -23,10 +25,8 @@ import java.util.List;
/**
* @author Iwein Fuld
*/
public class TestFileListFilter implements FileListFilter {
public List<File> filterFiles(File[] files) {
return Arrays.asList(files);
}
public class TestFileListFilter implements EntryListFilter<File> {
public List<File> filterEntries(File[] entries) {
return Arrays.asList(entries);
}
}

View File

@@ -58,8 +58,6 @@
<context:property-placeholder/>
<si:poller default="true">
<si:interval-trigger interval="10000"/>
</si:poller>
<si:poller default="true" fixed-rate="10000"/>
</beans>

View File

@@ -16,16 +16,10 @@
package org.springframework.integration.file.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
@@ -34,6 +28,11 @@ import org.springframework.integration.file.FileWritingMessageHandler;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.File;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Mark Fisher
*/
@@ -41,92 +40,92 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
public class AutoCreateDirectoryIntegrationTests {
private static final String BASE_PATH =
System.getProperty("java.io.tmpdir") + File.separator + AutoCreateDirectoryIntegrationTests.class.getSimpleName();
private static final String BASE_PATH =
System.getProperty("java.io.tmpdir") + File.separator + AutoCreateDirectoryIntegrationTests.class.getSimpleName();
@Autowired
private ApplicationContext context;
@Autowired
private ApplicationContext context;
@BeforeClass
public static void setupNonAutoCreatedDirectories() {
new File(BASE_PATH).delete();
new File(BASE_PATH + File.separator + "customInbound").mkdirs();
new File(BASE_PATH + File.separator + "customOutbound").mkdirs();
new File(BASE_PATH + File.separator + "customOutboundGateway").mkdirs();
}
@BeforeClass
public static void setupNonAutoCreatedDirectories() {
new File(BASE_PATH).delete();
new File(BASE_PATH + File.separator + "customInbound").mkdirs();
new File(BASE_PATH + File.separator + "customOutbound").mkdirs();
new File(BASE_PATH + File.separator + "customOutboundGateway").mkdirs();
}
@AfterClass
public static void deleteBaseDirectory() {
new File(BASE_PATH).delete();
}
@AfterClass
public static void deleteBaseDirectory() {
new File(BASE_PATH).delete();
}
@Test
public void defaultInbound() throws Exception {
Object adapter = context.getBean("defaultInbound");
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
FileReadingMessageSource source = (FileReadingMessageSource)
adapterAccessor.getPropertyValue("source");
assertEquals(Boolean.TRUE,
new DirectFieldAccessor(source).getPropertyValue("autoCreateDirectory"));
assertTrue(new File(BASE_PATH + File.separator + "defaultInbound").exists());
}
@Test
public void defaultInbound() throws Exception {
Object adapter = context.getBean("defaultInbound");
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
FileReadingMessageSource source = (FileReadingMessageSource)
adapterAccessor.getPropertyValue("source");
assertEquals(Boolean.TRUE,
new DirectFieldAccessor(source).getPropertyValue("autoCreateDirectory"));
assertTrue(new File(BASE_PATH + File.separator + "defaultInbound").exists());
}
@Test
public void customInbound() throws Exception {
Object adapter = context.getBean("customInbound");
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
FileReadingMessageSource source = (FileReadingMessageSource)
adapterAccessor.getPropertyValue("source");
assertTrue(new File(BASE_PATH + File.separator + "customInbound").exists());
assertEquals(Boolean.FALSE,
new DirectFieldAccessor(source).getPropertyValue("autoCreateDirectory"));
}
@Test
public void customInbound() throws Exception {
Object adapter = context.getBean("customInbound");
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
FileReadingMessageSource source = (FileReadingMessageSource)
adapterAccessor.getPropertyValue("source");
assertTrue(new File(BASE_PATH + File.separator + "customInbound").exists());
assertEquals(Boolean.FALSE,
new DirectFieldAccessor(source).getPropertyValue("autoCreateDirectory"));
}
@Test
public void defaultOutbound() throws Exception {
Object adapter = context.getBean("defaultOutbound");
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
FileWritingMessageHandler handler = (FileWritingMessageHandler)
adapterAccessor.getPropertyValue("handler");
assertEquals(Boolean.TRUE,
new DirectFieldAccessor(handler).getPropertyValue("autoCreateDirectory"));
assertTrue(new File(BASE_PATH + File.separator + "defaultOutbound").exists());
}
@Test
public void defaultOutbound() throws Exception {
Object adapter = context.getBean("defaultOutbound");
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
FileWritingMessageHandler handler = (FileWritingMessageHandler)
adapterAccessor.getPropertyValue("handler");
assertEquals(Boolean.TRUE,
new DirectFieldAccessor(handler).getPropertyValue("autoCreateDirectory"));
assertTrue(new File(BASE_PATH + File.separator + "defaultOutbound").exists());
}
@Test
public void customOutbound() throws Exception {
Object adapter = context.getBean("customOutbound");
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
FileWritingMessageHandler handler = (FileWritingMessageHandler)
adapterAccessor.getPropertyValue("handler");
assertTrue(new File(BASE_PATH + File.separator + "customOutbound").exists());
assertEquals(Boolean.FALSE,
new DirectFieldAccessor(handler).getPropertyValue("autoCreateDirectory"));
}
@Test
public void customOutbound() throws Exception {
Object adapter = context.getBean("customOutbound");
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter);
FileWritingMessageHandler handler = (FileWritingMessageHandler)
adapterAccessor.getPropertyValue("handler");
assertTrue(new File(BASE_PATH + File.separator + "customOutbound").exists());
assertEquals(Boolean.FALSE,
new DirectFieldAccessor(handler).getPropertyValue("autoCreateDirectory"));
}
@Test
public void defaultOutboundGateway() throws Exception {
Object gateway = context.getBean("defaultOutboundGateway");
DirectFieldAccessor gatewayAccessor = new DirectFieldAccessor(gateway);
FileWritingMessageHandler handler = (FileWritingMessageHandler)
gatewayAccessor.getPropertyValue("handler");
assertEquals(Boolean.TRUE,
new DirectFieldAccessor(handler).getPropertyValue("autoCreateDirectory"));
assertTrue(new File(BASE_PATH + File.separator + "defaultOutboundGateway").exists());
}
@Test
public void defaultOutboundGateway() throws Exception {
Object gateway = context.getBean("defaultOutboundGateway");
DirectFieldAccessor gatewayAccessor = new DirectFieldAccessor(gateway);
FileWritingMessageHandler handler = (FileWritingMessageHandler)
gatewayAccessor.getPropertyValue("handler");
assertEquals(Boolean.TRUE,
new DirectFieldAccessor(handler).getPropertyValue("autoCreateDirectory"));
assertTrue(new File(BASE_PATH + File.separator + "defaultOutboundGateway").exists());
}
@Test
public void customOutboundGateway() throws Exception {
Object gateway = context.getBean("customOutboundGateway");
DirectFieldAccessor gatewayAccessor = new DirectFieldAccessor(gateway);
FileWritingMessageHandler handler = (FileWritingMessageHandler)
gatewayAccessor.getPropertyValue("handler");
assertTrue(new File(BASE_PATH + File.separator + "customOutboundGateway").exists());
assertEquals(Boolean.FALSE,
new DirectFieldAccessor(handler).getPropertyValue("autoCreateDirectory"));
}
@Test
public void customOutboundGateway() throws Exception {
Object gateway = context.getBean("customOutboundGateway");
DirectFieldAccessor gatewayAccessor = new DirectFieldAccessor(gateway);
FileWritingMessageHandler handler = (FileWritingMessageHandler)
gatewayAccessor.getPropertyValue("handler");
assertTrue(new File(BASE_PATH + File.separator + "customOutboundGateway").exists());
assertEquals(Boolean.FALSE,
new DirectFieldAccessor(handler).getPropertyValue("autoCreateDirectory"));
}
}

View File

@@ -16,18 +16,18 @@
package org.springframework.integration.file.config;
import java.util.Date;
import org.springframework.integration.Message;
import org.springframework.integration.file.FileNameGenerator;
import java.util.Date;
/**
* @author Marius Bogoevici
*/
public class CustomFileNameGenerator implements FileNameGenerator {
public String generateFileName(Message<?> message) {
return "file" + new Date().getTime();
}
public String generateFileName(Message<?> message) {
return "file" + new Date().getTime();
}
}

View File

@@ -16,12 +16,8 @@
package org.springframework.integration.file.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
@@ -33,6 +29,9 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* @author Mark Fisher
* @since 1.0.3
@@ -41,32 +40,32 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ContextConfiguration
public class DefaultConfigurationTests {
@Autowired
private ApplicationContext context;
@Autowired
private ApplicationContext context;
@Test
public void verifyErrorChannel() {
Object errorChannel = context.getBean(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME);
assertNotNull(errorChannel);
assertEquals(PublishSubscribeChannel.class, errorChannel.getClass());
}
@Test
public void verifyErrorChannel() {
Object errorChannel = context.getBean(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME);
assertNotNull(errorChannel);
assertEquals(PublishSubscribeChannel.class, errorChannel.getClass());
}
@Test
public void verifyNullChannel() {
Object nullChannel = context.getBean(IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME);
assertNotNull(nullChannel);
assertEquals(NullChannel.class, nullChannel.getClass());
}
@Test
public void verifyNullChannel() {
Object nullChannel = context.getBean(IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME);
assertNotNull(nullChannel);
assertEquals(NullChannel.class, nullChannel.getClass());
}
@Test
public void verifyTaskScheduler() {
Object taskScheduler = context.getBean(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME);
assertEquals(ThreadPoolTaskScheduler.class, taskScheduler.getClass());
Object errorHandler = new DirectFieldAccessor(taskScheduler).getPropertyValue("errorHandler");
assertEquals(MessagePublishingErrorHandler.class, errorHandler.getClass());
Object defaultErrorChannel = new DirectFieldAccessor(errorHandler).getPropertyValue("defaultErrorChannel");
assertEquals(context.getBean(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME), defaultErrorChannel);
}
@Test
public void verifyTaskScheduler() {
Object taskScheduler = context.getBean(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME);
assertEquals(ThreadPoolTaskScheduler.class, taskScheduler.getClass());
Object errorHandler = new DirectFieldAccessor(taskScheduler).getPropertyValue("errorHandler");
assertEquals(MessagePublishingErrorHandler.class, errorHandler.getClass());
Object defaultErrorChannel = new DirectFieldAccessor(errorHandler).getPropertyValue("defaultErrorChannel");
assertEquals(context.getBean(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME), defaultErrorChannel);
}
}

View File

@@ -15,20 +15,22 @@
filter="filter"
comparator="testComparator"
auto-startup="false">
<integration:poller>
<integration:interval-trigger interval="5000"/>
</integration:poller>
<integration:poller fixed-rate="5000"/>
</inbound-channel-adapter>
<beans:bean id="filter" class="org.springframework.integration.file.CompositeFileListFilter">
<beans:bean id="filter" class="org.springframework.integration.file.config.FileListFilterFactoryBean">
</beans:bean>
<!--
<beans:bean id="filter" class="org.springframework.integration.file.filters.CompositeFileListFilter">
<beans:constructor-arg>
<beans:list></beans:list>
</beans:constructor-arg>
</beans:bean>
</beans:bean>-->
<beans:bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>
<beans:bean id="testComparator"
class="org.springframework.integration.file.config.FileInboundChannelAdapterParserTests$TestComparator"/>
</beans:beans>
</beans:beans>

View File

@@ -16,27 +16,24 @@
package org.springframework.integration.file.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.file.DefaultDirectoryScanner;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.File;
import java.util.Comparator;
import java.util.concurrent.PriorityBlockingQueue;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.file.CompositeFileListFilter;
import org.springframework.integration.file.DefaultDirectoryScanner;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.*;
/**
* @author Iwein Fuld
@@ -46,57 +43,58 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
public class FileInboundChannelAdapterParserTests {
@Autowired(required=true)
private ApplicationContext context;
@Autowired(required = true)
private ApplicationContext context;
@Autowired
private FileReadingMessageSource source;
@Autowired
private FileReadingMessageSource source;
private DirectFieldAccessor accessor;
private DirectFieldAccessor accessor;
@Before
public void init() {
accessor = new DirectFieldAccessor(source);
}
@Before
public void init() {
accessor = new DirectFieldAccessor(source);
}
@Test
public void channelName() throws Exception {
AbstractMessageChannel channel = context.getBean("inputDirPoller", AbstractMessageChannel.class);
assertEquals("Channel should be available under specified id", "inputDirPoller", channel.getComponentName());
}
@Test
public void channelName() throws Exception {
AbstractMessageChannel channel = context.getBean("inputDirPoller", AbstractMessageChannel.class);
assertEquals("Channel should be available under specified id", "inputDirPoller", channel.getComponentName());
}
@Test
public void inputDirectory() {
File expected = new File(System.getProperty("java.io.tmpdir"));
File actual = (File) accessor.getPropertyValue("directory");
assertEquals("'directory' should be set", expected, actual);
}
@Test
public void inputDirectory() {
File expected = new File(System.getProperty("java.io.tmpdir"));
File actual = (File) accessor.getPropertyValue("directory");
assertEquals("'directory' should be set", expected, actual);
}
@Test
public void filter() throws Exception {
DefaultDirectoryScanner scanner = (DefaultDirectoryScanner) accessor.getPropertyValue("scanner");
DirectFieldAccessor scannerAccessor = new DirectFieldAccessor(scanner);
assertTrue("'filter' should be set",
scannerAccessor.getPropertyValue("filter") instanceof CompositeFileListFilter);
}
@Test
public void filter() throws Exception {
DefaultDirectoryScanner scanner = (DefaultDirectoryScanner) accessor.getPropertyValue("scanner");
DirectFieldAccessor scannerAccessor = new DirectFieldAccessor(scanner);
Object filter = scannerAccessor.getPropertyValue("filter");
assertTrue("'filter' should be set",
filter instanceof AcceptOnceEntryFileListFilter);
}
@Test
public void comparator() throws Exception {
Object priorityQueue = accessor.getPropertyValue("toBeReceived");
assertEquals(PriorityBlockingQueue.class, priorityQueue.getClass());
Object expected = context.getBean("testComparator");
Object innerQueue = new DirectFieldAccessor(priorityQueue).getPropertyValue("q");
Object actual = new DirectFieldAccessor(innerQueue).getPropertyValue("comparator");
assertSame("comparator reference not set, ", expected, actual);
}
@Test
public void comparator() throws Exception {
Object priorityQueue = accessor.getPropertyValue("toBeReceived");
assertEquals(PriorityBlockingQueue.class, priorityQueue.getClass());
Object expected = context.getBean("testComparator");
Object innerQueue = new DirectFieldAccessor(priorityQueue).getPropertyValue("q");
Object actual = new DirectFieldAccessor(innerQueue).getPropertyValue("comparator");
assertSame("comparator reference not set, ", expected, actual);
}
static class TestComparator implements Comparator<File> {
static class TestComparator implements Comparator<File> {
public int compare(File f1, File f2) {
return 0;
}
}
public int compare(File f1, File f2) {
return 0;
}
}
}

View File

@@ -15,11 +15,9 @@
<inbound-channel-adapter id="inputDirPoller"
directory="${inputdir}"
auto-startup="false">
<integration:poller>
<integration:interval-trigger interval="5000"/>
</integration:poller>
<integration:poller fixed-rate="5000"/>
</inbound-channel-adapter>
<context:property-placeholder location="classpath:org/springframework/integration/file/config/test.properties" />
</beans:beans>
</beans:beans>

View File

@@ -15,9 +15,7 @@
<inbound-channel-adapter id="adapterWithPattern"
directory="file:${java.io.tmpdir}"
filename-pattern=".*\.txt" auto-startup="false">
<integration:poller>
<integration:interval-trigger interval="10000"/>
</integration:poller>
<integration:poller fixed-rate="1000"/>
</inbound-channel-adapter>
</beans:beans>
</beans:beans>

View File

@@ -16,32 +16,28 @@
package org.springframework.integration.file.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.util.Set;
import java.util.regex.Pattern;
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;
import org.springframework.context.ApplicationContext;
import org.springframework.integration.channel.AbstractMessageChannel;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.file.AcceptOnceFileListFilter;
import org.springframework.integration.file.CompositeFileListFilter;
import org.springframework.integration.file.FileListFilter;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.PatternMatchingFileListFilter;
import org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter;
import org.springframework.integration.file.entries.CompositeEntryListFilter;
import org.springframework.integration.file.entries.EntryListFilter;
import org.springframework.integration.file.entries.PatternMatchingEntryListFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.File;
import java.util.Set;
import java.util.regex.Pattern;
import static org.junit.Assert.*;
/**
* @author Mark Fisher
* @author Iwein Fuld
@@ -50,90 +46,90 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
public class FileInboundChannelAdapterWithPatternParserTests {
@Autowired(required=true)
private ApplicationContext context;
@Autowired(required = true)
private ApplicationContext context;
@Autowired(required=true)
@Qualifier("adapterWithPattern.adapter")
private AbstractEndpoint endpoint;
@Autowired(required = true)
@Qualifier("adapterWithPattern.adapter")
private AbstractEndpoint endpoint;
private DirectFieldAccessor accessor;
private DirectFieldAccessor accessor;
@Autowired(required=true)
public void setSource(FileReadingMessageSource source) {
this.accessor = new DirectFieldAccessor(source);
}
@Autowired(required = true)
public void setSource(FileReadingMessageSource source) {
this.accessor = new DirectFieldAccessor(source);
}
@Test
public void channelName() {
AbstractMessageChannel channel = context.getBean("adapterWithPattern", AbstractMessageChannel.class);
assertEquals("adapterWithPattern", channel.getComponentName());
}
@Test
public void channelName() {
AbstractMessageChannel channel = context.getBean("adapterWithPattern", AbstractMessageChannel.class);
assertEquals("adapterWithPattern", channel.getComponentName());
}
@Test
public void autoStartupDisabled() {
assertFalse(this.endpoint.isRunning());
assertEquals(Boolean.FALSE, new DirectFieldAccessor(endpoint).getPropertyValue("autoStartup"));
}
@Test
public void autoStartupDisabled() {
assertFalse(this.endpoint.isRunning());
assertEquals(Boolean.FALSE, new DirectFieldAccessor(endpoint).getPropertyValue("autoStartup"));
}
@Test
public void inputDirectory() {
File expected = new File(System.getProperty("java.io.tmpdir"));
File actual = (File) accessor.getPropertyValue("directory");
assertEquals(expected, actual);
}
@Test
public void inputDirectory() {
File expected = new File(System.getProperty("java.io.tmpdir"));
File actual = (File) accessor.getPropertyValue("directory");
assertEquals(expected, actual);
}
@Test
public void compositeFilterType() {
@Test
public void compositeFilterType() {
DirectFieldAccessor scannerAccessor = new DirectFieldAccessor(accessor.getPropertyValue("scanner"));
assertTrue(scannerAccessor.getPropertyValue("filter") instanceof CompositeFileListFilter);
}
assertTrue(scannerAccessor.getPropertyValue("filter") instanceof CompositeEntryListFilter);
}
@Test
@SuppressWarnings("unchecked")
public void compositeFilterSetSize() {
@Test
@SuppressWarnings("unchecked")
public void compositeFilterSetSize() {
DirectFieldAccessor scannerAccessor = new DirectFieldAccessor(accessor.getPropertyValue("scanner"));
Set<FileListFilter> filters = (Set<FileListFilter>) new DirectFieldAccessor(
scannerAccessor.getPropertyValue("filter")).getPropertyValue("fileFilters");
assertEquals(2, filters.size());
}
Set<EntryListFilter<File>> filters = (Set<EntryListFilter<File>>) new DirectFieldAccessor(
scannerAccessor.getPropertyValue("filter")).getPropertyValue("fileFilters");
assertEquals(2, filters.size());
}
@Test
@SuppressWarnings("unchecked")
public void acceptOnceFilter() {
@Test
@SuppressWarnings("unchecked")
public void acceptOnceFilter() {
DirectFieldAccessor scannerAccessor = new DirectFieldAccessor(accessor.getPropertyValue("scanner"));
Set<FileListFilter> filters = (Set<FileListFilter>) new DirectFieldAccessor(
scannerAccessor.getPropertyValue("filter")).getPropertyValue("fileFilters");
boolean hasAcceptOnceFilter = false;
for (FileListFilter filter : filters) {
if (filter instanceof AcceptOnceFileListFilter) {
hasAcceptOnceFilter = true;
}
}
assertTrue("expected AcceptOnceFileListFilter", hasAcceptOnceFilter);
}
Set<EntryListFilter<File>> filters = (Set<EntryListFilter<File>>) new DirectFieldAccessor(
scannerAccessor.getPropertyValue("filter")).getPropertyValue("fileFilters");
boolean hasAcceptOnceFilter = false;
for (EntryListFilter<File> filter : filters) {
if (filter instanceof AcceptOnceEntryFileListFilter) {
hasAcceptOnceFilter = true;
}
}
assertTrue("expected AcceptOnceFileListFilter", hasAcceptOnceFilter);
}
@Test
@SuppressWarnings("unchecked")
public void patternFilter() {
@Test
@SuppressWarnings("unchecked")
public void patternFilter() {
DirectFieldAccessor scannerAccessor = new DirectFieldAccessor(accessor.getPropertyValue("scanner"));
Set<FileListFilter> filters = (Set<FileListFilter>) new DirectFieldAccessor(
scannerAccessor.getPropertyValue("filter")).getPropertyValue("fileFilters");
Pattern pattern = null;
for (FileListFilter filter : filters) {
if (filter instanceof PatternMatchingFileListFilter) {
pattern = (Pattern) new DirectFieldAccessor(filter).getPropertyValue("pattern");
}
}
assertNotNull("expected PatternMatchingFileListFilter", pattern);
assertEquals(".*\\.txt", pattern.toString());
assertFalse(pattern.matcher("foo").matches());
assertTrue(pattern.matcher("foo.txt").matches());
}
Set<EntryListFilter> filters = (Set<EntryListFilter>) new DirectFieldAccessor(
scannerAccessor.getPropertyValue("filter")).getPropertyValue("fileFilters");
Pattern pattern = null;
for (EntryListFilter filter : filters) {
if (filter instanceof PatternMatchingEntryListFilter) {
pattern = (Pattern) new DirectFieldAccessor(filter).getPropertyValue("pattern");
}
}
assertNotNull("expected PatternMatchingFileListFilter", pattern);
assertEquals(".*\\.txt", pattern.toString());
assertFalse(pattern.matcher("foo").matches());
assertTrue(pattern.matcher("foo.txt").matches());
}
}

View File

@@ -1,114 +1,103 @@
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/file
http://www.springframework.org/schema/integration/file/spring-integration-file.xsd">
<beans:bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" />
<beans:bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>
<integration:channel id="channel">
<integration:queue/>
</integration:channel>
<integration:channel id="channel">
<integration:queue/>
</integration:channel>
<inbound-channel-adapter id="filterAndNull"
directory="file:${java.io.tmpdir}"
filter="testFilter"
channel="channel"
auto-startup="false">
<integration:poller>
<integration:interval-trigger interval="10000"/>
</integration:poller>
</inbound-channel-adapter>
<inbound-channel-adapter id="filterAndNull"
directory="file:${java.io.tmpdir}"
filter="testFilter"
channel="channel"
auto-startup="false">
<integration:poller fixed-rate="10000"/>
</inbound-channel-adapter>
<inbound-channel-adapter id="filterAndTrue"
directory="file:${java.io.tmpdir}"
filter="testFilter"
prevent-duplicates="true"
channel="channel"
auto-startup="false">
<integration:poller>
<integration:interval-trigger interval="10000"/>
</integration:poller>
</inbound-channel-adapter>
<inbound-channel-adapter id="filterAndTrue"
directory="file:${java.io.tmpdir}"
filter="testFilter"
prevent-duplicates="true"
channel="channel"
auto-startup="false">
<integration:poller fixed-rate="10000"/>
</inbound-channel-adapter>
<inbound-channel-adapter id="filterAndFalse"
directory="file:${java.io.tmpdir}"
filter="testFilter"
prevent-duplicates="false"
channel="channel"
auto-startup="false">
<integration:poller>
<integration:interval-trigger interval="10000"/>
</integration:poller>
</inbound-channel-adapter>
<inbound-channel-adapter id="filterAndFalse"
directory="file:${java.io.tmpdir}"
filter="testFilter"
prevent-duplicates="false"
channel="channel"
auto-startup="false">
<integration:poller fixed-rate="10000"/>
<inbound-channel-adapter id="patternAndNull"
directory="file:${java.io.tmpdir}"
filename-pattern="test"
channel="channel"
auto-startup="false">
<integration:poller>
<integration:interval-trigger interval="10000"/>
</integration:poller>
</inbound-channel-adapter>
</inbound-channel-adapter>
<inbound-channel-adapter id="patternAndTrue"
directory="file:${java.io.tmpdir}"
filename-pattern="test"
prevent-duplicates="true"
channel="channel"
auto-startup="false">
<integration:poller>
<integration:interval-trigger interval="10000"/>
</integration:poller>
</inbound-channel-adapter>
<inbound-channel-adapter id="patternAndNull"
directory="file:${java.io.tmpdir}"
filename-pattern="test"
channel="channel"
auto-startup="false">
<integration:poller fixed-rate="10000"/>
<inbound-channel-adapter id="patternAndFalse"
directory="file:${java.io.tmpdir}"
filename-pattern="test"
prevent-duplicates="false"
channel="channel"
auto-startup="false">
<integration:poller>
<integration:interval-trigger interval="10000"/>
</integration:poller>
</inbound-channel-adapter>
</inbound-channel-adapter>
<inbound-channel-adapter id="defaultAndNull"
directory="file:${java.io.tmpdir}"
channel="channel"
auto-startup="false">
<integration:poller>
<integration:interval-trigger interval="10000"/>
</integration:poller>
</inbound-channel-adapter>
<inbound-channel-adapter id="patternAndTrue"
directory="file:${java.io.tmpdir}"
filename-pattern="test"
prevent-duplicates="true"
channel="channel"
auto-startup="false">
<integration:poller fixed-rate="10000"/>
<inbound-channel-adapter id="defaultAndTrue"
directory="file:${java.io.tmpdir}"
prevent-duplicates="true"
channel="channel"
auto-startup="false">
<integration:poller>
<integration:interval-trigger interval="10000"/>
</integration:poller>
</inbound-channel-adapter>
</inbound-channel-adapter>
<inbound-channel-adapter id="defaultAndFalse"
directory="file:${java.io.tmpdir}"
prevent-duplicates="false"
channel="channel"
auto-startup="false">
<integration:poller>
<integration:interval-trigger interval="10000"/>
</integration:poller>
</inbound-channel-adapter>
<inbound-channel-adapter id="patternAndFalse"
directory="file:${java.io.tmpdir}"
filename-pattern="test"
prevent-duplicates="false"
channel="channel"
auto-startup="false">
<integration:poller fixed-rate="10000"/>
<beans:bean id="testFilter" class="org.springframework.integration.file.TestFileListFilter"/>
</inbound-channel-adapter>
<inbound-channel-adapter id="defaultAndNull"
directory="file:${java.io.tmpdir}"
channel="channel"
auto-startup="false">
<integration:poller fixed-rate="10000"/>
</inbound-channel-adapter>
<inbound-channel-adapter id="defaultAndTrue"
directory="file:${java.io.tmpdir}"
prevent-duplicates="true"
channel="channel"
auto-startup="false">
<integration:poller fixed-rate="10000"/>
</inbound-channel-adapter>
<inbound-channel-adapter id="defaultAndFalse"
directory="file:${java.io.tmpdir}"
prevent-duplicates="false"
channel="channel"
auto-startup="false">
<integration:poller fixed-rate="10000"/>
</inbound-channel-adapter>
<beans:bean id="testFilter" class="org.springframework.integration.file.TestFileListFilter"/>
</beans:beans>

View File

@@ -13,136 +13,146 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.file.config;
import static org.junit.Assert.*;
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;
import org.springframework.context.ApplicationContext;
import org.springframework.integration.file.*;
import org.springframework.integration.file.TestFileListFilter;
import org.springframework.integration.file.entries.AcceptOnceEntryFileListFilter;
import org.springframework.integration.file.entries.CompositeEntryListFilter;
import org.springframework.integration.file.entries.EntryListFilter;
import org.springframework.integration.file.entries.PatternMatchingEntryListFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.File;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import static org.junit.Assert.*;
/**
* @author Mark Fisher
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class FileInboundChannelAdapterWithPreventDuplicatesFlagTests {
@Autowired
private ApplicationContext context;
@Autowired
@Qualifier("testFilter")
private TestFileListFilter testFilter;
@Test
public void filterAndNull() {
FileListFilter filter = this.extractFilter("filterAndNull");
assertFalse(filter instanceof CompositeFileListFilter);
EntryListFilter filter = this.extractFilter("filterAndNull");
assertFalse(filter instanceof CompositeEntryListFilter);
assertSame(testFilter, filter);
}
@Test
@SuppressWarnings("unchecked")
public void filterAndTrue() {
FileListFilter filter = this.extractFilter("filterAndTrue");
assertTrue(filter instanceof CompositeFileListFilter);
EntryListFilter filter = this.extractFilter("filterAndTrue");
assertTrue(filter instanceof CompositeEntryListFilter);
Collection filters = (Collection) new DirectFieldAccessor(filter).getPropertyValue("fileFilters");
assertTrue(filters.iterator().next() instanceof AcceptOnceFileListFilter);
assertTrue(filters.iterator().next() instanceof AcceptOnceEntryFileListFilter);
assertTrue(filters.contains(testFilter));
}
@Test
public void filterAndFalse() throws Exception {
FileListFilter filter = this.extractFilter("filterAndFalse");
assertFalse(filter instanceof CompositeFileListFilter);
EntryListFilter filter = this.extractFilter("filterAndFalse");
assertFalse(filter instanceof CompositeEntryListFilter);
assertSame(testFilter, filter);
}
@Test
@SuppressWarnings("unchecked")
public void patternAndNull() throws Exception {
FileListFilter filter = this.extractFilter("patternAndNull");
assertTrue(filter instanceof CompositeFileListFilter);
EntryListFilter filter = this.extractFilter("patternAndNull");
assertTrue(filter instanceof CompositeEntryListFilter);
Collection filters = (Collection) new DirectFieldAccessor(filter).getPropertyValue("fileFilters");
Iterator<FileListFilter> iterator = filters.iterator();
assertTrue(iterator.next() instanceof AcceptOnceFileListFilter);
assertTrue(iterator.next() instanceof PatternMatchingFileListFilter);
Iterator<EntryListFilter<File>> iterator = filters.iterator();
assertTrue(iterator.next() instanceof AcceptOnceEntryFileListFilter);
assertTrue(iterator.next() instanceof PatternMatchingEntryListFilter);
}
@Test
@SuppressWarnings("unchecked")
public void patternAndTrue() throws Exception {
FileListFilter filter = this.extractFilter("patternAndTrue");
assertTrue(filter instanceof CompositeFileListFilter);
EntryListFilter filter = this.extractFilter("patternAndTrue");
assertTrue(filter instanceof CompositeEntryListFilter);
Collection filters = (Collection) new DirectFieldAccessor(filter).getPropertyValue("fileFilters");
Iterator<FileListFilter> iterator = filters.iterator();
assertTrue(iterator.next() instanceof AcceptOnceFileListFilter);
assertTrue(iterator.next() instanceof PatternMatchingFileListFilter);
Iterator<EntryListFilter> iterator = filters.iterator();
assertTrue(iterator.next() instanceof AcceptOnceEntryFileListFilter);
assertTrue(iterator.next() instanceof PatternMatchingEntryListFilter);
}
@Test
public void patternAndFalse() throws Exception {
FileListFilter filter = this.extractFilter("patternAndFalse");
assertFalse(filter instanceof CompositeFileListFilter);
assertTrue(filter instanceof PatternMatchingFileListFilter);
EntryListFilter<File> filter = this.extractFilter("patternAndFalse");
assertFalse(filter instanceof CompositeEntryListFilter);
assertTrue(filter instanceof PatternMatchingEntryListFilter);
}
@Test
public void defaultAndNull() throws Exception {
FileListFilter filter = this.extractFilter("defaultAndNull");
EntryListFilter<File> filter = this.extractFilter("defaultAndNull");
assertNotNull(filter);
assertFalse(filter instanceof CompositeFileListFilter);
assertTrue(filter instanceof AcceptOnceFileListFilter);
assertFalse(filter instanceof CompositeEntryListFilter);
assertTrue(filter instanceof AcceptOnceEntryFileListFilter);
File testFile = new File("test");
File[] files = new File[]{testFile, testFile, testFile};
List<File> result = filter.filterFiles(files);
File[] files = new File[] { testFile, testFile, testFile };
List<File> result = filter.filterEntries(files);
assertEquals(1, result.size());
}
@Test
@SuppressWarnings("unchecked")
public void defaultAndTrue() throws Exception {
FileListFilter filter = this.extractFilter("defaultAndTrue");
assertFalse(filter instanceof CompositeFileListFilter);
assertTrue(filter instanceof AcceptOnceFileListFilter);
EntryListFilter filter = this.extractFilter("defaultAndTrue");
assertFalse(filter instanceof CompositeEntryListFilter);
assertTrue(filter instanceof AcceptOnceEntryFileListFilter);
File testFile = new File("test");
File[] files = new File[]{testFile, testFile, testFile};
List<File> result = filter.filterFiles(files);
File[] files = new File[] { testFile, testFile, testFile };
List<File> result = filter.filterEntries(files);
assertEquals(1, result.size());
}
@Test
@SuppressWarnings("unchecked")
public void defaultAndFalse() throws Exception {
FileListFilter filter = this.extractFilter("defaultAndFalse");
EntryListFilter filter = this.extractFilter("defaultAndFalse");
assertNotNull(filter);
assertFalse(filter instanceof CompositeFileListFilter);
assertFalse(filter instanceof AcceptOnceFileListFilter);
assertFalse(filter instanceof CompositeEntryListFilter);
assertFalse(filter instanceof AcceptOnceEntryFileListFilter);
File testFile = new File("test");
File[] files = new File[]{testFile, testFile, testFile};
List<File> result = filter.filterFiles(files);
File[] files = new File[] { testFile, testFile, testFile };
List<File> result = filter.filterEntries(files);
assertEquals(3, result.size());
}
private FileListFilter extractFilter(String beanName) {
return (FileListFilter) new DirectFieldAccessor(
new DirectFieldAccessor(
new DirectFieldAccessor(context.getBean(beanName))
.getPropertyValue("source"))
.getPropertyValue("scanner"))
.getPropertyValue("filter");
@SuppressWarnings("unchecked")
private EntryListFilter<File> extractFilter(String beanName) {
return (EntryListFilter<File>) new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(context.getBean(beanName)).getPropertyValue("source")).getPropertyValue("scanner")).getPropertyValue(
"filter");
}
}

View File

@@ -31,12 +31,11 @@
filter="filter"
queue-size="30"
auto-startup="false">
<integration:poller>
<integration:interval-trigger interval="5000"/>
</integration:poller>
<integration:poller fixed-rate="1000"/>
</inbound-channel-adapter>
<beans:bean class="org.springframework.integration.file.config.FileListFilterFactoryBean" />
<beans:bean id="filter" class="org.springframework.integration.file.CompositeFileListFilter">
<beans:bean id="filter" class="org.springframework.integration.file.entries.CompositeEntryListFilter">
<beans:constructor-arg>
<beans:list></beans:list>
</beans:constructor-arg>
@@ -47,4 +46,4 @@
<beans:bean id="testComparator"
class="org.springframework.integration.file.config.FileInboundChannelAdapterParserTests$TestComparator"/>
</beans:beans>
</beans:beans>

View File

@@ -18,7 +18,7 @@ package org.springframework.integration.file.config;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.integration.file.*;
import org.springframework.integration.file.entries.*;
import java.io.File;
import java.util.Collection;
@@ -32,93 +32,92 @@ import static org.junit.Assert.*;
*/
public class FileListFilterFactoryBeanTests {
@Test(expected = IllegalArgumentException.class)
public void customFilterAndFilenamePatternAreMutuallyExclusive() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
factory.setFilterReference(new TestFilter());
factory.setFilenamePattern(Pattern.compile("foo"));
factory.getObject();
}
@Test(expected = IllegalArgumentException.class)
public void customFilterAndFilenamePatternAreMutuallyExclusive() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
factory.setFilterReference(new TestFilter());
factory.setFilenamePattern(Pattern.compile("foo"));
factory.getObject();
}
@Test
public void customFilterAndPreventDuplicatesNull() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
TestFilter testFilter = new TestFilter();
factory.setFilterReference(testFilter);
FileListFilter result = factory.getObject();
assertFalse(result instanceof CompositeFileListFilter);
assertSame(testFilter, result);
}
@Test
public void customFilterAndPreventDuplicatesNull() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
TestFilter testFilter = new TestFilter();
factory.setFilterReference(testFilter);
EntryListFilter<File> result = factory.getObject();
assertFalse(result instanceof CompositeEntryListFilter);
assertSame(testFilter, result);
}
@Test
@SuppressWarnings("unchecked")
public void customFilterAndPreventDuplicatesTrue() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
TestFilter testFilter = new TestFilter();
factory.setFilterReference(testFilter);
factory.setPreventDuplicates(Boolean.TRUE);
FileListFilter result = factory.getObject();
assertTrue(result instanceof CompositeFileListFilter);
Collection filters = (Collection) new DirectFieldAccessor(result).getPropertyValue("fileFilters");
assertTrue(filters.iterator().next() instanceof AcceptOnceFileListFilter);
assertTrue(filters.contains(testFilter));
}
@Test
@SuppressWarnings("unchecked")
public void customFilterAndPreventDuplicatesTrue() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
TestFilter testFilter = new TestFilter();
factory.setFilterReference(testFilter);
factory.setPreventDuplicates(Boolean.TRUE);
EntryListFilter<File> result = factory.getObject();
assertTrue(result instanceof CompositeEntryListFilter);
Collection filters = (Collection) new DirectFieldAccessor(result).getPropertyValue("fileFilters");
assertTrue(filters.iterator().next() instanceof AcceptOnceEntryFileListFilter);
assertTrue(filters.contains(testFilter));
}
@Test
public void customFilterAndPreventDuplicatesFalse() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
TestFilter testFilter = new TestFilter();
factory.setFilterReference(testFilter);
factory.setPreventDuplicates(Boolean.FALSE);
FileListFilter result = factory.getObject();
assertFalse(result instanceof CompositeFileListFilter);
assertSame(testFilter, result);
}
@Test
public void customFilterAndPreventDuplicatesFalse() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
TestFilter testFilter = new TestFilter();
factory.setFilterReference(testFilter);
factory.setPreventDuplicates(Boolean.FALSE);
EntryListFilter<File> result = factory.getObject();
assertFalse(result instanceof CompositeEntryListFilter);
assertSame(testFilter, result);
}
@Test
@SuppressWarnings("unchecked")
public void filenamePatternAndPreventDuplicatesNull() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
factory.setFilenamePattern(Pattern.compile("foo"));
FileListFilter result = factory.getObject();
assertTrue(result instanceof CompositeFileListFilter);
Collection filters = (Collection) new DirectFieldAccessor(result).getPropertyValue("fileFilters");
Iterator<FileListFilter> iterator = filters.iterator();
assertTrue(iterator.next() instanceof AcceptOnceFileListFilter);
assertTrue(iterator.next() instanceof PatternMatchingFileListFilter);
}
@Test
@SuppressWarnings("unchecked")
public void filenamePatternAndPreventDuplicatesNull() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
factory.setFilenamePattern(Pattern.compile("foo"));
EntryListFilter<File> result = factory.getObject();
assertTrue(result instanceof CompositeEntryListFilter);
Collection filters = (Collection) new DirectFieldAccessor(result).getPropertyValue("fileFilters");
Iterator<EntryListFilter> iterator = filters.iterator();
assertTrue(iterator.next() instanceof AcceptOnceEntryFileListFilter);
assertTrue(iterator.next() instanceof PatternMatchingEntryListFilter);
}
@Test
@SuppressWarnings("unchecked")
public void filenamePatternAndPreventDuplicatesTrue() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
factory.setFilenamePattern(Pattern.compile("foo"));
factory.setPreventDuplicates(Boolean.TRUE);
FileListFilter result = factory.getObject();
assertTrue(result instanceof CompositeFileListFilter);
Collection filters = (Collection) new DirectFieldAccessor(result).getPropertyValue("fileFilters");
Iterator<FileListFilter> iterator = filters.iterator();
assertTrue(iterator.next() instanceof AcceptOnceFileListFilter);
assertTrue(iterator.next() instanceof PatternMatchingFileListFilter);
}
@Test
@SuppressWarnings("unchecked")
public void filenamePatternAndPreventDuplicatesTrue() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
factory.setFilenamePattern(Pattern.compile("foo"));
factory.setPreventDuplicates(Boolean.TRUE);
EntryListFilter<File> result = factory.getObject();
assertTrue(result instanceof CompositeEntryListFilter);
Collection filters = (Collection) new DirectFieldAccessor(result).getPropertyValue("fileFilters");
Iterator<EntryListFilter> iterator = filters.iterator();
assertTrue(iterator.next() instanceof AcceptOnceEntryFileListFilter);
assertTrue(iterator.next() instanceof PatternMatchingEntryListFilter);
}
@Test
public void filenamePatternAndPreventDuplicatesFalse() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
factory.setFilenamePattern(Pattern.compile("foo"));
factory.setPreventDuplicates(Boolean.FALSE);
FileListFilter result = factory.getObject();
assertFalse(result instanceof CompositeFileListFilter);
assertTrue(result instanceof PatternMatchingFileListFilter);
}
@Test
public void filenamePatternAndPreventDuplicatesFalse() throws Exception {
FileListFilterFactoryBean factory = new FileListFilterFactoryBean();
factory.setFilenamePattern(Pattern.compile("foo"));
factory.setPreventDuplicates(Boolean.FALSE);
EntryListFilter<File> result = factory.getObject();
assertFalse(result instanceof CompositeEntryListFilter);
assertTrue(result instanceof PatternMatchingEntryListFilter);
}
private static class TestFilter extends AbstractFileListFilter {
@Override
protected boolean accept(File file) {
return true;
}
}
private static class TestFilter extends AbstractEntryListFilter<File> {
@Override
public boolean accept(File file) {
return true;
}
}
}

View File

@@ -16,13 +16,8 @@
package org.springframework.integration.file.config;
import static org.junit.Assert.assertEquals;
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;
@@ -31,6 +26,10 @@ import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.File;
import static org.junit.Assert.assertEquals;
/**
* @author Iwein Fuld
* @author Mark Fisher
@@ -40,31 +39,31 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
public class FileOutboundAdaptersWithClasspathInPropertiesTests {
@Autowired
@Qualifier("adapter")
private EventDrivenConsumer adapter;
@Autowired
@Qualifier("adapter")
private EventDrivenConsumer adapter;
@Autowired
@Qualifier("gateway")
private EventDrivenConsumer gateway;
@Autowired
@Qualifier("gateway")
private EventDrivenConsumer gateway;
@Test
public void outboundChannelAdapter() throws Exception {
DirectFieldAccessor accessor = new DirectFieldAccessor(
new DirectFieldAccessor(adapter).getPropertyValue("handler"));
File expected = new ClassPathResource("").getFile();
File actual = (File) accessor.getPropertyValue("destinationDirectory");
assertEquals("'destinationDirectory' should be set", expected, actual);
}
@Test
public void outboundChannelAdapter() throws Exception {
DirectFieldAccessor accessor = new DirectFieldAccessor(
new DirectFieldAccessor(adapter).getPropertyValue("handler"));
File expected = new ClassPathResource("").getFile();
File actual = (File) accessor.getPropertyValue("destinationDirectory");
assertEquals("'destinationDirectory' should be set", expected, actual);
}
@Test
public void outboundGateway() throws Exception {
DirectFieldAccessor accessor = new DirectFieldAccessor(
new DirectFieldAccessor(gateway).getPropertyValue("handler"));
File expected = new ClassPathResource("").getFile();
File actual = (File) accessor.getPropertyValue("destinationDirectory");
assertEquals("'destinationDirectory' should be set", expected, actual);
}
@Test
public void outboundGateway() throws Exception {
DirectFieldAccessor accessor = new DirectFieldAccessor(
new DirectFieldAccessor(gateway).getPropertyValue("handler"));
File expected = new ClassPathResource("").getFile();
File actual = (File) accessor.getPropertyValue("destinationDirectory");
assertEquals("'destinationDirectory' should be set", expected, actual);
}
}

View File

@@ -16,11 +16,8 @@
package org.springframework.integration.file.config;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
@@ -28,6 +25,8 @@ import org.springframework.integration.file.FileWritingMessageHandler;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertEquals;
/**
* @author Mark Fisher
*/
@@ -35,19 +34,19 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
public class FileOutboundGatewayParserTests {
@Autowired
private ApplicationContext context;
@Autowired
private ApplicationContext context;
@Test
public void checkOrderedGateway() throws Exception {
Object gateway = context.getBean("ordered");
DirectFieldAccessor gatewayAccessor = new DirectFieldAccessor(gateway);
FileWritingMessageHandler handler = (FileWritingMessageHandler)
gatewayAccessor.getPropertyValue("handler");
assertEquals(Boolean.FALSE, gatewayAccessor.getPropertyValue("autoStartup"));
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
assertEquals(777, handlerAccessor.getPropertyValue("order"));
}
@Test
public void checkOrderedGateway() throws Exception {
Object gateway = context.getBean("ordered");
DirectFieldAccessor gatewayAccessor = new DirectFieldAccessor(gateway);
FileWritingMessageHandler handler = (FileWritingMessageHandler)
gatewayAccessor.getPropertyValue("handler");
assertEquals(Boolean.FALSE, gatewayAccessor.getPropertyValue("autoStartup"));
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
assertEquals(777, handlerAccessor.getPropertyValue("order"));
}
}

View File

@@ -16,11 +16,8 @@
package org.springframework.integration.file.config;
import static org.junit.Assert.assertEquals;
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;
@@ -30,6 +27,8 @@ import org.springframework.integration.transformer.MessageTransformingHandler;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertEquals;
/**
* @author Mark Fisher
*/
@@ -37,20 +36,20 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
public class FileToStringTransformerParserTests {
@Autowired
@Qualifier("transformer")
EventDrivenConsumer endpoint;
@Autowired
@Qualifier("transformer")
EventDrivenConsumer endpoint;
@Test
public void checkDeleteFilesValue() {
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(endpoint);
MessageTransformingHandler handler = (MessageTransformingHandler)
endpointAccessor.getPropertyValue("handler");
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
FileToStringTransformer transformer = (FileToStringTransformer)
handlerAccessor.getPropertyValue("transformer");
DirectFieldAccessor transformerAccessor = new DirectFieldAccessor(transformer);
assertEquals(Boolean.TRUE, transformerAccessor.getPropertyValue("deleteFiles"));
}
@Test
public void checkDeleteFilesValue() {
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(endpoint);
MessageTransformingHandler handler = (MessageTransformingHandler)
endpointAccessor.getPropertyValue("handler");
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
FileToStringTransformer transformer = (FileToStringTransformer)
handlerAccessor.getPropertyValue("transformer");
DirectFieldAccessor transformerAccessor = new DirectFieldAccessor(transformer);
assertEquals(Boolean.TRUE, transformerAccessor.getPropertyValue("deleteFiles"));
}
}

View File

@@ -1,11 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="filter" class="org.springframework.integration.file.PatternMatchingFileListFilter">
<constructor-arg value="[fo+\.[tx]{3}"/>
</bean>
<bean id="filter" class="org.springframework.integration.file.entries.PatternMatchingEntryListFilter">
<constructor-arg>
<bean class="org.springframework.integration.file.entries.FileEntryNamer"/>
</constructor-arg>
<constructor-arg value="[fo+\.[tx]{3}"/>
</bean>
</beans>

View File

@@ -22,8 +22,8 @@ import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.file.CompositeFileListFilter;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.entries.CompositeEntryListFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -51,9 +51,10 @@ public class FileLockingNamespaceTests {
FileReadingMessageSource customLockingSource;
@Before public void extractSources() {
nioLockingSource = (FileReadingMessageSource) new DirectFieldAccessor( nioAdapter).getPropertyValue("source");
customLockingSource = (FileReadingMessageSource) new DirectFieldAccessor( customAdapter).getPropertyValue("source");
@Before
public void extractSources() {
nioLockingSource = (FileReadingMessageSource) new DirectFieldAccessor(nioAdapter).getPropertyValue("source");
customLockingSource = (FileReadingMessageSource) new DirectFieldAccessor(customAdapter).getPropertyValue("source");
}
@Test
@@ -64,17 +65,17 @@ public class FileLockingNamespaceTests {
@Test
public void shouldSetCustomLockerProperly() {
assertThat(extractFromScanner("locker", customLockingSource), is(StubLocker.class));
assertThat(extractFromScanner("filter", customLockingSource), is(CompositeFileListFilter.class));
assertThat(extractFromScanner("filter", customLockingSource), is(CompositeEntryListFilter.class));
}
private Object extractFromScanner(String propertyName, FileReadingMessageSource source) {
return new DirectFieldAccessor( new DirectFieldAccessor(source).getPropertyValue("scanner") ).getPropertyValue(propertyName);
return new DirectFieldAccessor(new DirectFieldAccessor(source).getPropertyValue("scanner")).getPropertyValue(propertyName);
}
@Test
public void shouldSetNioLockerProperly() {
assertThat(extractFromScanner("locker", nioLockingSource), is(NioFileLocker.class));
assertThat(extractFromScanner("filter", nioLockingSource), is(CompositeFileListFilter.class));
assertThat(extractFromScanner("filter", nioLockingSource), is(CompositeEntryListFilter.class));
}
public static class StubLocker extends AbstractFileLockerFilter {

View File

@@ -18,7 +18,7 @@ package org.springframework.integration.file.locking;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.integration.file.FileListFilter;
import org.springframework.integration.file.entries.EntryListFilter;
import java.io.File;
import java.io.IOException;
@@ -49,20 +49,20 @@ public class NioFileLockerTests {
NioFileLocker filter = new NioFileLocker();
File testFile = new File(workdir, "test0");
testFile.createNewFile();
assertThat(filter.filterFiles(workdir.listFiles()).get(0), is(testFile));
assertThat(filter.filterEntries(workdir.listFiles()).get(0), is(testFile));
filter.lock(testFile);
assertThat(filter.filterFiles(workdir.listFiles()).get(0), is(testFile));
assertThat(filter.filterEntries(workdir.listFiles()).get(0), is(testFile));
}
@Test
public void fileNotListedWhenLockedByOtherFilter() throws IOException {
NioFileLocker filter1 = new NioFileLocker();
FileListFilter filter2 = new NioFileLocker();
EntryListFilter<File> filter2 = new NioFileLocker();
File testFile = new File(workdir, "test1");
testFile.createNewFile();
assertThat(filter1.filterFiles(workdir.listFiles()).get(0), is(testFile));
assertThat(filter1.filterEntries(workdir.listFiles()).get(0), is(testFile));
filter1.lock(testFile);
assertThat(filter2.filterFiles(workdir.listFiles()), is((List<File>)new ArrayList<File>()));
assertThat(filter2.filterEntries(workdir.listFiles()), is((List<File>)new ArrayList<File>()));
}
}
}

View File

@@ -1,11 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="filter" class="org.springframework.integration.file.PatternMatchingFileListFilter">
<constructor-arg value="fo+\.[tx]{3}"/>
</bean>
<!--<bean class="org.springframework.integration.file.filters.PatternMatchingFileListFilter">
<constructor-arg value="fo+\.[tx]{3}"/>
</bean>
-->
<bean id="filter" class="org.springframework.integration.file.entries.PatternMatchingEntryListFilter">
<constructor-arg>
<bean class="org.springframework.integration.file.entries.FileEntryNamer"/>
</constructor-arg>
<constructor-arg value="fo+\.[tx]{3}"/>
</bean>
</beans>

View File

@@ -17,9 +17,7 @@
auto-startup="true"
scanner="recursiveScanner"
channel="files">
<integration:poller>
<integration:interval-trigger interval="100"/>
</integration:poller>
<integration:poller fixed-rate="1000"/>
</inbound-channel-adapter>
<integration:channel id="files">
@@ -29,4 +27,4 @@
<beans:bean id="recursiveScanner" class="org.springframework.integration.file.RecursiveLeafOnlyDirectoryScanner"/>
<beans:bean id="directory" class="org.junit.rules.TemporaryFolder" init-method="create" destroy-method="delete"/>
</beans:beans>
</beans:beans>

View File

@@ -10,4 +10,6 @@ Import-Template:
org.springframework.core.*;version="[3.0.0, 4.0.0)",
org.springframework.util;version="[3.0.0, 4.0.0)",
org.springframework.util.xml;version="[3.0.0, 4.0.0)",
org.springframework.scheduling.*;version="[3.0.0, 4.0.0)",
org.springframework.transaction.*;version="[3.0.0, 4.0.0)",
org.w3c.dom.*;version="0"

View File

@@ -1,33 +0,0 @@
package org.springframework.integration.ftp;
import org.apache.commons.net.ftp.FTPFile;
import java.util.ArrayList;
import java.util.List;
/**
* Convenience implementation patterned off {@link org.springframework.integration.file.FileListFilter}
*
* @author Josh Long
*/
public abstract class AbstractFtpFileListFilter implements FtpFileListFilter {
/**
* {@inheritDoc}
*/
abstract public boolean accept(FTPFile ftpFile);
public List<FTPFile> filterFiles(FTPFile[] files) {
List<FTPFile> accepted = new ArrayList<FTPFile>();
if (files != null) {
for (FTPFile f : files) {
if (this.accept(f)) {
accepted.add(f);
}
}
}
return accepted;
}
}

View File

@@ -1,37 +0,0 @@
package org.springframework.integration.ftp;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.util.Assert;
import java.util.*;
/**
* Patterned very much on the {@link org.springframework.integration.file.CompositeFileListFilter}
*
* @author Josh Long
*/
public class CompositeFtpFileListFilter implements FtpFileListFilter {
private Set<FtpFileListFilter> filters;
public CompositeFtpFileListFilter(FtpFileListFilter... ftpFileListFilter) {
this.filters = new LinkedHashSet<FtpFileListFilter>(Arrays.asList(ftpFileListFilter));
}
public CompositeFtpFileListFilter(Collection<FtpFileListFilter> ftpFileListFilter) {
this.filters = new LinkedHashSet<FtpFileListFilter>(ftpFileListFilter);
}
public void addFilter( FtpFileListFilter ftpFileListFilter ) {
this.filters.add(ftpFileListFilter);
}
public List<FTPFile> filterFiles(FTPFile[] files) {
Assert.notNull(files, "files[] can't be null!");
List<FTPFile> leftOver = Arrays.asList(files);
for (FtpFileListFilter ff : this.filters)
leftOver = ff.filterFiles(leftOver.toArray(new FTPFile[leftOver.size()]));
return leftOver;
}
}

View File

@@ -0,0 +1,17 @@
package org.springframework.integration.ftp;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.integration.file.entries.EntryNamer;
/**
* A {@link org.springframework.integration.file.entries.EntryNamer} for {@link org.apache.commons.net.ftp.FTPFile} objects
*
* @author Josh Long
*/
public class FtpFileEntryNamer implements EntryNamer<FTPFile> {
public String nameOf(FTPFile entry) {
return entry.getName();
}
}

View File

@@ -1,14 +0,0 @@
package org.springframework.integration.ftp;
import org.apache.commons.net.ftp.FTPFile;
import java.util.List;
/**
* Filters out all the FTPFiles taken in a scan of the remote mount o
*
* @author Josh Long
*/
public interface FtpFileListFilter {
List<FTPFile> filterFiles (FTPFile [] files);
}

View File

@@ -1,112 +0,0 @@
/*
* 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.ftp;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.Lifecycle;
import org.springframework.core.io.Resource;
import org.springframework.integration.Message;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.file.AcceptOnceFileListFilter;
import org.springframework.integration.file.CompositeFileListFilter;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.PatternMatchingFileListFilter;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import java.io.File;
import java.io.IOException;
import java.util.regex.Pattern;
/**
* A source adapter for receiving files via FTP.
*
* @author Iwein Fuld
*/
public class FtpFileSource implements MessageSource<File>, InitializingBean, Lifecycle {
private FileReadingMessageSource fileSource;
private FtpInboundSynchronizer synchronizer;
public FtpFileSource() {
this(new FileReadingMessageSource(), new FtpInboundSynchronizer());
}
public FtpFileSource(FileReadingMessageSource fileSource, FtpInboundSynchronizer synchronizer) {
this.fileSource = fileSource;
this.synchronizer = synchronizer;
Pattern completePattern = Pattern.compile("^.*(?<!" + FtpInboundSynchronizer.INCOMPLETE_EXTENSION + ")$");
fileSource.setFilter(new CompositeFileListFilter(new AcceptOnceFileListFilter(), new PatternMatchingFileListFilter(completePattern)));
}
public void setFileSource(FileReadingMessageSource fileSource) {
this.fileSource = fileSource;
}
public void setSynchronizer(FtpInboundSynchronizer synchronizer) {
this.synchronizer = synchronizer;
}
public void setLocalWorkingDirectory(Resource localWorkingDirectory) {
this.synchronizer.setLocalDirectory(localWorkingDirectory);
try {
this.fileSource.setDirectory(localWorkingDirectory.getFile());
} catch (IOException e) {
}
}
public void setTrigger(Trigger trigger) {
synchronizer.setTrigger(trigger);
}
public void setTaskScheduler(TaskScheduler scheduler) {
synchronizer.setTaskScheduler(scheduler);
}
public void setClientPool(FtpClientPool pool) {
synchronizer.setClientPool(pool);
}
public void afterPropertiesSet() throws Exception {
synchronizer.afterPropertiesSet();
}
public Message<File> receive() {
return fileSource.receive();
}
public void onFailure(Message<File> failedMessage, Throwable t) {
fileSource.onFailure(failedMessage, t);
}
public void onSend(Message<File> sentMessage) {
fileSource.onSend(sentMessage);
}
public boolean isRunning() {
return synchronizer.isRunning();
}
public void start() {
synchronizer.start();
}
public void stop() {
synchronizer.stop();
}
}

View File

@@ -1,179 +0,0 @@
/*
* 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.ftp;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.Lifecycle;
import org.springframework.core.io.Resource;
import org.springframework.integration.MessagingException;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.util.Assert;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
/**
* <code>FtpInboundSynchronizer</code> will keep a local directory in sync with a remote Ftp directory.
* It will NOT move new files put into the local directory to the remote server.
*
* @author Iwein Fuld
*/
public class FtpInboundSynchronizer implements InitializingBean, Lifecycle {
private static final Log logger = LogFactory.getLog(FtpInboundSynchronizer.class);
static final String INCOMPLETE_EXTENSION = ".INCOMPLETE";
private static final long DEFAULT_REFRESH_RATE = 10000;
private volatile TaskScheduler taskScheduler;
private volatile Trigger trigger = new PeriodicTrigger(DEFAULT_REFRESH_RATE);
private volatile FtpClientPool clientPool;
private volatile Resource localDirectory;
private boolean running = false;
private ScheduledFuture<?> scheduledFuture;
private FtpFileListFilter filter;
private FtpFileListFilter acceptAllFtpFileListFilter = new FtpFileListFilter() {
public List<FTPFile> filterFiles(FTPFile[] files) {
return Arrays.asList(files);
}
};
public void setFilter(FtpFileListFilter filter) {
this.filter = filter;
}
public void setTaskScheduler(TaskScheduler scheduler) {
this.taskScheduler = scheduler;
}
public void setTrigger(Trigger trigger) {
this.trigger = trigger;
}
public void setLocalDirectory(Resource localDirectory) {
this.localDirectory = localDirectory;
}
public void setClientPool(FtpClientPool pool) {
clientPool = pool;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(localDirectory, "'localDirectory' is required.");
if (this.filter == null) {
this.filter = acceptAllFtpFileListFilter;
}
}
private void synchronize() {
try {
FTPClient client = this.clientPool.getClient();
Assert.state(client != null, FtpClientPool.class.getSimpleName() + " returned 'null' client this most likely a bug in the pool implementation.");
Collection<FTPFile> fileList = this.filter.filterFiles(client.listFiles());
try {
for (FTPFile ftpFile : fileList) {
/*
* according to the FTPFile javadoc the list can contain
* nulls if files couldn't be parsed
*/
if ((ftpFile != null) && ftpFile.isFile()) {
copyFileToLocalDirectory(client, ftpFile, this.localDirectory);
}
}
} finally {
this.clientPool.releaseClient(client);
}
} catch (IOException e) {
throw new MessagingException("Problem occurred while synchronizing remote to local directory", e);
}
}
private boolean copyFileToLocalDirectory(FTPClient client, FTPFile ftpFile, Resource localDirectory)
throws IOException, FileNotFoundException {
String remoteFileName = ftpFile.getName();
String localFileName = localDirectory.getFile().getPath() + "/" + remoteFileName;
File localFile = new File(localFileName);
if (!localFile.exists()) {
String tempFileName = localFileName + INCOMPLETE_EXTENSION;
File file = new File(tempFileName);
FileOutputStream fos = new FileOutputStream(file);
try {
client.retrieveFile(remoteFileName, fos);
} finally {
fos.close();
}
file.renameTo(localFile);
return true;
} else {
return false;
}
}
public boolean isRunning() {
return running;
}
public void start() {
if (running) {
return;
}
Assert.state(taskScheduler != null, "'taskScheduler' is required");
scheduledFuture = taskScheduler.schedule(new SynchronizeTask(), trigger);
// future.get();
this.running = true;
if (logger.isInfoEnabled()) {
logger.info("Started " + this);
}
}
public void stop() {
if (!running) {
return;
}
this.scheduledFuture.cancel(true);
this.running = false;
if (logger.isInfoEnabled()) {
logger.info("Stopped " + this);
}
}
private class SynchronizeTask implements Runnable {
public void run() {
synchronize();
}
}
}

View File

@@ -1,198 +0,0 @@
package org.springframework.integration.ftp;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceEditor;
import org.springframework.core.io.ResourceLoader;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.ErrorHandler;
import org.springframework.util.StringUtils;
import java.io.File;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Makes it easier to assemble the moving pieces involved in standing up an {@link FtpMessageSourceFactoryBean}
*
* @author Josh Long
*/
public class FtpMessageSourceFactoryBean extends AbstractFactoryBean<FtpFileSource> implements ResourceLoaderAware, ApplicationContextAware {
private int port;
private boolean autoCreateDirectories;
private String filenamePattern;
private String username;
private String password;
private String host;
private String remoteDirectory;
private String localWorkingDirectory;
private ApplicationContext applicationContext;
private Resource localDirectoryResource;
private FtpInboundSynchronizer ftpInboundSynchronizer;
private TaskScheduler taskScheduler;
private ResourceLoader resourceLoader;
private FileReadingMessageSource fileReadingMessageSource;
private int clientMode = FTPClient.ACTIVE_LOCAL_DATA_CONNECTION_MODE;
/**
* Used to teach the FTP adapter what files you are interested in receiving
*/
private FtpFileListFilter filter;
public void setFilenamePattern(String filenamePattern) {
this.filenamePattern = filenamePattern;
}
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
public void setPort(int port) {
this.port = port;
}
public void setPassword(String password) {
this.password = password;
}
public void setUsername(String username) {
this.username = username;
}
public void setAutoCreateDirectories(boolean autoCreateDirectories) {
this.autoCreateDirectories = autoCreateDirectories;
}
public void setRemoteDirectory(String remoteDirectory) {
this.remoteDirectory = remoteDirectory;
}
public void setHost(String host) {
this.host = host;
}
public void setLocalWorkingDirectory(String localWorkingDirectory) {
this.localWorkingDirectory = localWorkingDirectory;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public Class<?extends FtpFileSource> getObjectType() {
return FtpFileSource.class;
}
public void setClientMode(int clientMode) {
this.clientMode = clientMode;
}
public void setFilter(FtpFileListFilter filter) {
this.filter = filter;
}
@Override
protected FtpFileSource createInstance() throws Exception {
// setup local dir
if (!StringUtils.hasText(this.localWorkingDirectory)) {
File tmp = SystemUtils.getJavaIoTmpDir();
File ftpTmp = new File(tmp, "ftpInbound");
this.localWorkingDirectory = "file://" + ftpTmp.getAbsolutePath();
}
Assert.hasText(this.localWorkingDirectory, "the local working directory can't be null!");
ResourceEditor resourceEditor = new ResourceEditor(this.resourceLoader);
resourceEditor.setAsText(this.localWorkingDirectory);
this.localDirectoryResource = (Resource) resourceEditor.getValue();
fileReadingMessageSource = new FileReadingMessageSource();
this.ftpInboundSynchronizer = new FtpInboundSynchronizer();
CompositeFtpFileListFilter compositeFtpFileListFilter = new CompositeFtpFileListFilter();
if (StringUtils.hasText(this.filenamePattern)) {
PatternMatchingFtpFileListFilter patternMatchingFTPFileListFilter = new PatternMatchingFtpFileListFilter();
patternMatchingFTPFileListFilter.setPattern(Pattern.compile(this.filenamePattern));
compositeFtpFileListFilter.addFilter(patternMatchingFTPFileListFilter);
}
if (this.filter != null) {
compositeFtpFileListFilter.addFilter(this.filter);
}
this.ftpInboundSynchronizer.setFilter(compositeFtpFileListFilter);
if (this.taskScheduler == null) {
Map<String, TaskScheduler> tss = null;
if ((tss = applicationContext.getBeansOfType(TaskScheduler.class)).keySet().size() != 0) {
taskScheduler = tss.get(tss.keySet().iterator().next());
}
}
if (null == taskScheduler) {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(10);
threadPoolTaskScheduler.setErrorHandler(new ErrorHandler() {
public void handleError(Throwable t) {
logger.debug("Error! ", t);
}
});
threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
threadPoolTaskScheduler.initialize();
this.taskScheduler = threadPoolTaskScheduler;
}
DefaultFtpClientFactory defaultFtpClientFactory = new DefaultFtpClientFactory();
defaultFtpClientFactory.setHost(this.host);
defaultFtpClientFactory.setPassword(this.password);
defaultFtpClientFactory.setPort(this.port);
defaultFtpClientFactory.setRemoteWorkingDirectory(this.remoteDirectory);
defaultFtpClientFactory.setUsername(this.username);
defaultFtpClientFactory.setClientMode(this.clientMode);
QueuedFtpClientPool queuedFtpClientPool = new QueuedFtpClientPool(15, defaultFtpClientFactory);
this.ftpInboundSynchronizer.setClientPool(queuedFtpClientPool);
this.ftpInboundSynchronizer.setLocalDirectory(this.localDirectoryResource);
this.ftpInboundSynchronizer.setTaskScheduler(this.taskScheduler);
assert this.localDirectoryResource != null : "the 'localDirectoryResource' can't be null at this point";
if (this.autoCreateDirectories) {
if (!this.localDirectoryResource.exists()) {
try {
if (!localDirectoryResource.getFile().mkdirs()) {
logger.debug("attempted to ensure the existence of the local directory '" + this.localDirectoryResource.getFile().getAbsolutePath() + "' but didn't succeed");
}
} catch (Throwable th) {
logger.debug("attempted to ensure the existence of the local directory '" + this.localDirectoryResource.getFile().getAbsolutePath() + "' but didn't succeed");
}
}
}
this.ftpInboundSynchronizer.afterPropertiesSet();
FtpFileSource ftpFileSource = new FtpFileSource(this.fileReadingMessageSource, this.ftpInboundSynchronizer);
ftpFileSource.setClientPool(queuedFtpClientPool);
ftpFileSource.setLocalWorkingDirectory(this.localDirectoryResource);
ftpFileSource.setSynchronizer(this.ftpInboundSynchronizer);
ftpFileSource.setTaskScheduler(this.taskScheduler);
ftpFileSource.setFileSource(this.fileReadingMessageSource);
ftpFileSource.afterPropertiesSet();
ftpFileSource.start();
return ftpFileSource;
}
}

View File

@@ -1,51 +0,0 @@
package org.springframework.integration.ftp;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.util.regex.Pattern;
/**
* Validates {@link org.apache.commons.net.ftp.FTPFile}s against a {@link java.util.regex.Pattern}.
* Patterned very much like {@link org.springframework.integration.file.PatternMatchingFileListFilter}.
*
* @author Josh Long
*/
public class PatternMatchingFtpFileListFilter extends AbstractFtpFileListFilter implements InitializingBean {
private Log logger = LogFactory.getLog(getClass());
private Pattern pattern;
private String patternExpression;
public void setPattern(Pattern pattern) {
this.pattern = pattern;
}
public void setPatternExpression(String patternExpression) {
this.patternExpression = patternExpression;
}
@Override
public boolean accept(FTPFile ftpFile) {
if (logger.isDebugEnabled()) {
logger.debug("testing: " + ToStringBuilder.reflectionToString(ftpFile));
}
return (ftpFile != null) && this.pattern.matcher(ftpFile.getName()).matches();
}
public void afterPropertiesSet() throws Exception {
if (StringUtils.hasText(this.patternExpression) && (this.pattern == null)) {
this.pattern = Pattern.compile(this.patternExpression);
}
Assert.notNull(this.pattern, "the pattern must not be null");
}
}

View File

@@ -23,6 +23,8 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.ftp.FtpSendingMessageHandlerFactoryBean;
import org.springframework.integration.ftp.impl.FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean;
import org.w3c.dom.Element;
import java.util.HashMap;
@@ -57,7 +59,7 @@ public class FtpNamespaceHandler extends NamespaceHandlerSupport {
private static class FTPMessageSendingConsumerBeanDefinitionParser extends AbstractOutboundChannelAdapterParser {
@Override
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(PACKAGE_NAME + ".FtpSendingMessageHandlerFactoryBean");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FtpSendingMessageHandlerFactoryBean.class.getName());
for (String p : "auto-create-directories,username,port,password,host,key-file,key-file-password,remote-directory".split(",")) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, p);
@@ -79,17 +81,20 @@ public class FtpNamespaceHandler extends NamespaceHandlerSupport {
@Override
@SuppressWarnings("unused")
protected String parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(PACKAGE_NAME + ".FtpMessageSourceFactoryBean");
// reference
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean.class.getName());
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element,"filter");
for (String p : ("filename-pattern,auto-create-directories,username,password,host,port," + "remote-directory,local-working-directory").split(",")) {
for (String p : ("auto-delete-remote-files-on-sync,filename-pattern,auto-create-directories,username,password,host,port," +
"remote-directory,local-working-directory").split(",")) {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, p);
}
int clientMode = CLIENT_MODES.get(element.getAttribute("client-mode"));
builder.addPropertyValue("clientMode", clientMode);
return BeanDefinitionReaderUtils.registerWithGeneratedName(builder.getBeanDefinition(), parserContext.getRegistry());
}
}

View File

@@ -0,0 +1,118 @@
package org.springframework.integration.ftp.impl;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.core.io.Resource;
import org.springframework.integration.MessagingException;
import org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer;
import org.springframework.integration.file.AbstractInboundRemoteFileSystemSynchronizingMessageSource;
import org.springframework.integration.ftp.FtpClientPool;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.util.Assert;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
/**
* An FTP-adapter implementation of {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer}
*
* @author Josh Long
*/
public class FtpInboundRemoteFileSystemSynchronizer extends AbstractInboundRemoteFileSystemSychronizer<FTPFile> {
protected FtpClientPool clientPool;
@Override
protected void onInit() throws Exception {
Assert.notNull(this.clientPool, "clientPool can't be null");
if (this.shouldDeleteSourceFile) {
this.entryAcknowledgmentStrategy = new DeletionEntryAcknowledgmentStrategy();
}
}
/**
* The {@link org.springframework.integration.ftp.FtpClientPool} that holds references to {@link org.apache.commons.net.ftp.FTPClient} instances
*
* @param clientPool the {@link org.springframework.integration.ftp.FtpClientPool}
*/
public void setClientPool(FtpClientPool clientPool) {
this.clientPool = clientPool;
}
protected boolean copyFileToLocalDirectory(FTPClient client, FTPFile ftpFile, Resource localDirectory)
throws IOException, FileNotFoundException {
String remoteFileName = ftpFile.getName();
String localFileName = localDirectory.getFile().getPath() + "/" + remoteFileName;
File localFile = new File(localFileName);
if (!localFile.exists()) {
String tempFileName = localFileName + AbstractInboundRemoteFileSystemSynchronizingMessageSource.INCOMPLETE_EXTENSION;
File file = new File(tempFileName);
FileOutputStream fos = new FileOutputStream(file);
try {
client.retrieveFile(remoteFileName, fos);
// Perhaps we have some dispatch of hte source file to do?
acknowledge(client, ftpFile);
} catch (Throwable th) {
throw new RuntimeException(th);
} finally {
fos.close();
}
file.renameTo(localFile);
return true;
} else {
return false;
}
}
@Override
protected void syncRemoteToLocalFileSystem() {
try {
FTPClient client = this.clientPool.getClient();
Assert.state(client != null, FtpClientPool.class.getSimpleName() + " returned a 'null' client. " + "This most likely a bug in the pool implementation.");
Collection<FTPFile> fileList = this.filter.filterEntries(client.listFiles());
try {
for (FTPFile ftpFile : fileList) {
if ((ftpFile != null) && ftpFile.isFile()) {
copyFileToLocalDirectory(client, ftpFile, this.localDirectory);
}
}
} finally {
this.clientPool.releaseClient(client);
}
} catch (IOException e) {
throw new MessagingException("Problem occurred while synchronizing remote to local directory", e);
}
}
@Override
protected Trigger getTrigger() {
return new PeriodicTrigger(10 * 1000);
}
/**
* An ackowledgment strategy that deletes
*/
class DeletionEntryAcknowledgmentStrategy implements AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy<FTPFile> {
public void acknowledge(Object useful, FTPFile msg)
throws Exception {
FTPClient ftpClient = (FTPClient) useful;
if ((msg != null) && ftpClient.deleteFile(msg.getName())) {
if (logger.isDebugEnabled()) {
logger.debug("deleted " + msg.getName());
}
}
}
}
}

View File

@@ -0,0 +1,35 @@
package org.springframework.integration.ftp.impl;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.integration.file.AbstractInboundRemoteFileSystemSynchronizingMessageSource;
import org.springframework.integration.ftp.FtpClientPool;
/**
* a {@link org.springframework.integration.core.MessageSource} implementation for FTP
*
* @author Josh Long
*/
public class FtpInboundRemoteFileSystemSynchronizingMessageSource extends AbstractInboundRemoteFileSystemSynchronizingMessageSource<FTPFile, FtpInboundRemoteFileSystemSynchronizer> {
private volatile FtpClientPool clientPool;
public void setClientPool(FtpClientPool clientPool) {
this.clientPool = clientPool;
}
@Override
protected void doStart() {
this.synchronizer.start();
}
@Override
protected void doStop() {
this.synchronizer.stop();
}
@Override
protected void onInit() throws Exception {
super.onInit();
this.synchronizer.setClientPool(this.clientPool);
}
}

View File

@@ -0,0 +1,174 @@
package org.springframework.integration.ftp.impl;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceEditor;
import org.springframework.core.io.ResourceLoader;
import org.springframework.integration.file.entries.CompositeEntryListFilter;
import org.springframework.integration.file.entries.EntryListFilter;
import org.springframework.integration.file.entries.PatternMatchingEntryListFilter;
import org.springframework.integration.ftp.DefaultFtpClientFactory;
import org.springframework.integration.ftp.FtpFileEntryNamer;
import org.springframework.integration.ftp.QueuedFtpClientPool;
import org.springframework.util.StringUtils;
import java.io.File;
/**
* Factory to make building the namespace easier
*
* @author Josh Long
*/
public class FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends AbstractFactoryBean<FtpInboundRemoteFileSystemSynchronizingMessageSource> implements ResourceLoaderAware {
private volatile String port;
private volatile String autoCreateDirectories;
private volatile String filenamePattern;
private volatile String username;
private volatile String password;
private volatile String host;
private volatile String remoteDirectory;
private volatile String localWorkingDirectory;
private volatile ResourceLoader resourceLoader;
private volatile Resource localDirectoryResource;
private volatile EntryListFilter<FTPFile> filter;
private volatile int clientMode = FTPClient.ACTIVE_LOCAL_DATA_CONNECTION_MODE;
private volatile String autoDeleteRemoteFilesOnSync;
@SuppressWarnings("unused")
public void setAutoDeleteRemoteFilesOnSync(String autoDeleteRemoteFilesOnSync) {
this.autoDeleteRemoteFilesOnSync = autoDeleteRemoteFilesOnSync;
}
@Override
public Class<?> getObjectType() {
return FtpInboundRemoteFileSystemSynchronizingMessageSource.class;
}
private Resource fromText(String path) {
ResourceEditor resourceEditor = new ResourceEditor(this.resourceLoader);
resourceEditor.setAsText(path);
return (Resource) resourceEditor.getValue();
}
private DefaultFtpClientFactory defaultFtpClientFactory() {
DefaultFtpClientFactory defaultFtpClientFactory = new DefaultFtpClientFactory();
defaultFtpClientFactory.setHost(this.host);
defaultFtpClientFactory.setPassword(this.password);
defaultFtpClientFactory.setPort(Integer.parseInt(this.port));
defaultFtpClientFactory.setRemoteWorkingDirectory(this.remoteDirectory);
defaultFtpClientFactory.setUsername(this.username);
defaultFtpClientFactory.setClientMode(this.clientMode);
return defaultFtpClientFactory;
}
@Override
protected FtpInboundRemoteFileSystemSynchronizingMessageSource createInstance()
throws Exception {
boolean autoCreatDirs = Boolean.parseBoolean(this.autoCreateDirectories);
boolean ackRemoteDir = Boolean.parseBoolean(this.autoDeleteRemoteFilesOnSync);
FtpInboundRemoteFileSystemSynchronizingMessageSource ftpRemoteFileSystemSynchronizingMessageSource = new FtpInboundRemoteFileSystemSynchronizingMessageSource();
ftpRemoteFileSystemSynchronizingMessageSource.setAutoCreateDirectories(autoCreatDirs);
if (!StringUtils.hasText(this.localWorkingDirectory)) {
File tmp = new File(SystemUtils.getJavaIoTmpDir(), "ftpInbound");
this.localWorkingDirectory = "file://" + tmp.getAbsolutePath();
}
this.localDirectoryResource = this.fromText(this.localWorkingDirectory);
FtpFileEntryNamer ftpFileEntryNamer = new FtpFileEntryNamer();
CompositeEntryListFilter<FTPFile> compositeFtpFileListFilter = new CompositeEntryListFilter<FTPFile>();
if (StringUtils.hasText(this.filenamePattern)) {
PatternMatchingEntryListFilter<FTPFile> ftpFilePatternMatchingEntryListFilter = new PatternMatchingEntryListFilter<FTPFile>(ftpFileEntryNamer, filenamePattern);
compositeFtpFileListFilter.addFilter(ftpFilePatternMatchingEntryListFilter);
}
if (this.filter != null) {
compositeFtpFileListFilter.addFilter(this.filter);
}
QueuedFtpClientPool queuedFtpClientPool = new QueuedFtpClientPool(15, defaultFtpClientFactory());
FtpInboundRemoteFileSystemSynchronizer ftpRemoteFileSystemSynchronizer = new FtpInboundRemoteFileSystemSynchronizer();
ftpRemoteFileSystemSynchronizer.setClientPool(queuedFtpClientPool);
ftpRemoteFileSystemSynchronizer.setLocalDirectory(this.localDirectoryResource);
ftpRemoteFileSystemSynchronizer.setShouldDeleteSourceFile(ackRemoteDir);
ftpRemoteFileSystemSynchronizer.setFilter(compositeFtpFileListFilter);
ftpRemoteFileSystemSynchronizingMessageSource.setRemotePredicate(compositeFtpFileListFilter);
ftpRemoteFileSystemSynchronizingMessageSource.setSynchronizer(ftpRemoteFileSystemSynchronizer);
ftpRemoteFileSystemSynchronizingMessageSource.setClientPool(queuedFtpClientPool);
ftpRemoteFileSystemSynchronizingMessageSource.setLocalDirectory(this.localDirectoryResource);
ftpRemoteFileSystemSynchronizingMessageSource.setBeanFactory(this.getBeanFactory());
ftpRemoteFileSystemSynchronizingMessageSource.setAutoStartup(true);
ftpRemoteFileSystemSynchronizingMessageSource.afterPropertiesSet();
ftpRemoteFileSystemSynchronizingMessageSource.start();
return ftpRemoteFileSystemSynchronizingMessageSource;
}
@SuppressWarnings("unused")
public void setPort(String port) {
this.port = port;
}
@SuppressWarnings("unused")
public void setAutoCreateDirectories(String autoCreateDirectories) {
this.autoCreateDirectories = autoCreateDirectories;
}
@SuppressWarnings("unused")
public void setFilenamePattern(String filenamePattern) {
this.filenamePattern = filenamePattern;
}
@SuppressWarnings("unused")
public void setUsername(String username) {
this.username = username;
}
@SuppressWarnings("unused")
public void setPassword(String password) {
this.password = password;
}
@SuppressWarnings("unused")
public void setHost(String host) {
this.host = host;
}
@SuppressWarnings("unused")
public void setRemoteDirectory(String remoteDirectory) {
this.remoteDirectory = remoteDirectory;
}
@SuppressWarnings("unused")
public void setLocalWorkingDirectory(String localWorkingDirectory) {
this.localWorkingDirectory = localWorkingDirectory;
}
@SuppressWarnings("unused")
public void setFilter(EntryListFilter<FTPFile> filter) {
this.filter = filter;
}
@SuppressWarnings("unused")
public void setClientMode(int clientMode) {
this.clientMode = clientMode;
}
@SuppressWarnings("unused")
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}

View File

@@ -138,7 +138,7 @@
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.ftp.FtpFileListFilter"/>
<tool:expected-type type="org.springframework.integration.file.entries.EntryListFilter"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>

View File

@@ -29,5 +29,6 @@ public class InboundFtpFileServiceActivator {
public static void main(String[] args) throws Throwable {
ClassPathXmlApplicationContext classPathXmlApplicationContext =
new ClassPathXmlApplicationContext("inbound-ftp-context.xml");
classPathXmlApplicationContext.start();
}
}

View File

@@ -20,7 +20,7 @@
filename-pattern=".*?jpg"
>
<int:poller>
<int:interval-trigger interval="10000" time-unit="MILLISECONDS"/>
<int:interval-trigger interval="1000" time-unit="MILLISECONDS"/>
</int:poller>
</ftp:inbound-channel-adapter>

View File

@@ -1,45 +0,0 @@
/*
* Copyright 2010 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp;
import com.jcraft.jsch.ChannelSftp;
import java.util.ArrayList;
import java.util.List;
/**
* Convenience implementation patterned off {@link org.springframework.integration.file.FileListFilter}
*
* @author Josh Long
*/
public abstract class AbstractSftpFileListFilter implements SftpFileListFilter {
abstract public boolean accept(ChannelSftp.LsEntry lsEntry);
public List<ChannelSftp.LsEntry> filterFiles(ChannelSftp.LsEntry[] files) {
List<ChannelSftp.LsEntry> accepted = new ArrayList<ChannelSftp.LsEntry>();
if (files != null) {
for (ChannelSftp.LsEntry lsEntry : files)
if (this.accept(lsEntry)) {
accepted.add(lsEntry);
}
}
return accepted;
}
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright 2010 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp;
import com.jcraft.jsch.ChannelSftp;
import org.springframework.util.Assert;
import java.util.*;
/**
* Patterned very much on the {@link org.springframework.integration.file.CompositeFileListFilter}
*
* @author Josh Long
*/
public class CompositeFtpFileListFilter implements SftpFileListFilter {
private Set<SftpFileListFilter> filters;
public CompositeFtpFileListFilter(SftpFileListFilter... ftpFileListFilter) {
this.filters = new LinkedHashSet<SftpFileListFilter>(Arrays.asList(ftpFileListFilter));
}
public CompositeFtpFileListFilter(Collection<SftpFileListFilter> ftpFileListFilter) {
this.filters = new LinkedHashSet<SftpFileListFilter>(ftpFileListFilter);
}
public void addFilter(SftpFileListFilter ftpFileListFilter) {
this.filters.add(ftpFileListFilter);
}
public List<ChannelSftp.LsEntry> filterFiles(ChannelSftp.LsEntry[] files) {
Assert.notNull(files, "files[] can't be null!");
List<ChannelSftp.LsEntry> leftOver = Arrays.asList(files);
for (SftpFileListFilter ff : this.filters)
leftOver = ff.filterFiles(leftOver.toArray(new ChannelSftp.LsEntry[leftOver.size()]));
return leftOver;
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright 2010 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp;
import com.jcraft.jsch.ChannelSftp;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.util.regex.Pattern;
/**
* Validates {@link com.jcraft.jsch.ChannelSftp.LsEntry}s against a {@link java.util.regex.Pattern}.
* Patterned very much like {@link org.springframework.integration.file.PatternMatchingFileListFilter}.
*
* @author Josh Long
*/
public class PatternMatchingSftpFileListFilter extends AbstractSftpFileListFilter implements InitializingBean {
private Log logger = LogFactory.getLog(getClass());
private Pattern pattern;
private String patternExpression;
public void setPattern(Pattern pattern) {
this.pattern = pattern;
}
public void setPatternExpression(String patternExpression) {
this.patternExpression = patternExpression;
}
@Override
public boolean accept(ChannelSftp.LsEntry lsEntry) {
if (logger.isDebugEnabled()) {
logger.debug("testing: " + ToStringBuilder.reflectionToString(lsEntry));
}
return (lsEntry != null) && this.pattern.matcher(lsEntry.getFilename()).matches();
}
public void afterPropertiesSet() throws Exception {
if (StringUtils.hasText(this.patternExpression) && (this.pattern == null)) {
this.pattern = Pattern.compile(this.patternExpression);
}
Assert.notNull(this.pattern, "the pattern must not be null");
}
}

View File

@@ -0,0 +1,16 @@
package org.springframework.integration.sftp;
import com.jcraft.jsch.ChannelSftp;
import org.springframework.integration.file.entries.EntryNamer;
/**
* Knows how to name a {@link com.jcraft.jsch.ChannelSftp.LsEntry} instance
*
* @author Josh Long
*/
public class SftpEntryNamer implements EntryNamer<ChannelSftp.LsEntry>{
public String nameOf(ChannelSftp.LsEntry entry) {
return entry.getFilename() ;
}
}

View File

@@ -1,30 +0,0 @@
/*
* Copyright 2010 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp;
import com.jcraft.jsch.ChannelSftp;
import java.util.List;
/**
* Filters out all the {@link com.jcraft.jsch.ChannelSftp.LsEntry} taken in a scan of the remote mount
* and returns the balance. These are then sync'd to the local directory.
*
* @author Josh Long
*/
public interface SftpFileListFilter {
List<ChannelSftp.LsEntry> filterFiles (ChannelSftp.LsEntry [] files);
}

View File

@@ -1,280 +0,0 @@
/*
* Copyright 2010 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpATTRS;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.integration.MessagingException;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.util.Assert;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
/**
* This handles keeping the {@link #localDirectory} in sync with the contents of the remote mount. From there, files are deposited
* into a folder where the {@link org.springframework.integration.file.FileReadingMessageSource} will eventually deliver them as events
*
* @author Josh Long
* @author Mario Gray
*/
public class SftpInboundSynchronizer implements InitializingBean {
private static final long DEFAULT_REFRESH_RATE = 10 * 1000; // 10 seconds
// a lot of the approach for this (including the use of a FileReadingMessageSource and the regex / mask approach were lifted from FtpInboundSynchronizer
static final String INCOMPLETE_EXTENSION = ".INCOMPLETE";
private Log logger = LogFactory.getLog(getClass());
private volatile Resource localDirectory;
private volatile SftpSessionPool pool;
private volatile ScheduledFuture<?> scheduledFuture;
private volatile String remotePath;
private volatile TaskScheduler taskScheduler;
private volatile Trigger trigger = new PeriodicTrigger(DEFAULT_REFRESH_RATE);
private volatile boolean autoCreatePath;
private volatile boolean running;
private SftpFileListFilter filter;
public void setFilter(SftpFileListFilter filter) {
this.filter = filter;
}
private volatile boolean shouldDeleteDownloadedRemoteFiles; //.. this is false
private SftpFileListFilter acceptAllFilteListFilter = new SftpFileListFilter(){
public List<ChannelSftp.LsEntry> filterFiles(ChannelSftp.LsEntry[] files) {
return Arrays.asList( files);
}
} ;
public void afterPropertiesSet() throws Exception {
Assert.state(taskScheduler != null, "taskScheduler can't be null!");
Assert.state(localDirectory != null, "the localDirectory property must not be null!");
File localDir = localDirectory.getFile();
if (!localDir.exists()) {
if (autoCreatePath) {
if (!localDir.mkdirs()) {
throw new RuntimeException(String.format("couldn't create localDirectory %s", this.localDirectory.getFile().getAbsolutePath()));
}
}
}
if(this.filter == null)
this.filter = acceptAllFilteListFilter;
}
public boolean isRunning() {
return running;
}
public boolean isShouldDeleteDownloadedRemoteFiles() {
return shouldDeleteDownloadedRemoteFiles;
}
public void setAutoCreatePath(boolean autoCreatePath) {
this.autoCreatePath = autoCreatePath;
}
public void setLocalDirectory(Resource localDirectory) {
this.localDirectory = localDirectory;
}
public void setPool(SftpSessionPool pool) {
this.pool = pool;
}
public void setRemotePath(String remotePath) {
this.remotePath = remotePath;
}
public void setScheduledFuture(ScheduledFuture<?> scheduledFuture) {
this.scheduledFuture = scheduledFuture;
}
public void setShouldDeleteDownloadedRemoteFiles(boolean shouldDeleteDownloadedRemoteFiles) {
this.shouldDeleteDownloadedRemoteFiles = shouldDeleteDownloadedRemoteFiles;
}
public void setTaskScheduler(TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
public void setTrigger(Trigger t) {
this.trigger = t;
}
public void start() {
if (running) {
return;
}
Assert.state(checkThatRemotePathExists(remotePath), "the remotePath should exist before we can sync with it!");
Assert.state(taskScheduler != null, "'taskScheduler' is required");
scheduledFuture = taskScheduler.schedule(new SynchronizeTask(), trigger);
this.running = true;
}
public void stop() {
if (!running) {
return;
}
Assert.state(scheduledFuture != null, "scheduledFuture is null!");
this.scheduledFuture.cancel(true);
this.running = false;
}
@SuppressWarnings("unchecked")
public void synchronize() throws Exception {
SftpSession session = null;
try {
session = pool.getSession();
session.start();
ChannelSftp channelSftp = session.getChannel();
Collection<ChannelSftp.LsEntry> beforeFilter = channelSftp.ls(remotePath);
ChannelSftp.LsEntry [] entries = beforeFilter == null? new ChannelSftp.LsEntry[0] :
beforeFilter.toArray(new ChannelSftp.LsEntry[ beforeFilter.size()]) ;
Collection<ChannelSftp.LsEntry> files = this.filter.filterFiles( entries );
for (ChannelSftp.LsEntry lsEntry : files) {
if ((lsEntry != null) && !lsEntry.getAttrs().isDir() && !lsEntry.getAttrs().isLink()) {
copyFromRemoteToLocalDirectory(session, lsEntry, this.localDirectory);
}
}
} catch (IOException e) {
throw new MessagingException("couldn't synchronize remote to local directory", e);
} finally {
if ((session != null) && (pool != null)) {
pool.release(session);
}
}
}
/**
* there be dragons this way ... This method will check to ensure that the remote directory exists. If the directory
* doesnt exist, and autoCreatePath is 'true,' then this method makes a few reasonably sane attempts
* to create it. Otherwise, it fails fast.
*
* @param remotePath the path on the remote SSH / SFTP server to create.
* @return whether or not the directory is there (regardless of whether we created it in this method or it already
* existed.)
*/
private boolean checkThatRemotePathExists(String remotePath) {
SftpSession session = null;
ChannelSftp channelSftp = null;
try {
session = pool.getSession();
Assert.state(session != null, "session as returned from the pool should not be null. " + "If it is, it is most likely an error in the pool implementation. ");
session.start();
channelSftp = session.getChannel();
SftpATTRS attrs = channelSftp.stat(remotePath);
assert (attrs != null) && attrs.isDir() : "attrs can't be null, and should indicate that it's a directory!";
return true;
} catch (Throwable th) {
if (this.autoCreatePath && (pool != null) && (session != null)) {
try {
if (channelSftp != null) {
channelSftp.mkdir(remotePath);
if (channelSftp.stat(remotePath).isDir()) {
return true;
}
}
} catch (Throwable t) {
return false;
}
}
} finally {
if ((pool != null) && (session != null)) {
pool.release(session);
}
}
return false;
}
@SuppressWarnings("ignored")
private boolean copyFromRemoteToLocalDirectory(SftpSession sftpSession, ChannelSftp.LsEntry entry, Resource localDir)
throws Exception {
File fileForLocalDir = localDir.getFile();
File localFile = new File(fileForLocalDir, entry.getFilename());
if (!localFile.exists()) {
InputStream in = null;
FileOutputStream fos = null;
try {
File tmpLocalTarget = new File(localFile.getAbsolutePath() + INCOMPLETE_EXTENSION);
fos = new FileOutputStream(tmpLocalTarget);
String remoteFqPath = this.remotePath + "/" + entry.getFilename();
in = sftpSession.getChannel().get(remoteFqPath);
IOUtils.copy(in, fos);
if (tmpLocalTarget.renameTo(localFile)) {
// last step
if (isShouldDeleteDownloadedRemoteFiles()) {
sftpSession.getChannel().rm(remoteFqPath);
}
}
return true;
} catch (Throwable th) {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(fos);
}
} else {
return true;
}
return false;
}
class SynchronizeTask implements Runnable {
public void run() {
try {
synchronize();
} catch (Throwable e) {
// todo logger.debug("couldn't invoke synchronize()", e);
}
}
}
}

View File

@@ -1,126 +0,0 @@
/*
* Copyright 2010 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.Lifecycle;
import org.springframework.core.io.Resource;
import org.springframework.integration.Message;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.file.AcceptOnceFileListFilter;
import org.springframework.integration.file.CompositeFileListFilter;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.file.PatternMatchingFileListFilter;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import java.io.File;
import java.io.IOException;
import java.util.regex.Pattern;
/**
* this creates the message source that ultimately 'see's files on a local directory and forwards them on to the bus.
* These files are asynchronously deposited into a folder via the SFTP synchronizer. This code is <i>very</i> influenced
* by the FtpFileSource class from the Spring Integration FTP adapter.
*
* @author Josh Long
*/
public class SftpMessageSource implements MessageSource<File>, InitializingBean, Lifecycle {
private FileReadingMessageSource fileReadingMessageSource;
private Resource localDirectory;
private SftpInboundSynchronizer synchronizer;
private TaskScheduler taskScheduler;
private Trigger trigger;
public SftpMessageSource(FileReadingMessageSource fileSource, SftpInboundSynchronizer synchronizer) {
this.fileReadingMessageSource = fileSource;
this.synchronizer = synchronizer;
Pattern completePattern = Pattern.compile("^.*(?<!" + SftpInboundSynchronizer.INCOMPLETE_EXTENSION + ")$");
fileReadingMessageSource.setFilter(new CompositeFileListFilter(new AcceptOnceFileListFilter(), new PatternMatchingFileListFilter(completePattern)));
}
public void afterPropertiesSet() throws Exception {
synchronizer.afterPropertiesSet();
this.fileReadingMessageSource.afterPropertiesSet();
}
public FileReadingMessageSource getFileReadingMessageSource() {
return fileReadingMessageSource;
}
public Resource getLocalDirectory() {
return localDirectory;
}
public SftpInboundSynchronizer getSynchronizer() {
return synchronizer;
}
public TaskScheduler getTaskScheduler() {
return taskScheduler;
}
public Trigger getTrigger() {
return trigger;
}
public boolean isRunning() {
return this.synchronizer.isRunning();
}
public Message<File> receive() {
return this.fileReadingMessageSource.receive();
}
public void setFileReadingMessageSource(final FileReadingMessageSource fileReadingMessageSource) {
this.fileReadingMessageSource = fileReadingMessageSource;
}
public void setLocalDirectory(final Resource localDirectory) {
this.localDirectory = localDirectory;
try {
this.fileReadingMessageSource.setDirectory(localDirectory.getFile());
} catch (IOException e) {
}
this.synchronizer.setLocalDirectory(localDirectory);
}
public void setSynchronizer(final SftpInboundSynchronizer synchronizer) {
this.synchronizer = synchronizer;
}
public void setTaskScheduler(final TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
synchronizer.setTaskScheduler(taskScheduler);
}
public void setTrigger(final Trigger trigger) {
this.trigger = trigger;
this.synchronizer.setTrigger(trigger);
}
public void start() {
synchronizer.start();
}
public void stop() {
synchronizer.stop();
}
}

View File

@@ -1,290 +0,0 @@
/*
* Copyright 2010 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.sftp.config;
import org.apache.commons.lang.SystemUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceEditor;
import org.springframework.core.io.ResourceLoader;
import org.springframework.integration.file.FileReadingMessageSource;
import org.springframework.integration.sftp.*;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.ErrorHandler;
import org.springframework.util.StringUtils;
import java.io.File;
import java.util.Map;
/**
* Building a {@link org.springframework.integration.sftp.SftpMessageSource} is a complicated because we also
* use a {@link org.springframework.integration.file.FileReadingMessageSource} to handle the "receipt" of files in
* a {@link #localWorkingDirectory}.
*
* @author Josh Long
*/
public class SftpMessageSourceFactoryBean extends AbstractFactoryBean<SftpMessageSource> implements ApplicationContextAware, ResourceLoaderAware {
private ApplicationContext applicationContext;
private FileReadingMessageSource fileReadingMessageSource;
private Resource localDirectoryResource;
private ResourceLoader resourceLoader;
private SftpInboundSynchronizer synchronizer;
private String host;
private String keyFile;
private String keyFilePassword;
private String localWorkingDirectory;
private String password;
private String remoteDirectory;
private String username;
private TaskScheduler taskScheduler;
private Trigger trigger;
private boolean autoCreateDirectories;
private boolean autoDeleteRemoteFilesOnSync;
private int port = 22;
private SftpFileListFilter filter;
private String filenamePattern;
public FileReadingMessageSource getFileReadingMessageSource() {
return fileReadingMessageSource;
}
public String getHost() {
return host;
}
public String getKeyFile() {
return keyFile;
}
public String getKeyFilePassword() {
return keyFilePassword;
}
public String getLocalWorkingDirectory() {
return localWorkingDirectory;
}
@Override
public Class<?extends SftpMessageSource> getObjectType() {
return SftpMessageSource.class;
}
public String getPassword() {
return password;
}
public int getPort() {
return port;
}
public String getRemoteDirectory() {
return remoteDirectory;
}
public SftpInboundSynchronizer getSynchronizer() {
return synchronizer;
}
public TaskScheduler getTaskScheduler() {
return taskScheduler;
}
public Trigger getTrigger() {
return trigger;
}
// this is the ultimate layer of control
// users will configure theeir entire experience using this class and trust that a working
// component comes out as a result of their input
// we need to support user/pw/keys/host/port/auto-delete properties
public String getUsername() {
return username;
}
public boolean isAutoCreateDirectories() {
return autoCreateDirectories;
}
public boolean isAutoDeleteRemoteFilesOnSync() {
return autoDeleteRemoteFilesOnSync;
}
public void setApplicationContext(final ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
public void setAutoCreateDirectories(final boolean autoCreateDirectories) {
this.autoCreateDirectories = autoCreateDirectories;
}
public void setAutoDeleteRemoteFilesOnSync(final boolean autoDeleteRemoteFilesOnSync) {
this.autoDeleteRemoteFilesOnSync = autoDeleteRemoteFilesOnSync;
}
public void setFileReadingMessageSource(final FileReadingMessageSource fileReadingMessageSource) {
this.fileReadingMessageSource = fileReadingMessageSource;
}
public void setHost(final String host) {
this.host = host;
}
public void setKeyFile(final String keyFile) {
this.keyFile = keyFile;
}
public void setKeyFilePassword(final String keyFilePassword) {
this.keyFilePassword = keyFilePassword;
}
public void setLocalWorkingDirectory(final String lwd) {
this.localWorkingDirectory = lwd;
}
public void setPassword(final String password) {
this.password = password;
}
public void setPort(final int port) {
this.port = port;
}
public void setRemoteDirectory(final String remoteDirectory) {
this.remoteDirectory = remoteDirectory;
}
public void setResourceLoader(final ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public void setSynchronizer(final SftpInboundSynchronizer synchronizer) {
this.synchronizer = synchronizer;
}
public void setTaskScheduler(final TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
public void setTrigger(final Trigger trigger) {
this.trigger = trigger;
}
public void setFilenamePattern(String filenamePattern) {
this.filenamePattern = filenamePattern;
}
public void setFilter(SftpFileListFilter filter) {
this.filter = filter;
}
public void setUsername(final String username) {
this.username = username;
}
@Override
protected SftpMessageSource createInstance() throws Exception {
try {
if ((localWorkingDirectory == null) || !StringUtils.hasText(localWorkingDirectory)) {
File tmp = SystemUtils.getJavaIoTmpDir();
File sftpTmp = new File(tmp, "sftpInbound");
this.localWorkingDirectory = "file://" + sftpTmp.getAbsolutePath();
}
// resource for local directory
ResourceEditor editor = new ResourceEditor(this.resourceLoader);
editor.setAsText(this.localWorkingDirectory);
this.localDirectoryResource = (Resource) editor.getValue();
fileReadingMessageSource = new FileReadingMessageSource();
synchronizer = new SftpInboundSynchronizer();
CompositeFtpFileListFilter compositeFtpFileListFilter = new CompositeFtpFileListFilter();
if (StringUtils.hasText(this.filenamePattern)) {
PatternMatchingSftpFileListFilter flp = new PatternMatchingSftpFileListFilter();
flp.setPatternExpression(this.filenamePattern);
flp.afterPropertiesSet();
compositeFtpFileListFilter.addFilter(flp);
}
if (this.filter != null) {
compositeFtpFileListFilter.addFilter(this.filter);
}
synchronizer.setFilter(compositeFtpFileListFilter);
if (null == taskScheduler) {
Map<String, TaskScheduler> tss = null;
if ((tss = applicationContext.getBeansOfType(TaskScheduler.class)).keySet().size() != 0) {
taskScheduler = tss.get(tss.keySet().iterator().next());
}
}
if (null == taskScheduler) {
ThreadPoolTaskScheduler ts = new ThreadPoolTaskScheduler();
ts.setPoolSize(10);
ts.setErrorHandler(new ErrorHandler() {
public void handleError(Throwable t) {
// todo make this forward a message onto the error channel (how does that work?)
logger.debug("error! ", t);
}
});
ts.setWaitForTasksToCompleteOnShutdown(true);
ts.initialize();
this.taskScheduler = ts;
}
SftpSessionFactory sessionFactory = SftpSessionUtils.buildSftpSessionFactory(
this.getHost(), this.getPassword(), this.getUsername(), this.getKeyFile(), this.getKeyFilePassword(), this.getPort());
QueuedSftpSessionPool pool = new QueuedSftpSessionPool(15, sessionFactory);
pool.afterPropertiesSet();
synchronizer.setRemotePath(this.getRemoteDirectory());
synchronizer.setPool(pool);
synchronizer.setAutoCreatePath(this.isAutoCreateDirectories());
synchronizer.setShouldDeleteDownloadedRemoteFiles(this.isAutoDeleteRemoteFilesOnSync());
SftpMessageSource sftpMessageSource = new SftpMessageSource(fileReadingMessageSource, synchronizer);
sftpMessageSource.setTaskScheduler(taskScheduler);
if (null != this.trigger) {
sftpMessageSource.setTrigger(trigger);
}
sftpMessageSource.setLocalDirectory(this.localDirectoryResource);
sftpMessageSource.afterPropertiesSet();
sftpMessageSource.start();
return sftpMessageSource;
} catch (Throwable thr) {
logger.debug("error occurred when trying to configure SFTPmessageSource ", thr);
}
return null;
}
}

View File

@@ -23,6 +23,8 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser;
import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.springframework.integration.sftp.impl.SftpInboundRemoteFileSystemSynchronizingMessageSource;
import org.springframework.integration.sftp.impl.SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean;
import org.w3c.dom.Element;
@@ -63,7 +65,8 @@ public class SftpNamespaceHandler extends NamespaceHandlerSupport {
private static class SFTPMessageSourceBeanDefinitionParser extends AbstractPollingInboundChannelAdapterParser {
@Override
protected String parseSource(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition( SftpMessageSourceFactoryBean.class.getName());
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean.class.getName());
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "filter");

View File

@@ -19,12 +19,9 @@ import org.springframework.integration.sftp.SftpSessionFactory;
/**
*
* Provides a single place to handle this tedious chore.
*
* todo : replace all the ad-hoc definitions of {@link org.springframework.integration.sftp.SftpSessionFactory}
*
* @author Josh Long
* @author Josh Long
*/
public class SftpSessionUtils {
/**
@@ -43,7 +40,7 @@ public class SftpSessionUtils {
* @throws Exception thrown in case of darned near <em>anything</em>
*/
public static SftpSessionFactory buildSftpSessionFactory(String host, String pw, String usr, String pvKey, String pvKeyPass, int port)
throws Exception {
throws Exception {
SftpSessionFactory sftpSessionFactory = new SftpSessionFactory();
sftpSessionFactory.setPassword(pw);
sftpSessionFactory.setPort(port);

View File

@@ -0,0 +1,142 @@
package org.springframework.integration.sftp.impl;
import com.jcraft.jsch.ChannelSftp;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.core.io.Resource;
import org.springframework.integration.MessagingException;
import org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer;
import org.springframework.integration.file.AbstractInboundRemoteFileSystemSynchronizingMessageSource;
import org.springframework.integration.sftp.SftpSession;
import org.springframework.integration.sftp.SftpSessionPool;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.util.Assert;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
/**
* This handles the synchronization between a remote SFTP endpoint and a local mount
*
* @author Josh Long
*/
public class SftpInboundRemoteFileSystemSynchronizer extends AbstractInboundRemoteFileSystemSychronizer<ChannelSftp.LsEntry> {
/**
* the path on the remote mount
*/
private volatile String remotePath;
/**
* the pool of {@link org.springframework.integration.sftp.SftpSessionPool} SFTP sessions
*/
private volatile SftpSessionPool clientPool;
public void setRemotePath(String remotePath) {
this.remotePath = remotePath;
}
@Override
protected void onInit() throws Exception {
Assert.notNull(this.clientPool, "'clientPool' can't be null");
Assert.notNull(this.remotePath, "'remotePath' can't be null");
if (this.shouldDeleteSourceFile) {
this.entryAcknowledgmentStrategy = new DeletionEntryAcknowledgmentStrategy();
}
}
@Required
public void setClientPool(SftpSessionPool clientPool) {
this.clientPool = clientPool;
}
@SuppressWarnings("ignored")
private boolean copyFromRemoteToLocalDirectory(SftpSession sftpSession, ChannelSftp.LsEntry entry, Resource localDir)
throws Exception {
File fileForLocalDir = localDir.getFile();
File localFile = new File(fileForLocalDir, entry.getFilename());
if (!localFile.exists()) {
InputStream in = null;
FileOutputStream fileOutputStream = null;
try {
File tmpLocalTarget = new File(localFile.getAbsolutePath() +
AbstractInboundRemoteFileSystemSynchronizingMessageSource.INCOMPLETE_EXTENSION);
fileOutputStream = new FileOutputStream(tmpLocalTarget);
String remoteFqPath = this.remotePath + "/" + entry.getFilename();
in = sftpSession.getChannel().get(remoteFqPath);
IOUtils.copy(in, fileOutputStream);
if (tmpLocalTarget.renameTo(localFile)) {
// last step
this.acknowledge(sftpSession, entry);
}
return true;
} catch (Throwable th) {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(fileOutputStream);
}
} else {
return true;
}
return false;
}
@Override
@SuppressWarnings("unchecked")
protected void syncRemoteToLocalFileSystem() throws Exception {
SftpSession session = null;
try {
session = clientPool.getSession();
session.start();
ChannelSftp channelSftp = session.getChannel();
Collection<ChannelSftp.LsEntry> beforeFilter = channelSftp.ls(remotePath);
ChannelSftp.LsEntry[] entries = (beforeFilter == null) ? new ChannelSftp.LsEntry[0] : beforeFilter.toArray(new ChannelSftp.LsEntry[beforeFilter.size()]);
Collection<ChannelSftp.LsEntry> files = this.filter.filterEntries(entries);
for (ChannelSftp.LsEntry lsEntry : files) {
if ((lsEntry != null) && !lsEntry.getAttrs().isDir() && !lsEntry.getAttrs().isLink()) {
copyFromRemoteToLocalDirectory(session, lsEntry, this.localDirectory);
}
}
} catch (IOException e) {
throw new MessagingException("couldn't synchronize remote to local directory", e);
} finally {
if ((session != null) && (clientPool != null)) {
clientPool.release(session);
}
}
}
@Override
protected Trigger getTrigger() {
return new PeriodicTrigger(10 * 1000);
}
class DeletionEntryAcknowledgmentStrategy implements AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy<ChannelSftp.LsEntry> {
public void acknowledge(Object useful, ChannelSftp.LsEntry msg)
throws Exception {
SftpSession sftpSession = (SftpSession) useful;
String remoteFqPath = remotePath + "/" + msg.getFilename();
sftpSession.getChannel().rm(remoteFqPath);
if (logger.isDebugEnabled()) {
logger.debug("deleted " + msg.getFilename());
}
}
}
}

View File

@@ -0,0 +1,101 @@
package org.springframework.integration.sftp.impl;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpATTRS;
import org.springframework.integration.file.AbstractInboundRemoteFileSystemSynchronizingMessageSource;
import org.springframework.integration.sftp.SftpSession;
import org.springframework.integration.sftp.SftpSessionPool;
import org.springframework.util.Assert;
/**
* a {@link org.springframework.integration.core.MessageSource} implementation for SFTP
*
* @author Josh Long
*/
public class SftpInboundRemoteFileSystemSynchronizingMessageSource extends AbstractInboundRemoteFileSystemSynchronizingMessageSource<ChannelSftp.LsEntry, SftpInboundRemoteFileSystemSynchronizer> {
/**
* the pool of sessions
*/
private volatile SftpSessionPool clientPool;
/**
* the remote path on teh server
*/
private volatile String remotePath;
public void setClientPool(SftpSessionPool clientPool) {
this.clientPool = clientPool;
}
public void setRemotePath(String remotePath) {
this.remotePath = remotePath;
}
@Override
protected void doStart() {
this.synchronizer.start();
}
@Override
protected void doStop() {
this.synchronizer.stop();
}
/**
* there be dragons this way ... This method will check to ensure that the remote directory exists. If the directory
* doesnt exist, and autoCreatePath is 'true,' then this method makes a few reasonably sane attempts
* to create it. Otherwise, it fails fast.
*
* @param remotePath the path on the remote SSH / SFTP server to create.
* @return whether or not the directory is there (regardless of whether we created it in this method or it already
* existed.)
*/
private boolean checkThatRemotePathExists(String remotePath) {
SftpSession session = null;
ChannelSftp channelSftp = null;
try {
session = this.clientPool.getSession();
Assert.state(session != null, "session as returned from the pool should not be null. " + "If it is, it is most likely an error in the pool implementation. ");
session.start();
channelSftp = session.getChannel();
SftpATTRS attrs = channelSftp.stat(remotePath);
assert (attrs != null) && attrs.isDir() : "attrs can't be null, and should indicate that it's a directory!";
return true;
} catch (Throwable th) {
if (this.autoCreateDirectories && (this.clientPool != null) && (session != null)) {
try {
if (channelSftp != null) {
channelSftp.mkdir(remotePath);
if (channelSftp.stat(remotePath).isDir()) {
return true;
}
}
} catch (Throwable t) {
return false;
}
}
} finally {
if ((clientPool != null) && (session != null)) {
clientPool.release(session);
}
}
return false;
}
@Override
protected void onInit() throws Exception {
super.onInit();
this.checkThatRemotePathExists(this.remotePath);
this.synchronizer.setClientPool(this.clientPool);
}
}

View File

@@ -0,0 +1,188 @@
package org.springframework.integration.sftp.impl;
import com.jcraft.jsch.ChannelSftp;
import org.apache.commons.lang.SystemUtils;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceEditor;
import org.springframework.core.io.ResourceLoader;
import org.springframework.integration.file.entries.CompositeEntryListFilter;
import org.springframework.integration.file.entries.EntryListFilter;
import org.springframework.integration.file.entries.PatternMatchingEntryListFilter;
import org.springframework.integration.sftp.QueuedSftpSessionPool;
import org.springframework.integration.sftp.SftpEntryNamer;
import org.springframework.integration.sftp.SftpSessionFactory;
import org.springframework.integration.sftp.config.SftpSessionUtils;
import org.springframework.util.StringUtils;
import java.io.File;
/**
* a factory bean to hide the fairly complex configuration possibilities for an SFTP endpoint
*
* @author Josh Long
*/
public class SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean extends AbstractFactoryBean<SftpInboundRemoteFileSystemSynchronizingMessageSource> implements ResourceLoaderAware {
/**
* injected by the container
*/
private volatile ResourceLoader resourceLoader;
private volatile Resource localDirectoryResource;
private volatile String localDirectoryPath;
private volatile String autoCreateDirectories;
private volatile String autoDeleteRemoteFilesOnSync;
private volatile String filenamePattern;
private volatile EntryListFilter<ChannelSftp.LsEntry> filter;
private int port = 22;
private String host;
private String keyFile;
private String keyFilePassword;
private String password;
private String remoteDirectory;
private String username;
@SuppressWarnings("unused")
public void setLocalDirectoryResource(Resource localDirectoryResource) {
this.localDirectoryResource = localDirectoryResource;
}
@SuppressWarnings("unused")
public void setLocalDirectoryPath(String localDirectoryPath) {
this.localDirectoryPath = localDirectoryPath;
}
@SuppressWarnings("unused")
public void setAutoCreateDirectories(String autoCreateDirectories) {
this.autoCreateDirectories = autoCreateDirectories;
}
@SuppressWarnings("unused")
public void setAutoDeleteRemoteFilesOnSync(String autoDeleteRemoteFilesOnSync) {
this.autoDeleteRemoteFilesOnSync = autoDeleteRemoteFilesOnSync;
}
@SuppressWarnings("unused")
public void setFilenamePattern(String filenamePattern) {
this.filenamePattern = filenamePattern;
}
@SuppressWarnings("unused")
public void setFilter(EntryListFilter<ChannelSftp.LsEntry> filter) {
this.filter = filter;
}
@SuppressWarnings("unused")
public void setPort(int port) {
this.port = port;
}
@SuppressWarnings("unused")
public void setHost(String host) {
this.host = host;
}
@SuppressWarnings("unused")
public void setKeyFile(String keyFile) {
this.keyFile = keyFile;
}
@SuppressWarnings("unused")
public void setKeyFilePassword(String keyFilePassword) {
this.keyFilePassword = keyFilePassword;
}
@SuppressWarnings("unused")
public void setPassword(String password) {
this.password = password;
}
@SuppressWarnings("unused")
public void setRemoteDirectory(String remoteDirectory) {
this.remoteDirectory = remoteDirectory;
}
@SuppressWarnings("unused")
public void setUsername(String username) {
this.username = username;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public Class<?> getObjectType() {
return SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean.class;
}
@Override
protected SftpInboundRemoteFileSystemSynchronizingMessageSource createInstance()
throws Exception {
boolean autoCreatDirs = Boolean.parseBoolean(this.autoCreateDirectories);
boolean ackRemoteDir = Boolean.parseBoolean(this.autoDeleteRemoteFilesOnSync);
SftpInboundRemoteFileSystemSynchronizingMessageSource sftpMsgSrc = new SftpInboundRemoteFileSystemSynchronizingMessageSource();
sftpMsgSrc.setAutoCreateDirectories(autoCreatDirs);
// local directories
if ((this.localDirectoryResource == null) || !StringUtils.hasText(this.localDirectoryPath)) {
File tmp = SystemUtils.getJavaIoTmpDir();
File sftpTmp = new File(tmp, "sftpInbound");
this.localDirectoryPath = "file://" + sftpTmp.getAbsolutePath();
}
this.localDirectoryResource = this.fromText(localDirectoryPath);
// remote predicates
SftpEntryNamer sftpEntryNamer = new SftpEntryNamer();
CompositeEntryListFilter<ChannelSftp.LsEntry> compositeFtpFileListFilter = new CompositeEntryListFilter<ChannelSftp.LsEntry>();
if (StringUtils.hasText(this.filenamePattern)) {
PatternMatchingEntryListFilter<ChannelSftp.LsEntry> ftpFilePatternMatchingEntryListFilter = new PatternMatchingEntryListFilter<ChannelSftp.LsEntry>(sftpEntryNamer, filenamePattern);
compositeFtpFileListFilter.addFilter(ftpFilePatternMatchingEntryListFilter);
}
if (this.filter != null) {
compositeFtpFileListFilter.addFilter(this.filter);
}
this.filter = compositeFtpFileListFilter;
// pools
SftpSessionFactory sessionFactory = SftpSessionUtils.buildSftpSessionFactory(this.host, this.password, this.username, this.keyFile, this.keyFilePassword, this.port);
QueuedSftpSessionPool pool = new QueuedSftpSessionPool(15, sessionFactory);
pool.afterPropertiesSet();
SftpInboundRemoteFileSystemSynchronizer sftpSync = new SftpInboundRemoteFileSystemSynchronizer();
sftpSync.setClientPool(pool);
sftpSync.setLocalDirectory(this.localDirectoryResource);
sftpSync.setShouldDeleteSourceFile(ackRemoteDir);
sftpSync.setFilter(compositeFtpFileListFilter);
sftpSync.setBeanFactory(this.getBeanFactory());
sftpSync.setRemotePath(this.remoteDirectory);
sftpSync.afterPropertiesSet(); // todo is this correct ?
sftpSync.start(); //todo
sftpMsgSrc.setRemotePredicate(compositeFtpFileListFilter);
sftpMsgSrc.setSynchronizer(sftpSync);
sftpMsgSrc.setClientPool(pool);
sftpMsgSrc.setRemotePath(this.remoteDirectory);
sftpMsgSrc.setLocalDirectory(this.localDirectoryResource);
sftpMsgSrc.setBeanFactory(this.getBeanFactory());
sftpMsgSrc.setAutoStartup(true);
sftpMsgSrc.afterPropertiesSet();
sftpMsgSrc.start();
return sftpMsgSrc;
}
private Resource fromText(String path) {
ResourceEditor resourceEditor = new ResourceEditor(this.resourceLoader);
resourceEditor.setAsText(path);
return (Resource) resourceEditor.getValue();
}
}

View File

@@ -1,108 +0,0 @@
package org.springframework.integration.sftp;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.ErrorHandler;
import java.io.File;
import java.util.logging.Logger;
/**
* This tests the API, more than the end-to-end XML to Java approach.
*
* @author Josh Long
*/
public class TestSftpReceipt {
private static final Logger logger = Logger.getLogger(TestSftpReceipt.class.getName());
private SftpSessionFactory sftpSessionFactory;
private String host;
private String password;
private String user;
private String privateKeyPath;
private String privateKeyPassword;
private int port;
@Before
public void before() throws Throwable {
this.sftpSessionFactory = buildSFTPSessionFactory(this.host, this.password, this.user, this.privateKeyPath, this.privateKeyPassword, this.port);
}
@Test
public void testReceive() throws Throwable {
String localMount = SystemUtils.getUserHome() + "/local_mount";
String remoteMount = "remote_mount";
// local path
File local = new File(localMount); // obviously this is just for test. Do what you need to do in your own
// we are testing, after all
if (local.exists() && (local.list().length > 0)) {
for (File f : local.listFiles()) {
if (!f.delete()) {
logger.fine("couldn't delete " + f.getAbsolutePath());
}
}
}
Resource localDirectory = new FileSystemResource(local);
// pool
QueuedSftpSessionPool queuedSFTPSessionPool = new QueuedSftpSessionPool(sftpSessionFactory);
queuedSFTPSessionPool.afterPropertiesSet();
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(10);
taskScheduler.setErrorHandler(new ErrorHandler() {
public void handleError(Throwable t) {
System.out.println("Error occurred: " + ExceptionUtils.getFullStackTrace(t));
}
});
taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
taskScheduler.initialize();
// synchronizer
final SftpInboundSynchronizer sftpInboundSynchronizer = new SftpInboundSynchronizer();
sftpInboundSynchronizer.setLocalDirectory(localDirectory);
sftpInboundSynchronizer.setRemotePath(remoteMount);
sftpInboundSynchronizer.setAutoCreatePath(true);
sftpInboundSynchronizer.setPool(queuedSFTPSessionPool);
sftpInboundSynchronizer.setShouldDeleteDownloadedRemoteFiles(false);
sftpInboundSynchronizer.setTaskScheduler(taskScheduler);
sftpInboundSynchronizer.afterPropertiesSet();
sftpInboundSynchronizer.start();
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(60 * 1000); // 1 minute
sftpInboundSynchronizer.stop();
} catch (InterruptedException e) {
// don't care
}
}
}).start();
}
private SftpSessionFactory buildSFTPSessionFactory(String host, String pw, String usr, String pvKey, String pvKeyPass, int port)
throws Throwable {
SftpSessionFactory sftpSessionFactory = new SftpSessionFactory();
sftpSessionFactory.setPassword(pw);
sftpSessionFactory.setPort(port);
sftpSessionFactory.setRemoteHost(host);
sftpSessionFactory.setUser(usr);
sftpSessionFactory.setPrivateKey(pvKey);
sftpSessionFactory.setPrivateKeyPassphrase(pvKeyPass);
sftpSessionFactory.afterPropertiesSet();
return sftpSessionFactory;
}
}

View File

@@ -34,6 +34,7 @@
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd
http://www.springframework.org/schema/integration/sftp http://www.springframework.org/schema/integration/sftp/spring-integration-sftp-2.0.xsd">
<context:component-scan base-package="org.springframework.integration.sftp"/>
<context:property-placeholder
location="file://${user.home}/Desktop/sftp.properties"
@@ -49,6 +50,8 @@
channel="inboundFilesChannel"
filename-pattern=".*?jpg"
username="${sftp.username}"
auto-create-directories="true"
auto-delete-remote-files-on-sync="true"
host="${sftp.host}">
<poller>
<interval-trigger interval="1000" time-unit="MILLISECONDS"/>

View File

@@ -54,11 +54,11 @@
class="org.springframework.integration.file.FileReadingMessageSource"
p:inputDirectory="file:${input.directory}"
p:filter-ref="compositeFilter"/>
<bean id="compositeFilter" class="org.springframework.integration.file.CompositeFileListFilter">
<bean id="compositeFilter" class="org.springframework.integration.file.filters.CompositeFileListFilter">
<constructor-arg>
<list>
<bean class="org.springframework.integration.file.AcceptOnceFileListFilter" />
<bean class="org.springframework.integration.file.PatternMatchingFileListFilter">
<bean class="org.springframework.integration.file.filters.AcceptOnceFileListFilter" />
<bean class="org.springframework.integration.file.filters.PatternMatchingFileListFilter">
<constructor-arg value="^test.*$"/>
</bean>
</list>
@@ -225,4 +225,4 @@
</para>
</section>
</chapter>
</chapter>