diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/AbstractInboundRemoteFileSystemSychronizer.java b/spring-integration-file/src/main/java/org/springframework/integration/file/AbstractInboundRemoteFileSystemSychronizer.java new file mode 100644 index 0000000000..b560747508 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/AbstractInboundRemoteFileSystemSychronizer.java @@ -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. + *

+ * 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 extends AbstractEndpoint { + /** + * Should we delete the source file? + * For an FTP server, for example, this would delete the original FTPFile instance + *

+ * 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 remote file system view! + */ + protected volatile EntryListFilter filter = new AcceptAllEntryListFilter(); + + /** + * 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 entryAcknowledgmentStrategy; + + /** + * Obviously thread safe - simply provides a NOOP impl so we don't have to keep dancing around NPE's + */ + private EntryAcknowledgmentStrategy noOpEntryAcknowledgmentStrategy = new EntryAcknowledgmentStrategy() { + public void acknowledge(Object o, T msg) { + } + }; + + public void setEntryAcknowledgmentStrategy(EntryAcknowledgmentStrategy entryAcknowledgmentStrategy) { + this.entryAcknowledgmentStrategy = entryAcknowledgmentStrategy; + } + + public void setShouldDeleteSourceFile(boolean shouldDeleteSourceFile) { + this.shouldDeleteSourceFile = shouldDeleteSourceFile; + } + + public void setLocalDirectory(Resource localDirectory) { + this.localDirectory = localDirectory; + } + + public void setFilter(EntryListFilter 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 source entry on the remote file system. + * This is the file-system version of an ack-mode. 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 the entry type (file, sftp, ftp, ...) + */ + public static interface EntryAcknowledgmentStrategy { + /** + * 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); + } + } + } +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/AbstractInboundRemoteFileSystemSynchronizingMessageSource.java b/spring-integration-file/src/main/java/org/springframework/integration/file/AbstractInboundRemoteFileSystemSynchronizingMessageSource.java new file mode 100644 index 0000000000..bac4b1370e --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/AbstractInboundRemoteFileSystemSynchronizingMessageSource.java @@ -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). + *

+ * 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). + *

+ * This class is to be used as a pair with an implementation of + * {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer}. 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> extends AbstractEndpoint implements MessageSource { + /** + * 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 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 remotePredicate) { + this.remotePredicate = remotePredicate; + } + + private EntryListFilter buildFilter() { + FileEntryNamer fileEntryNamer = new FileEntryNamer(); + Pattern completePattern = Pattern.compile("^.*(?(new AcceptOnceEntryFileListFilter(), new PatternMatchingEntryListFilter(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 receive() { + return this.fileSource.receive(); + } +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/AcceptOnceFileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/AcceptOnceFileListFilter.java deleted file mode 100644 index 576d168726..0000000000 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/AcceptOnceFileListFilter.java +++ /dev/null @@ -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}. - *

- * This implementation is thread safe. - * - * @author Iwein Fuld - * @since 1.0.0 - */ -public class AcceptOnceFileListFilter extends AbstractFileListFilter { - - private final Queue 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(maxCapacity); - } - - /** - * Creates an AcceptOnceFileFilter based on an unbounded queue. - */ - public AcceptOnceFileListFilter() { - this.seen = new LinkedBlockingQueue(); - } - - - 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; - } - } - -} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/DefaultDirectoryScanner.java b/spring-integration-file/src/main/java/org/springframework/integration/file/DefaultDirectoryScanner.java index 27580bbbfa..aff9f362ec 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/DefaultDirectoryScanner.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/DefaultDirectoryScanner.java @@ -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 filter = new AcceptOnceEntryFileListFilter(); private FileLocker locker; public final List 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 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); } /** diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/DirectoryScanner.java b/spring-integration-file/src/main/java/org/springframework/integration/file/DirectoryScanner.java index a194998c61..bcd3d69396 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/DirectoryScanner.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/DirectoryScanner.java @@ -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 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 filter); /** diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/FileReadingMessageSource.java b/spring-integration-file/src/main/java/org/springframework/integration/file/FileReadingMessageSource.java index 85d71be275..a1eb4d698a 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/FileReadingMessageSource.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/FileReadingMessageSource.java @@ -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. *

* 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. *

* 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, - InitializingBean { - +public class FileReadingMessageSource implements MessageSource, 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, * There is no locking around the queue, so there is also no iteration. */ private final Queue toBeReceived; - private boolean scanEachPoll = false; /** @@ -92,7 +86,7 @@ public class FileReadingMessageSource implements MessageSource, */ 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, * @param receptionOrderComparator the comparator to be used to order the files in the internal queue */ public FileReadingMessageSource(Comparator receptionOrderComparator) { - toBeReceived = new PriorityBlockingQueue(DEFAULT_INTERNAL_QUEUE_CAPACITY, - receptionOrderComparator); + toBeReceived = new PriorityBlockingQueue(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, /** * 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, * Specify whether to create the source directory automatically if it does not yet exist upon initialization. By * default, this value is true. If set to false 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. *

* The supplied filter must be thread safe.. + * + * @param filter a filter */ - public void setFilter(FileListFilter filter) { + public void setFilter(EntryListFilter filter) { Assert.notNull(filter, "'filter' must not be null"); this.scanner.setFilter(filter); } @@ -153,6 +154,8 @@ public class FileReadingMessageSource implements MessageSource, * against duplicate processing. *

* The supplied FileLocker must be thread safe + * + * @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, * 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 false, but it will change more often (causing expensive reordering) if * it is set to true. + * + * @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 receive() throws MessagingException { Message 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 filteredFiles = scanner.listFiles(directory); Set freshFiles = new HashSet(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, /** * 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 failedMessage, Throwable t) { + public void onFailure(Message 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 receive() + * + * @param sentMessage the message that was successfully delivered */ public void onSend(Message sentMessage) { if (logger.isDebugEnabled()) { diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/HeadDirectoryScanner.java b/spring-integration-file/src/main/java/org/springframework/integration/file/HeadDirectoryScanner.java index 7f761687cd..6a3c291b24 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/HeadDirectoryScanner.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/HeadDirectoryScanner.java @@ -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 maxNumberOfFiles 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 { private final int maxNumberOfFiles; public HeadFilter(int maxNumberOfFiles) { this.maxNumberOfFiles = maxNumberOfFiles; } - public List filterFiles(File[] files) { + public List filterEntries(File[] files) { return Arrays.asList(files).subList(0, Math.min(files.length, maxNumberOfFiles)); } } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileListFilterFactoryBean.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileListFilterFactoryBean.java index fa98a9bc0c..6cd13164c0 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileListFilterFactoryBean.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileListFilterFactoryBean.java @@ -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 { +public class FileListFilterFactoryBean implements FactoryBean> { + private volatile EntryListFilter fileListFilter; + private volatile EntryListFilter filterReference; + private volatile Pattern filenamePattern; + private volatile Boolean preventDuplicates; + private final Object monitor = new Object(); + private volatile Collection> filterReferences; + private FileEntryNamer fileNamer = new FileEntryNamer(); - private volatile FileListFilter fileListFilter; + public void setFilterReferences(Collection> filterReferences) { + this.filterReferences = filterReferences; + } - private volatile FileListFilter filterReference; + public void setFilterReference(EntryListFilter 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 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 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 patternFilter = new PatternMatchingEntryListFilter(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(); + } else { // preventDuplicates is either TRUE or NULL + flf = new AcceptOnceEntryFileListFilter(); + } - 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 flfc = new CompositeEntryListFilter(); + for (EntryListFilter ff : filterReferences) + flfc.addFilter(ff); + + flf = flfc; + } + if( flf== null)flf =new CompositeEntryListFilter(); + + this.fileListFilter = flf; + } + + private CompositeEntryListFilter createCompositeWithAcceptOnceFilter(EntryListFilter otherFilter) { + CompositeEntryListFilter compositeFilter = new CompositeEntryListFilter(); + compositeFilter.addFilter(new AcceptOnceEntryFileListFilter() ); + compositeFilter.addFilter(otherFilter); + + return compositeFilter; + } } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileReadingMessageSourceFactoryBean.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileReadingMessageSourceFactoryBean.java index 09d5cfab4f..aa0c41b79e 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileReadingMessageSourceFactoryBean.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileReadingMessageSourceFactoryBean.java @@ -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 filter; private volatile AbstractFileLockerFilter locker; @@ -69,7 +69,7 @@ public class FileReadingMessageSourceFactoryBean implements FactoryBean filter) { if (filter instanceof AbstractFileLockerFilter && this.locker == null) { this.setLocker((AbstractFileLockerFilter) filter); } @@ -133,7 +133,7 @@ public class FileReadingMessageSourceFactoryBean implements FactoryBean(this.filter, this.locker)); this.source.setLocker(locker); } } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AbstractEntryListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AbstractEntryListFilter.java index 3f00a62660..87239b84b2 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AbstractEntryListFilter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AbstractEntryListFilter.java @@ -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 implements EntryListFilter { - 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 implements InitializingBean, EntryListFilter { + public abstract boolean accept(T t); public List filterEntries(T[] entries) { List accepted = new ArrayList(); @@ -37,4 +47,8 @@ public abstract class AbstractEntryListFilter implements EntryListFilter { return accepted; } + + public void afterPropertiesSet() throws Exception { + // its all you! + } } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AcceptAllEntryListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AcceptAllEntryListFilter.java new file mode 100644 index 0000000000..87cbe8d847 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AcceptAllEntryListFilter.java @@ -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 + */ +public class AcceptAllEntryListFilter extends AbstractEntryListFilter { + @Override + public boolean accept(T t) { + return true; + } +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AcceptOnceEntryFileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AcceptOnceEntryFileListFilter.java index 789c8c00a5..cc37ee7197 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AcceptOnceEntryFileListFilter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/AcceptOnceEntryFileListFilter.java @@ -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}. + *

+ * This implementation is thread safe. + * + * @author Iwein Fuld + * @since 1.0.0 + */ public class AcceptOnceEntryFileListFilter extends AbstractEntryListFilter { private final Queue 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 extends AbstractEntryListFilter this.seen = new LinkedBlockingQueue(); } - protected boolean accept(T pathname) { + public boolean accept(T pathname) { synchronized (this.monitor) { if (seen.contains(pathname)) { return false; diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/CompositeEntryListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/CompositeEntryListFilter.java index 82ecdc00e8..8ba156a376 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/CompositeEntryListFilter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/CompositeEntryListFilter.java @@ -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 implements EntryListFilter { - private final Set fileFilters; + private final Set> fileFilters; - public CompositeEntryListFilter(EntryListFilter... fileFilters) { - this.fileFilters = new LinkedHashSet(Arrays.asList(fileFilters)); + public CompositeEntryListFilter(EntryListFilter... fileFilters) { + this.fileFilters = new LinkedHashSet>(Arrays.asList(fileFilters)); } - public CompositeEntryListFilter(Collection fileFilters) { - this.fileFilters = new LinkedHashSet(fileFilters); + public CompositeEntryListFilter(Collection> fileFilters) { + this.fileFilters = new LinkedHashSet>(fileFilters); } @SuppressWarnings("unchecked") public List filterEntries(T[] entries) { Assert.notNull(entries, "'files' should not be null"); - List leftOver = Arrays.asList(entries); - for (EntryListFilter fileFilter : this.fileFilters) { - T[] ts =(T[]) leftOver.toArray(); + + List leftOver = Arrays.asList(entries); + + for (EntryListFilter fileFilter : this.fileFilters) { + T[] ts = (T[]) leftOver.toArray(); leftOver = fileFilter.filterEntries(ts); } + return leftOver; } + public CompositeEntryListFilter addFilter(EntryListFilter 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 addFilters(EntryListFilter[] filters) { return addFilters(Arrays.asList(filters)); } @@ -59,7 +69,16 @@ public class CompositeEntryListFilter implements EntryListFilter { * @param filtersToAdd a list of filters to add * @return this CompositeEntryListFilter instance with the added filters */ - public CompositeEntryListFilter addFilters(Collection filtersToAdd) { + public CompositeEntryListFilter addFilters(Collection> filtersToAdd) { + for (EntryListFilter elf : filtersToAdd) + if (elf instanceof InitializingBean) { + try { + ((InitializingBean) elf).afterPropertiesSet(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + this.fileFilters.addAll(filtersToAdd); return this; diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/EntryListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/EntryListFilter.java index 8ab82e15cc..076fca83a8 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/EntryListFilter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/EntryListFilter.java @@ -18,6 +18,19 @@ package org.springframework.integration.file.entries; import java.util.List; -public interface EntryListFilter { - List filterEntries(T [] entries ); +/** + * Strategy interface for filtering a group of entries / files. + *

+ * {@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}. + *

+ * This implementation is thread safe. + * + * @author Iwein Fuld + * @author Josh Long + * @since 1.0.0 + */ +public interface EntryListFilter { + List filterEntries(T[] entries); } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/EntryNamer.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/EntryNamer.java index 1bc181cb42..fde16e3a34 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/EntryNamer.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/EntryNamer.java @@ -15,6 +15,21 @@ */ package org.springframework.integration.file.entries; +/** + * Responsible for coercing a String identification out of the {@link T} entry. + * @param the type of entry (there's an implementation for FTP, SFTP, and plain-old java.io.Files) + * + * @author Josh Long + */ public interface EntryNamer { + + /** + * 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); } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/FileEntryNamer.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/FileEntryNamer.java new file mode 100644 index 0000000000..23de52ac76 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/FileEntryNamer.java @@ -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 { + public String nameOf(File entry) { + return (entry != null) ? entry.getName() : null; + } +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/PatternMatchingEntryListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/PatternMatchingEntryListFilter.java index 1d9d604b06..1e29c1d2cb 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/PatternMatchingEntryListFilter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/PatternMatchingEntryListFilter.java @@ -24,16 +24,29 @@ import java.util.regex.Pattern; /** - * experimental + * experimental + *

+ * 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 the type of entry */ -public abstract class PatternMatchingEntryListFilter extends AbstractEntryListFilter implements InitializingBean { +public class PatternMatchingEntryListFilter extends AbstractEntryListFilter implements InitializingBean { private Pattern pattern; private String patternExpression; private EntryNamer entryNamer; + public PatternMatchingEntryListFilter(EntryNamer en, String p) { + this.entryNamer = en; + this.patternExpression = p; + } + + public PatternMatchingEntryListFilter(EntryNamer 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 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(); } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/entries/SingleEntryAdaptingEntryListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/SingleEntryAdaptingEntryListFilter.java new file mode 100644 index 0000000000..0842b6e027 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/entries/SingleEntryAdaptingEntryListFilter.java @@ -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 one argument instea of an array + * + * @author Josh Long + */ +public class SingleEntryAdaptingEntryListFilter extends AbstractEntryListFilter { + + /** + * the {@link org.springframework.integration.file.entries.EntryListFilter} that you'd like to delegate to + */ + private volatile EntryListFilter entryFilter; + + public SingleEntryAdaptingEntryListFilter(EntryListFilter 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; + } +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/AbstractFileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractFileListFilter.java similarity index 65% rename from spring-integration-file/src/main/java/org/springframework/integration/file/AbstractFileListFilter.java rename to spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractFileListFilter.java index 23fcda4766..c38d35d153 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/AbstractFileListFilter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AbstractFileListFilter.java @@ -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 filterFiles(File[] files) { - List accepted = new ArrayList(); - if (files != null) { - for (File file : files) { - if (this.accept(file)) { - accepted.add(file); - } - } - } - return accepted; - } + /** + * {@inheritDoc} + */ + public final List filterFiles(File[] files) { + List accepted = new ArrayList(); + 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); } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AcceptOnceFileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AcceptOnceFileListFilter.java new file mode 100644 index 0000000000..9cd914ec7c --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/AcceptOnceFileListFilter.java @@ -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}. + *

+ * This implementation is thread safe. + * + * @author Iwein Fuld + * @since 1.0.0 + */ +@Deprecated +public class AcceptOnceFileListFilter extends AbstractFileListFilter { + private final Queue 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(maxCapacity); + } + + /** + * Creates an AcceptOnceFileFilter based on an unbounded queue. + */ + public AcceptOnceFileListFilter() { + this.seen = new LinkedBlockingQueue(); + } + + 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; + } + } +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/CompositeFileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/CompositeFileListFilter.java similarity index 89% rename from spring-integration-file/src/main/java/org/springframework/integration/file/CompositeFileListFilter.java rename to spring-integration-file/src/main/java/org/springframework/integration/file/filters/CompositeFileListFilter.java index a1ea42ee71..c97ed0e12b 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/CompositeFileListFilter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/CompositeFileListFilter.java @@ -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 fileFilters; - public CompositeFileListFilter(FileListFilter... fileFilters) { this.fileFilters = new LinkedHashSet(Arrays.asList(fileFilters)); } @@ -42,7 +41,6 @@ public class CompositeFileListFilter implements FileListFilter { this.fileFilters = new LinkedHashSet(fileFilters); } - /** * {@inheritDoc} *

@@ -50,33 +48,35 @@ public class CompositeFileListFilter implements FileListFilter { */ public List filterFiles(File[] files) { Assert.notNull(files, "'files' should not be null"); + List 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. - * + *

* 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 filtersToAdd) { +/* public CompositeFileListFilter addFilters(Collection filtersToAdd) { this.fileFilters.addAll(filtersToAdd); - return this; - } + return this; + }*/ } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/FileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/FileListFilter.java similarity index 76% rename from spring-integration-file/src/main/java/org/springframework/integration/file/FileListFilter.java rename to spring-integration-file/src/main/java/org/springframework/integration/file/filters/FileListFilter.java index 615f57b129..3a77093669 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/FileListFilter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/FileListFilter.java @@ -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 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 filterFiles(File[] files); } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/PatternMatchingFileListFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/PatternMatchingFileListFilter.java similarity index 95% rename from spring-integration-file/src/main/java/org/springframework/integration/file/PatternMatchingFileListFilter.java rename to spring-integration-file/src/main/java/org/springframework/integration/file/filters/PatternMatchingFileListFilter.java index 3dac0f7629..786477c454 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/PatternMatchingFileListFilter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/filters/PatternMatchingFileListFilter.java @@ -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; diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/locking/AbstractFileLockerFilter.java b/spring-integration-file/src/main/java/org/springframework/integration/file/locking/AbstractFileLockerFilter.java index 7804468f75..71fff4aac8 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/locking/AbstractFileLockerFilter.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/locking/AbstractFileLockerFilter.java @@ -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 implements FileLocker { - protected final boolean accept(File file) { - return isLockable(file); + @Override + public boolean accept(File file) { + return this.isLockable(file); } -} \ No newline at end of file +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/monitors/AbstractEventDrivenFileMonitor.java b/spring-integration-file/src/main/java/org/springframework/integration/file/monitors/AbstractEventDrivenFileMonitor.java new file mode 100644 index 0000000000..4d47ca5186 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/monitors/AbstractEventDrivenFileMonitor.java @@ -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. + *

+ * In the meantime, this provides us with a base class for building event driven file adapters quickly. The two cases I see are: + *

+ *

    + *
  1. + *
  2. + *
+ * + * @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 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 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 filter) { + this.filter = new SingleEntryAdaptingEntryListFilter(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(this.maxQueueSize); + + if (this.executor == null) { + this.executor = new SimpleAsyncTaskExecutor(); + } + + if (this.filter == null) { + this.filter = new SingleEntryAdaptingEntryListFilter(new AcceptAllEntryListFilter()); + } + + 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 acceptOnceEntryFileListFilter=new AcceptOnceEntryFileListFilter() ; + acceptOnceEntryFileListFilter.afterPropertiesSet(); + PatternMatchingEntryListFilter patternMatchingEntryListFilter= + new PatternMatchingEntryListFilter(new FileEntryNamer(), ".*?jpg"); + patternMatchingEntryListFilter.afterPropertiesSet(); + + Collection> l=Arrays.asList( acceptOnceEntryFileListFilter, patternMatchingEntryListFilter); + CompositeEntryListFilter compositeEntryListFilter = new CompositeEntryListFilter(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) { + // + } + } + } + }); + } +} +*/ diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/monitors/DirectedEventDrivenFileMonitor.java b/spring-integration-file/src/main/java/org/springframework/integration/file/monitors/DirectedEventDrivenFileMonitor.java new file mode 100644 index 0000000000..a7ad60980d --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/monitors/DirectedEventDrivenFileMonitor.java @@ -0,0 +1,18 @@ +package org.springframework.integration.file.monitors; + +import java.io.File; + + +/** + * simply takes a cue / hint (something tells 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); + } +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/monitors/EventDrivenDirectoryMonitor.java b/spring-integration-file/src/main/java/org/springframework/integration/file/monitors/EventDrivenDirectoryMonitor.java new file mode 100644 index 0000000000..718486a8f7 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/monitors/EventDrivenDirectoryMonitor.java @@ -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; +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/monitors/FileAdditionListener.java b/spring-integration-file/src/main/java/org/springframework/integration/file/monitors/FileAdditionListener.java new file mode 100644 index 0000000000..f32860346a --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/monitors/FileAdditionListener.java @@ -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); +} diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/monitors/MessageSendingFileAdditionListener.java b/spring-integration-file/src/main/java/org/springframework/integration/file/monitors/MessageSendingFileAdditionListener.java new file mode 100644 index 0000000000..3ae9b649a7 --- /dev/null +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/monitors/MessageSendingFileAdditionListener.java @@ -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 fileMsg = MessageBuilder.withPayload(f).build(); + this.messagingTemplate.send(fileMsg); + } +} diff --git a/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-1.0.xsd b/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-1.0.xsd index c2c89f3aa6..3753122ea1 100644 --- a/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-1.0.xsd +++ b/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-1.0.xsd @@ -53,7 +53,7 @@ - + diff --git a/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-2.0.xsd b/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-2.0.xsd index cf960a43d2..f00a15167c 100644 --- a/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-2.0.xsd +++ b/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-2.0.xsd @@ -56,7 +56,7 @@ - + diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/CompositeFileListFilterTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/CompositeFileListFilterTests.java index e3de58346b..bb0e28edf3 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/CompositeFileListFilterTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/CompositeFileListFilterTests.java @@ -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 fileFilterMock1 = mock(EntryListFilter.class); - private FileListFilter fileFilterMock2 = mock(FileListFilter.class); + @SuppressWarnings("unchecked") + private EntryListFilter 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 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 compositeFileFilter = new CompositeEntryListFilter(fileFilterMock1, fileFilterMock2); + List 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 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 compositeFileFilter = new CompositeEntryListFilter(); + compositeFileFilter.addFilter(fileFilterMock1); + compositeFileFilter.addFilter( fileFilterMock2); + List 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()); - when(fileFilterMock1.filterFiles(isA(File[].class))).thenReturn(new ArrayList()); - 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 compositeFileFilter = new CompositeEntryListFilter(); + compositeFileFilter.addFilter(fileFilterMock1); + compositeFileFilter.addFilter(fileFilterMock2); + + when(fileFilterMock2.filterEntries(isA(File[].class))).thenReturn(new ArrayList()); + when(fileFilterMock1.filterEntries(isA(File[].class))).thenReturn(new ArrayList()); + assertTrue(compositeFileFilter.filterEntries(new File[]{fileMock}).isEmpty()); + } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceIntegrationTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceIntegrationTests-context.xml index f6c205b022..f0336e1452 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceIntegrationTests-context.xml +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceIntegrationTests-context.xml @@ -1,28 +1,49 @@ + 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"> - - + + - - - - - - - - - - - - + - + + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceIntegrationTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceIntegrationTests.java index 44ae3e5845..fbbf8a17ad 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceIntegrationTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceIntegrationTests.java @@ -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 received1 = pollableFileSource.receive(); + System.out.println("receive files round 1"); + assertNotNull("This should return the first message", received1); + pollableFileSource.onSend(received1); + Message received2 = pollableFileSource.receive(); + assertNotNull(received2); + pollableFileSource.onSend(received2); + Message 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 received1 = pollableFileSource.receive(); + Message received2 = pollableFileSource.receive(); + Message 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 received = pollableFileSource.receive(); + while (received == null) { + Thread.yield(); + received = pollableFileSource.receive(); + } + pollableFileSource.onSend(received); + } + }; + Runnable failingConsumer = new Runnable() { + public void run() { + Message 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 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 received1 = pollableFileSource.receive(); - assertNotNull("This should return the first message", received1); - pollableFileSource.onSend(received1); - Message received2 = pollableFileSource.receive(); - assertNotNull(received2); - pollableFileSource.onSend(received2); - Message 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 received1 = pollableFileSource.receive(); - Message received2 = pollableFileSource.receive(); - Message 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 received = pollableFileSource.receive(); - while (received == null) { - Thread.yield(); - received = pollableFileSource.receive(); - } - pollableFileSource.onSend(received); - } - }; - Runnable failingConsumer = new Runnable() { - public void run() { - Message 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 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; + } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceTests.java index 244e5bb49a..d064041378 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/FileReadingMessageSourceTests.java @@ -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(); } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/FileToChannelIntegrationTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/FileToChannelIntegrationTests-context.xml index a342db3147..0462c2fb0f 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/FileToChannelIntegrationTests-context.xml +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/FileToChannelIntegrationTests-context.xml @@ -1,46 +1,48 @@ + 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"> - - + + - + - - - + + + - - - + + + - - - - - - - - - - - - - + + - + - \ No newline at end of file + + + + + + + + + + + + + + + diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/PatternMatchingFileListFilterTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/PatternMatchingFileListFilterTests.java index c86e12b9d5..431b221665 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/PatternMatchingFileListFilterTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/PatternMatchingFileListFilterTests.java @@ -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 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 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 filter = new PatternMatchingEntryListFilter(fileEntryNamer, pattern); + List 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 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 filter = new PatternMatchingEntryListFilter(fileEntryNamer, pattern); + List 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 filter = new PatternMatchingEntryListFilter(this.fileEntryNamer, pattern); + List 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 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 filter = (EntryListFilter) context.getBean("filter"); + File[] files = new File[]{new File("/some/path/foo.txt")}; + List accepted = filter.filterEntries(files); + assertEquals(1, accepted.size()); + } + + @Test(expected = BeanCreationException.class) + public void invalidPatternSyntax() throws Throwable { + new ClassPathXmlApplicationContext("invalidPatternMatchingFileListFilterTests.xml", this.getClass()); + } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/TestFileListFilter.java b/spring-integration-file/src/test/java/org/springframework/integration/file/TestFileListFilter.java index 88d4ad9d24..4b3b1bb6b8 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/TestFileListFilter.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/TestFileListFilter.java @@ -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 filterFiles(File[] files) { - return Arrays.asList(files); - } - +public class TestFileListFilter implements EntryListFilter { + public List filterEntries(File[] entries) { + return Arrays.asList(entries); + } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/AutoCreateDirectoryIntegrationTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/config/AutoCreateDirectoryIntegrationTests-context.xml index ac5e948dd6..45c2f7aac9 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/AutoCreateDirectoryIntegrationTests-context.xml +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/AutoCreateDirectoryIntegrationTests-context.xml @@ -58,8 +58,6 @@ - - - + diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/AutoCreateDirectoryIntegrationTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/AutoCreateDirectoryIntegrationTests.java index a7d9ac64d2..2918672304 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/AutoCreateDirectoryIntegrationTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/AutoCreateDirectoryIntegrationTests.java @@ -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")); + } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/CustomFileNameGenerator.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/CustomFileNameGenerator.java index 1959fe125f..f1ff9da6f0 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/CustomFileNameGenerator.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/CustomFileNameGenerator.java @@ -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(); + } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/DefaultConfigurationTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/DefaultConfigurationTests.java index b206de47e7..7a64ee3fa3 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/DefaultConfigurationTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/DefaultConfigurationTests.java @@ -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); + } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests-context.xml index 1bf152ea65..bfcb0e3ce9 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests-context.xml +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests-context.xml @@ -15,20 +15,22 @@ filter="filter" comparator="testComparator" auto-startup="false"> - - - + - + + + + - \ No newline at end of file + diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests.java index 764b47afb2..f12927b6b2 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterParserTests.java @@ -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 { + static class TestComparator implements Comparator { - public int compare(File f1, File f2) { - return 0; - } - } + public int compare(File f1, File f2) { + return 0; + } + } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithClasspathInPropertiesTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithClasspathInPropertiesTests-context.xml index 0d1327ad2b..05e27ae1d1 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithClasspathInPropertiesTests-context.xml +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithClasspathInPropertiesTests-context.xml @@ -15,11 +15,9 @@ - - - + - \ No newline at end of file + diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests-context.xml index 5b548b56a2..24f536d111 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests-context.xml +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests-context.xml @@ -15,9 +15,7 @@ - - - + - \ No newline at end of file + diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests.java index 5bdc482781..7fb76e2f19 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPatternParserTests.java @@ -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 filters = (Set) new DirectFieldAccessor( - scannerAccessor.getPropertyValue("filter")).getPropertyValue("fileFilters"); - assertEquals(2, filters.size()); - } + Set> filters = (Set>) 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 filters = (Set) 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> filters = (Set>) new DirectFieldAccessor( + scannerAccessor.getPropertyValue("filter")).getPropertyValue("fileFilters"); + boolean hasAcceptOnceFilter = false; + for (EntryListFilter 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 filters = (Set) 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 filters = (Set) 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()); + } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPreventDuplicatesFlagTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPreventDuplicatesFlagTests-context.xml index f25eaa3936..871d840fdd 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPreventDuplicatesFlagTests-context.xml +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPreventDuplicatesFlagTests-context.xml @@ -1,114 +1,103 @@ - + - - - + + + - - - - - + + + - - - - - + + + - - - - - + + - - - - - + - - - - - + + - - - - - + - - - - - + + - - - - - + - - - - - + + - + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPreventDuplicatesFlagTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPreventDuplicatesFlagTests.java index d8b2b677c6..55b12232e9 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPreventDuplicatesFlagTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithPreventDuplicatesFlagTests.java @@ -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 iterator = filters.iterator(); - assertTrue(iterator.next() instanceof AcceptOnceFileListFilter); - assertTrue(iterator.next() instanceof PatternMatchingFileListFilter); + Iterator> 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 iterator = filters.iterator(); - assertTrue(iterator.next() instanceof AcceptOnceFileListFilter); - assertTrue(iterator.next() instanceof PatternMatchingFileListFilter); + Iterator 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 filter = this.extractFilter("patternAndFalse"); + assertFalse(filter instanceof CompositeEntryListFilter); + assertTrue(filter instanceof PatternMatchingEntryListFilter); } @Test public void defaultAndNull() throws Exception { - FileListFilter filter = this.extractFilter("defaultAndNull"); + EntryListFilter 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 result = filter.filterFiles(files); + File[] files = new File[] { testFile, testFile, testFile }; + List 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 result = filter.filterFiles(files); + File[] files = new File[] { testFile, testFile, testFile }; + List 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 result = filter.filterFiles(files); + File[] files = new File[] { testFile, testFile, testFile }; + List 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 extractFilter(String beanName) { + return (EntryListFilter) new DirectFieldAccessor(new DirectFieldAccessor(new DirectFieldAccessor(context.getBean(beanName)).getPropertyValue("source")).getPropertyValue("scanner")).getPropertyValue( + "filter"); } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithQueueSizeTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithQueueSizeTests-context.xml index 62aa45c0e5..b27cb1105c 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithQueueSizeTests-context.xml +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileInboundChannelAdapterWithQueueSizeTests-context.xml @@ -31,12 +31,11 @@ filter="filter" queue-size="30" auto-startup="false"> - - - + + - + @@ -47,4 +46,4 @@ - \ No newline at end of file + diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileListFilterFactoryBeanTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileListFilterFactoryBeanTests.java index be9424c178..a12083bc45 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileListFilterFactoryBeanTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileListFilterFactoryBeanTests.java @@ -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 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 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 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 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 result = factory.getObject(); + assertTrue(result instanceof CompositeEntryListFilter); + Collection filters = (Collection) new DirectFieldAccessor(result).getPropertyValue("fileFilters"); + Iterator 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 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 result = factory.getObject(); + assertTrue(result instanceof CompositeEntryListFilter); + Collection filters = (Collection) new DirectFieldAccessor(result).getPropertyValue("fileFilters"); + Iterator 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 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 { + @Override + public boolean accept(File file) { + return true; + } + } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundAdaptersWithClasspathInPropertiesTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundAdaptersWithClasspathInPropertiesTests.java index 6a45db4db4..ee2e6e90bc 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundAdaptersWithClasspathInPropertiesTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundAdaptersWithClasspathInPropertiesTests.java @@ -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); + } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundGatewayParserTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundGatewayParserTests.java index 574176a00c..44f7bd4e65 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundGatewayParserTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundGatewayParserTests.java @@ -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")); + } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileToStringTransformerParserTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileToStringTransformerParserTests.java index 3109e4f091..fc32c1c94d 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileToStringTransformerParserTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileToStringTransformerParserTests.java @@ -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")); + } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/invalidPatternMatchingFileListFilterTests.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/invalidPatternMatchingFileListFilterTests.xml index 3401588dcd..9a8517c663 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/invalidPatternMatchingFileListFilterTests.xml +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/invalidPatternMatchingFileListFilterTests.xml @@ -1,11 +1,14 @@ - - - + + + + + + diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/locking/FileLockingNamespaceTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/locking/FileLockingNamespaceTests.java index 28f74dd6bd..73c2de30f0 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/locking/FileLockingNamespaceTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/locking/FileLockingNamespaceTests.java @@ -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 { diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/locking/NioFileLockerTests.java b/spring-integration-file/src/test/java/org/springframework/integration/file/locking/NioFileLockerTests.java index 46b427da68..1e38e3ff9b 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/locking/NioFileLockerTests.java +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/locking/NioFileLockerTests.java @@ -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 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)new ArrayList())); + assertThat(filter2.filterEntries(workdir.listFiles()), is((List)new ArrayList())); } -} \ No newline at end of file +} diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/patternMatchingFileListFilterTests.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/patternMatchingFileListFilterTests.xml index ddd5f8f965..c541d36b39 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/patternMatchingFileListFilterTests.xml +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/patternMatchingFileListFilterTests.xml @@ -1,11 +1,18 @@ - - - + + + + + + + diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/recursive/FileInboundChannelAdapterWithRecursiveDirectoryTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/recursive/FileInboundChannelAdapterWithRecursiveDirectoryTests-context.xml index 238710c46e..b35cc91a0b 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/recursive/FileInboundChannelAdapterWithRecursiveDirectoryTests-context.xml +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/recursive/FileInboundChannelAdapterWithRecursiveDirectoryTests-context.xml @@ -17,9 +17,7 @@ auto-startup="true" scanner="recursiveScanner" channel="files"> - - - + @@ -29,4 +27,4 @@ - \ No newline at end of file + diff --git a/spring-integration-file/template.mf b/spring-integration-file/template.mf index 8a4ea705bb..cc7dd1ef9b 100644 --- a/spring-integration-file/template.mf +++ b/spring-integration-file/template.mf @@ -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" diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/AbstractFtpFileListFilter.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/AbstractFtpFileListFilter.java deleted file mode 100644 index 7eb08590c5..0000000000 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/AbstractFtpFileListFilter.java +++ /dev/null @@ -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 filterFiles(FTPFile[] files) { - List accepted = new ArrayList(); - - if (files != null) { - for (FTPFile f : files) { - if (this.accept(f)) { - accepted.add(f); - } - } - } - - return accepted; - } -} diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/CompositeFtpFileListFilter.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/CompositeFtpFileListFilter.java deleted file mode 100644 index acb844fdc7..0000000000 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/CompositeFtpFileListFilter.java +++ /dev/null @@ -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 filters; - - public CompositeFtpFileListFilter(FtpFileListFilter... ftpFileListFilter) { - this.filters = new LinkedHashSet(Arrays.asList(ftpFileListFilter)); - } - - public CompositeFtpFileListFilter(Collection ftpFileListFilter) { - this.filters = new LinkedHashSet(ftpFileListFilter); - } - public void addFilter( FtpFileListFilter ftpFileListFilter ) { - this.filters.add(ftpFileListFilter); - } - public List filterFiles(FTPFile[] files) { - Assert.notNull(files, "files[] can't be null!"); - - List leftOver = Arrays.asList(files); - - for (FtpFileListFilter ff : this.filters) - leftOver = ff.filterFiles(leftOver.toArray(new FTPFile[leftOver.size()])); - - return leftOver; - } -} diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpFileEntryNamer.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpFileEntryNamer.java new file mode 100644 index 0000000000..e511efac4d --- /dev/null +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpFileEntryNamer.java @@ -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 { + public String nameOf(FTPFile entry) { + return entry.getName(); + } +} diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpFileListFilter.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpFileListFilter.java deleted file mode 100644 index bb92725a99..0000000000 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpFileListFilter.java +++ /dev/null @@ -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 filterFiles (FTPFile [] files); -} diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpFileSource.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpFileSource.java deleted file mode 100644 index 44cb3cb13b..0000000000 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpFileSource.java +++ /dev/null @@ -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, 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("^.*(? receive() { - return fileSource.receive(); - } - - public void onFailure(Message failedMessage, Throwable t) { - fileSource.onFailure(failedMessage, t); - } - - public void onSend(Message sentMessage) { - fileSource.onSend(sentMessage); - } - - public boolean isRunning() { - return synchronizer.isRunning(); - } - - public void start() { - synchronizer.start(); - } - - public void stop() { - synchronizer.stop(); - } -} diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpInboundSynchronizer.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpInboundSynchronizer.java deleted file mode 100644 index 36c33731d2..0000000000 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpInboundSynchronizer.java +++ /dev/null @@ -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; - - -/** - * FtpInboundSynchronizer 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 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 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(); - } - } -} diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpMessageSourceFactoryBean.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpMessageSourceFactoryBean.java deleted file mode 100644 index c13ace9d46..0000000000 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpMessageSourceFactoryBean.java +++ /dev/null @@ -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 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 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 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; - } -} diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/PatternMatchingFtpFileListFilter.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/PatternMatchingFtpFileListFilter.java deleted file mode 100644 index feab1f1d0f..0000000000 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/PatternMatchingFtpFileListFilter.java +++ /dev/null @@ -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"); - } -} diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpNamespaceHandler.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpNamespaceHandler.java index d72823110a..9ca93523b0 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpNamespaceHandler.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/config/FtpNamespaceHandler.java @@ -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()); } } diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpInboundRemoteFileSystemSynchronizer.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpInboundRemoteFileSystemSynchronizer.java new file mode 100644 index 0000000000..00f2129885 --- /dev/null +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpInboundRemoteFileSystemSynchronizer.java @@ -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 { + 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 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 { + 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()); + } + } + } + } +} diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpInboundRemoteFileSystemSynchronizingMessageSource.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpInboundRemoteFileSystemSynchronizingMessageSource.java new file mode 100644 index 0000000000..3861a57342 --- /dev/null +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpInboundRemoteFileSystemSynchronizingMessageSource.java @@ -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 { + 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); + } +} diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java new file mode 100644 index 0000000000..b5a187ce23 --- /dev/null +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/impl/FtpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java @@ -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 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 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 compositeFtpFileListFilter = new CompositeEntryListFilter(); + + if (StringUtils.hasText(this.filenamePattern)) { + PatternMatchingEntryListFilter ftpFilePatternMatchingEntryListFilter = new PatternMatchingEntryListFilter(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 filter) { + this.filter = filter; + } + + @SuppressWarnings("unused") + public void setClientMode(int clientMode) { + this.clientMode = clientMode; + } + + @SuppressWarnings("unused") + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } +} diff --git a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.0.xsd b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.0.xsd index cf4dbc9fb2..391b4d7522 100644 --- a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.0.xsd +++ b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.0.xsd @@ -138,7 +138,7 @@ - + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/InboundFtpFileServiceActivator.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/InboundFtpFileServiceActivator.java index 75ad88642e..48544076e1 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/InboundFtpFileServiceActivator.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/InboundFtpFileServiceActivator.java @@ -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(); } } diff --git a/spring-integration-ftp/src/test/resources/inbound-ftp-context.xml b/spring-integration-ftp/src/test/resources/inbound-ftp-context.xml index 355348baaf..aa937e6a32 100644 --- a/spring-integration-ftp/src/test/resources/inbound-ftp-context.xml +++ b/spring-integration-ftp/src/test/resources/inbound-ftp-context.xml @@ -20,7 +20,7 @@ filename-pattern=".*?jpg" > - + diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/AbstractSftpFileListFilter.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/AbstractSftpFileListFilter.java deleted file mode 100644 index cc5d1b8691..0000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/AbstractSftpFileListFilter.java +++ /dev/null @@ -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 filterFiles(ChannelSftp.LsEntry[] files) { - List accepted = new ArrayList(); - - if (files != null) { - for (ChannelSftp.LsEntry lsEntry : files) - if (this.accept(lsEntry)) { - accepted.add(lsEntry); - } - } - - return accepted; - } -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/CompositeFtpFileListFilter.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/CompositeFtpFileListFilter.java deleted file mode 100644 index 4d2d0b54a3..0000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/CompositeFtpFileListFilter.java +++ /dev/null @@ -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 filters; - - public CompositeFtpFileListFilter(SftpFileListFilter... ftpFileListFilter) { - this.filters = new LinkedHashSet(Arrays.asList(ftpFileListFilter)); - } - - public CompositeFtpFileListFilter(Collection ftpFileListFilter) { - this.filters = new LinkedHashSet(ftpFileListFilter); - } - - public void addFilter(SftpFileListFilter ftpFileListFilter) { - this.filters.add(ftpFileListFilter); - } - - public List filterFiles(ChannelSftp.LsEntry[] files) { - Assert.notNull(files, "files[] can't be null!"); - - List leftOver = Arrays.asList(files); - - for (SftpFileListFilter ff : this.filters) - leftOver = ff.filterFiles(leftOver.toArray(new ChannelSftp.LsEntry[leftOver.size()])); - - return leftOver; - } -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/PatternMatchingSftpFileListFilter.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/PatternMatchingSftpFileListFilter.java deleted file mode 100644 index c79f1d9a0c..0000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/PatternMatchingSftpFileListFilter.java +++ /dev/null @@ -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"); - } -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpEntryNamer.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpEntryNamer.java new file mode 100644 index 0000000000..404c93510b --- /dev/null +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpEntryNamer.java @@ -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{ + + public String nameOf(ChannelSftp.LsEntry entry) { + return entry.getFilename() ; + } +} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpFileListFilter.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpFileListFilter.java deleted file mode 100644 index 98a14fc56d..0000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpFileListFilter.java +++ /dev/null @@ -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 filterFiles (ChannelSftp.LsEntry [] files); -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpInboundSynchronizer.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpInboundSynchronizer.java deleted file mode 100644 index fe04a7e3ff..0000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpInboundSynchronizer.java +++ /dev/null @@ -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 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 beforeFilter = channelSftp.ls(remotePath); - ChannelSftp.LsEntry [] entries = beforeFilter == null? new ChannelSftp.LsEntry[0] : - beforeFilter.toArray(new ChannelSftp.LsEntry[ beforeFilter.size()]) ; - Collection 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); - } - } - } -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpMessageSource.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpMessageSource.java deleted file mode 100644 index 3d418ba1e6..0000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpMessageSource.java +++ /dev/null @@ -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 very influenced - * by the FtpFileSource class from the Spring Integration FTP adapter. - * - * @author Josh Long - */ -public class SftpMessageSource implements MessageSource, 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("^.*(? 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(); - } -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpMessageSourceFactoryBean.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpMessageSourceFactoryBean.java deleted file mode 100644 index cc44d6dd34..0000000000 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpMessageSourceFactoryBean.java +++ /dev/null @@ -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 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 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 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; - } -} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java index 8d3e3b83e0..ec2cc9aada 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpNamespaceHandler.java @@ -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"); diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpSessionUtils.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpSessionUtils.java index 4d639480fc..908239a35c 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpSessionUtils.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpSessionUtils.java @@ -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 anything */ 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); diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/impl/SftpInboundRemoteFileSystemSynchronizer.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/impl/SftpInboundRemoteFileSystemSynchronizer.java new file mode 100644 index 0000000000..203d6c7f29 --- /dev/null +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/impl/SftpInboundRemoteFileSystemSynchronizer.java @@ -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 { + /** + * 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 beforeFilter = channelSftp.ls(remotePath); + ChannelSftp.LsEntry[] entries = (beforeFilter == null) ? new ChannelSftp.LsEntry[0] : beforeFilter.toArray(new ChannelSftp.LsEntry[beforeFilter.size()]); + Collection 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 { + 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()); + } + } + } +} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/impl/SftpInboundRemoteFileSystemSynchronizingMessageSource.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/impl/SftpInboundRemoteFileSystemSynchronizingMessageSource.java new file mode 100644 index 0000000000..f45177b690 --- /dev/null +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/impl/SftpInboundRemoteFileSystemSynchronizingMessageSource.java @@ -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 { + /** + * 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); + } +} diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/impl/SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/impl/SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java new file mode 100644 index 0000000000..5bbd0854c5 --- /dev/null +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/impl/SftpRemoteFileSystemSynchronizingMessageSourceFactoryBean.java @@ -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 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 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 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 compositeFtpFileListFilter = new CompositeEntryListFilter(); + + if (StringUtils.hasText(this.filenamePattern)) { + PatternMatchingEntryListFilter ftpFilePatternMatchingEntryListFilter = new PatternMatchingEntryListFilter(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(); + } +} diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpReceipt.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpReceipt.java deleted file mode 100644 index 325651015e..0000000000 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/TestSftpReceipt.java +++ /dev/null @@ -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; - } -} diff --git a/spring-integration-sftp/src/test/resources/TestInboundSftp.xml b/spring-integration-sftp/src/test/resources/TestInboundSftp.xml index 6ef7076255..aa6efb21db 100644 --- a/spring-integration-sftp/src/test/resources/TestInboundSftp.xml +++ b/spring-integration-sftp/src/test/resources/TestInboundSftp.xml @@ -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"> + diff --git a/src/docbkx/file.xml b/src/docbkx/file.xml index f5a57ff030..e4b6aa6662 100644 --- a/src/docbkx/file.xml +++ b/src/docbkx/file.xml @@ -54,11 +54,11 @@ class="org.springframework.integration.file.FileReadingMessageSource" p:inputDirectory="file:${input.directory}" p:filter-ref="compositeFilter"/> - + - - + + @@ -225,4 +225,4 @@ - \ No newline at end of file +