INT-1562 moved synchronization base classes into a dedicated package
This commit is contained in:
@@ -1,161 +0,0 @@
|
||||
package org.springframework.integration.file;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.file.entries.AcceptAllEntryListFilter;
|
||||
import org.springframework.integration.file.entries.EntryListFilter;
|
||||
import org.springframework.scheduling.Trigger;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
|
||||
/**
|
||||
* Strategy class charged with knowing how to connect to a remote file system, scan it for new files and then downloading the file.
|
||||
* <p/>
|
||||
* The implementation should run through any configured {@link org.springframework.integration.file.entries.EntryListFilter}s
|
||||
* to ensure the entry is worth downloading.
|
||||
*
|
||||
* @author Josh Long
|
||||
*/
|
||||
public abstract class AbstractInboundRemoteFileSystemSychronizer<T> extends AbstractEndpoint {
|
||||
/**
|
||||
* Should we <emphasis>delete</emphasis> the <b>source</b> file?
|
||||
* For an FTP server, for example, this would delete the original FTPFile instance
|
||||
* <p/>
|
||||
* At the moment I can simply see this triggering an implementation specific {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy}
|
||||
* implementation that knows how to delete an entry on the remote file system.
|
||||
*/
|
||||
protected boolean shouldDeleteSourceFile;
|
||||
|
||||
/**
|
||||
* the directory we're writing our synchronizations to
|
||||
*/
|
||||
protected volatile Resource localDirectory;
|
||||
|
||||
/**
|
||||
* a {@link org.springframework.integration.file.entries.EntryListFilter} that we're running against the <emphasis>remote</emphasis> file system view!
|
||||
*/
|
||||
protected volatile EntryListFilter<T> filter = new AcceptAllEntryListFilter<T>();
|
||||
|
||||
/**
|
||||
* the {@link java.util.concurrent.ScheduledFuture} instance we get when we schedule our {@link AbstractInboundRemoteFileSystemSychronizer.SynchronizeTask}
|
||||
*/
|
||||
protected ScheduledFuture<?> scheduledFuture;
|
||||
|
||||
/**
|
||||
* Used to store the {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy} implementation
|
||||
*/
|
||||
protected EntryAcknowledgmentStrategy<T> entryAcknowledgmentStrategy;
|
||||
|
||||
/**
|
||||
* Obviously thread safe - simply provides a NOOP impl so we don't have to keep dancing around NPE's
|
||||
*/
|
||||
private EntryAcknowledgmentStrategy<T> noOpEntryAcknowledgmentStrategy = new EntryAcknowledgmentStrategy<T>() {
|
||||
public void acknowledge(Object o, T msg) {
|
||||
}
|
||||
};
|
||||
|
||||
public void setEntryAcknowledgmentStrategy(EntryAcknowledgmentStrategy<T> entryAcknowledgmentStrategy) {
|
||||
this.entryAcknowledgmentStrategy = entryAcknowledgmentStrategy;
|
||||
}
|
||||
|
||||
public void setShouldDeleteSourceFile(boolean shouldDeleteSourceFile) {
|
||||
this.shouldDeleteSourceFile = shouldDeleteSourceFile;
|
||||
}
|
||||
|
||||
public void setLocalDirectory(Resource localDirectory) {
|
||||
this.localDirectory = localDirectory;
|
||||
}
|
||||
|
||||
public void setFilter(EntryListFilter<T> filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param usefulContextOrClientData this is context information to be passed to the individual {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy} implementation.
|
||||
* {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy#acknowledge(Object, Object)} will be called
|
||||
* in line with the {@link org.springframework.integration.core.MessageSource#receive()} call so this could conceivably be a 'live' stateful
|
||||
* client (a connection?) that is inappropriate to cache as it has per-request state.
|
||||
* @param t leverages strategy implementations to enable different behavior. It's a hook to the entry ({@link T}) after it's been successfully downloaded.
|
||||
* Conceptually, you might delete the remote one or rename it or something
|
||||
* @throws Throwable escape hatch exception, let the adapter deal with it.
|
||||
*/
|
||||
protected void acknowledge(Object usefulContextOrClientData, T t)
|
||||
throws Throwable {
|
||||
Assert.notNull(this.entryAcknowledgmentStrategy != null, "entryAcknowledgmentStrategy can't be null!");
|
||||
this.entryAcknowledgmentStrategy.acknowledge(usefulContextOrClientData, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the callback where we need the implementation to do some specific work
|
||||
*
|
||||
* @throws Exception thrown if anything goes wrong
|
||||
*/
|
||||
protected abstract void syncRemoteToLocalFileSystem()
|
||||
throws Exception;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected void doStop() {
|
||||
Assert.notNull(this.scheduledFuture, "the 'scheduledFuture' can't be null!");
|
||||
this.scheduledFuture.cancel(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value in millis dictating how frequently the trigger should fire
|
||||
*
|
||||
* @return a {@link org.springframework.scheduling.Trigger} implementation (likely,
|
||||
* {@link org.springframework.scheduling.support.PeriodicTrigger})
|
||||
*/
|
||||
protected abstract Trigger getTrigger();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected void doStart() {
|
||||
if (this.entryAcknowledgmentStrategy == null) {
|
||||
this.entryAcknowledgmentStrategy = noOpEntryAcknowledgmentStrategy;
|
||||
}
|
||||
|
||||
this.scheduledFuture = this.getTaskScheduler().schedule(new SynchronizeTask(), this.getTrigger());
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy interface to expose a hook for dispatching, moving, or deleting the file once it's been delivered.
|
||||
* This will typically be a NOOP for the implementation. Adapters should (for consistency) expose an attribute
|
||||
* dictating whether the adapter will delete the <emphasis>source</emphasis> entry on the remote file system.
|
||||
* This is the file-system version of an <code>ack-mode</code>. Future implementations should consider
|
||||
* exposing a custom attribute that plugs a custom {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy}
|
||||
* into the pipeline and also some more advanced scenarios (i.e., 'move file to another folder on delete ', or 'rename on delete')
|
||||
*
|
||||
* @param <T> the entry type (file, sftp, ftp, ...)
|
||||
*/
|
||||
public static interface EntryAcknowledgmentStrategy<T> {
|
||||
/**
|
||||
* Semantics are simple. You get a pointer to the entry just processed and any kind of helper data you could ask for. Since the strategy is a
|
||||
* singleton and the clients you might ask for as context data are pooled, it's not recommended that you try to cache them.
|
||||
*
|
||||
* @param useful any context data
|
||||
* @param msg the data / file / entry you want to process -- specific to sublcasses
|
||||
* @throws Exception thrown for any old reason
|
||||
*/
|
||||
void acknowledge(Object useful, T msg) throws Exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* This {@link Runnable} is launched as a background thread and is used to babysit the
|
||||
* {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer#localDirectory},
|
||||
* queueing and delivering accumulated files as possible.
|
||||
*/
|
||||
class SynchronizeTask implements Runnable {
|
||||
public void run() {
|
||||
try {
|
||||
syncRemoteToLocalFileSystem();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package org.springframework.integration.file;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.integration.endpoint.AbstractEndpoint;
|
||||
import org.springframework.integration.endpoint.MessageProducerSupport;
|
||||
import org.springframework.integration.file.entries.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* Ultimately, this factors out a lot of the common logic between the FTP and SFTP adapters. Designed to be extendable to handle
|
||||
* adapters whose task it is to synchronize a remote file system with a local file system (NB: this does *NOT* handle pushing files TO the remote
|
||||
* file system that exist uniquely in the local file system. It only handles bringing down the remote file system - as you'd expect
|
||||
* an 'inbound' adapter would).
|
||||
* <p/>
|
||||
* The base class supports configuration of whether the remote file system and local file system's directories should
|
||||
* be created on start (what 'creating a directory' means to the specific adapter is of course implementaton specific).
|
||||
* <p/>
|
||||
* This class is to be used as a pair with an implementation of
|
||||
* {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer<T>}. This synchronizer
|
||||
* must handle the work of actually connecting to the remote file system and delivering new {@link java.io.File}s.
|
||||
* The synchronizer is designed to be
|
||||
*
|
||||
* @author Josh Long
|
||||
*/
|
||||
public abstract class AbstractInboundRemoteFileSystemSynchronizingMessageSource<Y, T extends AbstractInboundRemoteFileSystemSychronizer<Y>> extends MessageProducerSupport implements MessageSource<File> {
|
||||
/**
|
||||
* Extension used when downloading files. We change it right after we know it's downloaded
|
||||
*/
|
||||
public static final String INCOMPLETE_EXTENSION = ".INCOMPLETE";
|
||||
|
||||
/**
|
||||
* Should the endpoint attempt to create the local directory and / or the remote directory?
|
||||
*/
|
||||
protected volatile boolean autoCreateDirectories = true;
|
||||
|
||||
/**
|
||||
* An implementation that will handle the chores of actually connecting to and syncing up the remote FS with the local one, in an inbound direction
|
||||
*/
|
||||
protected volatile T synchronizer;
|
||||
|
||||
/**
|
||||
* What directory should things be synced to locally ?
|
||||
*/
|
||||
protected volatile Resource localDirectory;
|
||||
|
||||
/**
|
||||
* The actual {@link FileReadingMessageSource} that we continue to trust to do the job monitoring the filesystem once files are moved down
|
||||
*/
|
||||
protected volatile FileReadingMessageSource fileSource;
|
||||
|
||||
/**
|
||||
* The predicate to use in scanning the remote Fs for downloads
|
||||
*/
|
||||
protected EntryListFilter<Y> remotePredicate;
|
||||
|
||||
public void setAutoCreateDirectories(boolean autoCreateDirectories) {
|
||||
this.autoCreateDirectories = autoCreateDirectories;
|
||||
}
|
||||
|
||||
public void setSynchronizer(T synchronizer) {
|
||||
this.synchronizer = synchronizer;
|
||||
}
|
||||
|
||||
public void setLocalDirectory(Resource localDirectory) {
|
||||
this.localDirectory = localDirectory;
|
||||
}
|
||||
|
||||
public void setRemotePredicate(EntryListFilter<Y> remotePredicate) {
|
||||
this.remotePredicate = remotePredicate;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private EntryListFilter<File> buildFilter() {
|
||||
FileEntryNamer fileEntryNamer = new FileEntryNamer();
|
||||
Pattern completePattern = Pattern.compile("^.*(?<!" + INCOMPLETE_EXTENSION + ")$");
|
||||
return new CompositeEntryListFilter<File>(
|
||||
Arrays.asList(
|
||||
new AcceptOnceEntryFileListFilter<File>(), new PatternMatchingEntryListFilter<File>(fileEntryNamer, completePattern)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
try {
|
||||
if (this.remotePredicate != null) {
|
||||
this.synchronizer.setFilter(this.remotePredicate);
|
||||
}
|
||||
|
||||
if (this.localDirectory != null && !this.localDirectory.exists()){
|
||||
if (this.autoCreateDirectories){
|
||||
logger.debug("The '" + localDirectory + "' directory doesn't exist. Creating " + this.localDirectory);
|
||||
this.localDirectory.getFile().mkdirs();
|
||||
} else {
|
||||
throw new FileNotFoundException(localDirectory.getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
} catch (Exception e) {
|
||||
if (e instanceof RuntimeException){
|
||||
throw (RuntimeException)e;
|
||||
} else {
|
||||
throw new MessagingException("Failure during initialization of MessageSource for: " + this.getComponentType(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Message<File> receive() {
|
||||
return this.fileSource.receive();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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.synchronization;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.MessagingException;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Base class charged with knowing how to connect to a remote file system,
|
||||
* scan it for new files and then download the files.
|
||||
* <p/>
|
||||
* The implementation should run through any configured
|
||||
* {@link org.springframework.integration.file.entries.EntryListFilter}s to
|
||||
* ensure the entry is acceptable.
|
||||
*
|
||||
* @author Josh Long
|
||||
*/
|
||||
public abstract class AbstractInboundRemoteFileSystemSychronizer<T> extends AbstractEndpoint {
|
||||
|
||||
/**
|
||||
* Should we <emphasis>delete</emphasis> the <b>source</b> file? For an FTP
|
||||
* server, for example, this would delete the original FTPFile instance.
|
||||
*/
|
||||
protected boolean shouldDeleteSourceFile;
|
||||
|
||||
/**
|
||||
* The directory to which we write our synchronizations.
|
||||
*/
|
||||
protected volatile Resource localDirectory;
|
||||
|
||||
/**
|
||||
* An {@link EntryListFilter} that runs against the <emphasis>remote</emphasis> file system view.
|
||||
*/
|
||||
protected volatile EntryListFilter<T> filter = new AcceptAllEntryListFilter<T>();
|
||||
|
||||
/**
|
||||
* The {@link ScheduledFuture} instance we get when we
|
||||
* schedule our {@link SynchronizeTask}
|
||||
*/
|
||||
protected ScheduledFuture<?> scheduledFuture;
|
||||
|
||||
/**
|
||||
* The {@link EntryAcknowledgmentStrategy} implementation.
|
||||
*/
|
||||
protected EntryAcknowledgmentStrategy<T> entryAcknowledgmentStrategy;
|
||||
|
||||
|
||||
public void setEntryAcknowledgmentStrategy(EntryAcknowledgmentStrategy<T> entryAcknowledgmentStrategy) {
|
||||
this.entryAcknowledgmentStrategy = entryAcknowledgmentStrategy;
|
||||
}
|
||||
|
||||
public void setShouldDeleteSourceFile(boolean shouldDeleteSourceFile) {
|
||||
this.shouldDeleteSourceFile = shouldDeleteSourceFile;
|
||||
}
|
||||
|
||||
public void setLocalDirectory(Resource localDirectory) {
|
||||
this.localDirectory = localDirectory;
|
||||
}
|
||||
|
||||
public void setFilter(EntryListFilter<T> filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param usefulContextOrClientData
|
||||
* this is context information to be passed to the individual {@link EntryAcknowledgmentStrategy}.
|
||||
* {@link 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, etc.
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected void doStart() {
|
||||
if (this.entryAcknowledgmentStrategy == null) {
|
||||
this.entryAcknowledgmentStrategy = new EntryAcknowledgmentStrategy<T>() {
|
||||
public void acknowledge(Object o, T msg) {
|
||||
// no-op
|
||||
}
|
||||
};
|
||||
}
|
||||
this.scheduledFuture = this.getTaskScheduler().schedule(new SynchronizeTask(), this.getTrigger());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected void doStop() {
|
||||
if (this.scheduledFuture != null) {
|
||||
this.scheduledFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Trigger} that dictates how frequently the trigger should fire.
|
||||
*/
|
||||
protected abstract Trigger getTrigger();
|
||||
|
||||
/**
|
||||
* This is the callback where we need the implementation to do some specific work
|
||||
*/
|
||||
protected abstract void syncRemoteToLocalFileSystem() throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
* This {@link Runnable} is launched as a background thread and is used to manage the
|
||||
* {@link AbstractInboundRemoteFileSystemSychronizer#localDirectory} by queueing and
|
||||
* delivering accumulated files as possible.
|
||||
*/
|
||||
class SynchronizeTask implements Runnable {
|
||||
public void run() {
|
||||
try {
|
||||
syncRemoteToLocalFileSystem();
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new MessagingException("failure occurred in synchronization task", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Strategy interface to expose a hook for dispatching, moving, or deleting
|
||||
* the file once it has been delivered. This will typically be a NOOP for the
|
||||
* implementation. Adapters should (for consistency) expose an attribute
|
||||
* dictating whether the adapter will delete the <emphasis>source</emphasis>
|
||||
* entry on the remote file system. This is the file-system version of an
|
||||
* <code>ack-mode</code>. Future implementations should consider exposing a
|
||||
* custom attribute that plugs a custom {@link EntryAcknowledgmentStrategy}
|
||||
* into the pipeline and also some more advanced scenarios (i.e., 'move file
|
||||
* to another folder on delete ', or 'rename on delete')
|
||||
*
|
||||
* @param <T> the entry type (file, sftp, ftp, ...)
|
||||
*/
|
||||
public static interface EntryAcknowledgmentStrategy<T> {
|
||||
|
||||
/**
|
||||
* Semantics are simple. You get a pointer to the entry just processed
|
||||
* and any kind of helper data you could ask for. Since the strategy is
|
||||
* a singleton and the clients you might ask for as context data are
|
||||
* pooled, it's not recommended that you try to cache them.
|
||||
*
|
||||
* @param useful
|
||||
* any context data
|
||||
* @param msg
|
||||
* the data / file / entry you want to process -- specific to subclasses
|
||||
* @throws Exception in case of an error while acknowledging
|
||||
*/
|
||||
void acknowledge(Object useful, T msg) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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.synchronization;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.integration.endpoint.MessageProducerSupport;
|
||||
import org.springframework.integration.file.FileReadingMessageSource;
|
||||
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.FileEntryNamer;
|
||||
import org.springframework.integration.file.entries.PatternMatchingEntryListFilter;
|
||||
|
||||
/**
|
||||
* Factors out the common logic between the FTP and SFTP adapters. Designed to
|
||||
* be extensible 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 pulling from the remote file system - as you would expect
|
||||
* from an 'inbound' adapter).
|
||||
* <p/>
|
||||
* The base class supports configuration of whether the remote file system and
|
||||
* local file system's directories should be created on start (what 'creating a
|
||||
* directory' means to the specific adapter is of course implementaton
|
||||
* specific).
|
||||
* <p/>
|
||||
* This class is to be used as a pair with an implementation of
|
||||
* {@link AbstractInboundRemoteFileSystemSychronizer}. The synchronizer must
|
||||
* handle the work of actually connecting to the remote file system and
|
||||
* delivering new {@link File}s.
|
||||
*
|
||||
* @author Josh Long
|
||||
*/
|
||||
public abstract class AbstractInboundRemoteFileSystemSynchronizingMessageSource<Y, T extends AbstractInboundRemoteFileSystemSychronizer<Y>>
|
||||
extends MessageProducerSupport implements MessageSource<File> {
|
||||
|
||||
/**
|
||||
* Extension used when downloading files. We change it right after we know it's downloaded.
|
||||
*/
|
||||
public static final String INCOMPLETE_EXTENSION = ".INCOMPLETE";
|
||||
|
||||
/**
|
||||
* Should the endpoint attempt to create the local directory and/or the remote directory?
|
||||
*/
|
||||
protected volatile boolean autoCreateDirectories = true;
|
||||
|
||||
/**
|
||||
* An implementation that will handle the chores of actually connecting to and synching up
|
||||
* the remote file system with the local one, in an inbound direction.
|
||||
*/
|
||||
protected volatile T synchronizer;
|
||||
|
||||
/**
|
||||
* Directory to which things should be synched locally.
|
||||
*/
|
||||
protected volatile Resource localDirectory;
|
||||
|
||||
/**
|
||||
* The actual {@link FileReadingMessageSource} that monitors the local filesystem once files are synched.
|
||||
*/
|
||||
protected volatile FileReadingMessageSource fileSource;
|
||||
|
||||
/**
|
||||
* The predicate to use in scanning the remote File system for downloads.
|
||||
*/
|
||||
protected EntryListFilter<Y> remotePredicate;
|
||||
|
||||
|
||||
public void setAutoCreateDirectories(boolean autoCreateDirectories) {
|
||||
this.autoCreateDirectories = autoCreateDirectories;
|
||||
}
|
||||
|
||||
public void setSynchronizer(T synchronizer) {
|
||||
this.synchronizer = synchronizer;
|
||||
}
|
||||
|
||||
public void setLocalDirectory(Resource localDirectory) {
|
||||
this.localDirectory = localDirectory;
|
||||
}
|
||||
|
||||
public void setRemotePredicate(EntryListFilter<Y> remotePredicate) {
|
||||
this.remotePredicate = remotePredicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
try {
|
||||
if (this.remotePredicate != null) {
|
||||
this.synchronizer.setFilter(this.remotePredicate);
|
||||
}
|
||||
if (this.localDirectory != null && !this.localDirectory.exists()) {
|
||||
if (this.autoCreateDirectories) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("The '" + this.localDirectory + "' directory doesn't exist; Will create.");
|
||||
}
|
||||
this.localDirectory.getFile().mkdirs();
|
||||
}
|
||||
else {
|
||||
throw new FileNotFoundException(this.localDirectory.getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the remote files get here.
|
||||
*/
|
||||
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());
|
||||
|
||||
/**
|
||||
* Forwards files once they ultimately appear in the {@link #localDirectory}.
|
||||
*/
|
||||
this.fileSource = new FileReadingMessageSource();
|
||||
this.fileSource.setFilter(this.buildFilter());
|
||||
this.fileSource.setDirectory(this.localDirectory.getFile());
|
||||
this.fileSource.afterPropertiesSet();
|
||||
this.synchronizer.afterPropertiesSet();
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new MessagingException("Failure during initialization of MessageSource for: "
|
||||
+ this.getComponentType(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public Message<File> receive() {
|
||||
return this.fileSource.receive();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private EntryListFilter<File> buildFilter() {
|
||||
FileEntryNamer fileEntryNamer = new FileEntryNamer();
|
||||
Pattern completePattern = Pattern.compile("^.*(?<!" + INCOMPLETE_EXTENSION + ")$");
|
||||
return new CompositeEntryListFilter<File>(Arrays.asList(
|
||||
new AcceptOnceEntryFileListFilter<File>(),
|
||||
new PatternMatchingEntryListFilter<File>(fileEntryNamer, completePattern)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,8 +20,8 @@ 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.file.synchronization.AbstractInboundRemoteFileSystemSychronizer;
|
||||
import org.springframework.integration.file.synchronization.AbstractInboundRemoteFileSystemSynchronizingMessageSource;
|
||||
import org.springframework.integration.ftp.FtpClientPool;
|
||||
import org.springframework.scheduling.Trigger;
|
||||
import org.springframework.scheduling.support.PeriodicTrigger;
|
||||
@@ -34,7 +34,7 @@ import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* An FTP-adapter implementation of {@link org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer}
|
||||
* An FTP-adapter implementation of {@link org.springframework.integration.file.synchronization.AbstractInboundRemoteFileSystemSychronizer}
|
||||
*
|
||||
* @author Iwein Fuld
|
||||
* @author Josh Long
|
||||
|
||||
@@ -18,7 +18,7 @@ package org.springframework.integration.ftp;
|
||||
|
||||
import org.apache.commons.net.ftp.FTPFile;
|
||||
|
||||
import org.springframework.integration.file.AbstractInboundRemoteFileSystemSynchronizingMessageSource;
|
||||
import org.springframework.integration.file.synchronization.AbstractInboundRemoteFileSystemSynchronizingMessageSource;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.integration.core.MessageSource} implementation for FTP.
|
||||
|
||||
@@ -21,8 +21,8 @@ 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.file.synchronization.AbstractInboundRemoteFileSystemSychronizer;
|
||||
import org.springframework.integration.file.synchronization.AbstractInboundRemoteFileSystemSynchronizingMessageSource;
|
||||
import org.springframework.integration.sftp.SftpSession;
|
||||
import org.springframework.integration.sftp.SftpSessionPool;
|
||||
import org.springframework.scheduling.Trigger;
|
||||
|
||||
@@ -17,7 +17,8 @@ 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.file.synchronization.AbstractInboundRemoteFileSystemSynchronizingMessageSource;
|
||||
import org.springframework.integration.sftp.SftpSession;
|
||||
import org.springframework.integration.sftp.SftpSessionPool;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.file.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy;
|
||||
import org.springframework.integration.file.synchronization.AbstractInboundRemoteFileSystemSychronizer.EntryAcknowledgmentStrategy;
|
||||
import org.springframework.integration.sftp.SftpSession;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user