GH-3043: Add FileHeaders.REMOTE_HOST header (#3044)
* GH-3043: Add FileHeaders.REMOTE_HOST header Fixes https://github.com/spring-projects/spring-integration/issues/3043 * Populate a `FileHeaders.REMOTE_HOST` from the `AbstractRemoteFileStreamingMessageSource` and "get"-based commands in the `AbstractRemoteFileOutboundGateway` * Extract the value from the a `Session.getHost()` contract * The `AbstractInboundFileSynchronizingMessageSource` cannot be addressed with this because the real message is already based on the locally stored file * Adjust some affected tests according our code style requirements * * Add remote file info support into `AbstractInboundFileSynchronizingMessageSource` * Introduce a `MetadataStore` functionality into the `AbstractInboundFileSynchronizer` to gather a remote file info an save it in the URI style against local file * Retrieve such an info in the `AbstractInboundFileSynchronizingMessageSource` during local file polling * Introduce `protocol()` contract for the `AbstractInboundFileSynchronizer` to build a proper URI in the metadata for external readers to distinguish remote files properly * Document the feature * * Fix some typos in Docs * * Rename property and header constant to the `HOST_PORT` pair * Fix typos in Docs * Add `remote-file-metadata-store` and `metadata-store-prefix` into XSD of (S)FTP Inbound Channel Adapters * Add `remoteFileMetadataStore` and `metadataStorePrefix` options into `RemoteFileInboundChannelAdapterSpec` for Java DSL
This commit is contained in:
committed by
Gary Russell
parent
ff15d5265d
commit
a756e6334d
@@ -52,4 +52,9 @@ public abstract class FileHeaders {
|
||||
*/
|
||||
public static final String REMOTE_FILE_INFO = PREFIX + "remoteFileInfo";
|
||||
|
||||
/**
|
||||
* A remote host/port the file has been polled from
|
||||
*/
|
||||
public static final String REMOTE_HOST_PORT = PREFIX + "remoteHostPort";
|
||||
|
||||
}
|
||||
|
||||
@@ -45,14 +45,15 @@ public abstract class AbstractRemoteFileInboundChannelAdapterParser extends Abst
|
||||
|
||||
@Override
|
||||
protected final BeanMetadataElement parseSource(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder synchronizerBuilder = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
this.getInboundFileSynchronizerClass());
|
||||
BeanDefinitionBuilder synchronizerBuilder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(getInboundFileSynchronizerClass());
|
||||
|
||||
synchronizerBuilder.addConstructorArgReference(element.getAttribute("session-factory"));
|
||||
|
||||
// configure the InboundFileSynchronizer properties
|
||||
BeanDefinition expressionDef = IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression(
|
||||
"remote-directory", "remote-directory-expression", parserContext, element, false);
|
||||
BeanDefinition expressionDef =
|
||||
IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression(
|
||||
"remote-directory", "remote-directory-expression", parserContext, element, false);
|
||||
if (expressionDef != null) {
|
||||
synchronizerBuilder.addPropertyValue("remoteDirectoryExpression", expressionDef);
|
||||
}
|
||||
@@ -62,6 +63,9 @@ public abstract class AbstractRemoteFileInboundChannelAdapterParser extends Abst
|
||||
String remoteFileSeparator = element.getAttribute("remote-file-separator");
|
||||
synchronizerBuilder.addPropertyValue("remoteFileSeparator", remoteFileSeparator);
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "temporary-file-suffix");
|
||||
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(synchronizerBuilder, element,
|
||||
"remote-file-metadata-store");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(synchronizerBuilder, element, "metadata-store-prefix");
|
||||
|
||||
FileParserUtils.configureFilter(synchronizerBuilder, element, parserContext,
|
||||
getSimplePatternFileListFilterClass(), getRegexPatternFileListFilterClass(),
|
||||
@@ -100,6 +104,7 @@ public abstract class AbstractRemoteFileInboundChannelAdapterParser extends Abst
|
||||
|
||||
protected abstract Class<? extends FileListFilter<?>> getRegexPatternFileListFilterClass();
|
||||
|
||||
protected abstract Class<? extends AbstractPersistentAcceptOnceFileListFilter<?>> getPersistentAcceptOnceFileListFilterClass();
|
||||
protected abstract Class<? extends AbstractPersistentAcceptOnceFileListFilter<?>>
|
||||
getPersistentAcceptOnceFileListFilterClass();
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.springframework.integration.file.filters.ExpressionFileListFilter;
|
||||
import org.springframework.integration.file.filters.FileListFilter;
|
||||
import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer;
|
||||
import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizingMessageSource;
|
||||
import org.springframework.integration.metadata.MetadataStore;
|
||||
|
||||
/**
|
||||
* A {@link MessageSourceSpec} for an {@link AbstractInboundFileSynchronizingMessageSource}.
|
||||
@@ -245,15 +246,37 @@ public abstract class RemoteFileInboundChannelAdapterSpec<F, S extends RemoteFil
|
||||
return _this();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a {@link MetadataStore} for remote files metadata.
|
||||
* @param remoteFileMetadataStore the {@link MetadataStore} to use.
|
||||
* @return the spec.
|
||||
* @since 5.2
|
||||
* @see AbstractInboundFileSynchronizer#setRemoteFileMetadataStore(MetadataStore)
|
||||
*/
|
||||
public S remoteFileMetadataStore(MetadataStore remoteFileMetadataStore) {
|
||||
this.synchronizer.setRemoteFileMetadataStore(remoteFileMetadataStore);
|
||||
return _this();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a prefix for remote files metadata keys.
|
||||
* @param metadataStorePrefix the metadata key prefix to use.
|
||||
* @return the spec.
|
||||
* @since 5.2
|
||||
* @see #remoteFileMetadataStore
|
||||
*/
|
||||
public S metadataStorePrefix(String metadataStorePrefix) {
|
||||
this.synchronizer.setMetadataStorePrefix(metadataStorePrefix);
|
||||
return _this();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, String> getComponentsToRegister() {
|
||||
Map<Object, String> componentsToRegister = new LinkedHashMap<>();
|
||||
componentsToRegister.put(this.synchronizer, null);
|
||||
|
||||
if (this.expressionFileListFilter != null) {
|
||||
componentsToRegister.put(this.expressionFileListFilter, null);
|
||||
}
|
||||
|
||||
return componentsToRegister;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,18 +38,16 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractPersistentAcceptOnceFileListFilter<F> extends AbstractFileListFilter<F>
|
||||
implements ReversibleFileListFilter<F>, ResettableFileListFilter<F>, Closeable {
|
||||
implements ReversibleFileListFilter<F>, ResettableFileListFilter<F>, Closeable {
|
||||
|
||||
protected final ConcurrentMetadataStore store; // NOSONAR
|
||||
|
||||
protected final String prefix; // NOSONAR
|
||||
|
||||
@Nullable
|
||||
protected final Flushable flushableStore; // NOSONAR
|
||||
|
||||
protected final String prefix; // NOSONAR
|
||||
|
||||
protected volatile boolean flushOnUpdate; // NOSONAR
|
||||
|
||||
private final Object monitor = new Object();
|
||||
protected boolean flushOnUpdate; // NOSONAR
|
||||
|
||||
public AbstractPersistentAcceptOnceFileListFilter(ConcurrentMetadataStore store, String prefix) {
|
||||
Assert.notNull(store, "'store' cannot be null");
|
||||
@@ -76,20 +74,18 @@ public abstract class AbstractPersistentAcceptOnceFileListFilter<F> extends Abst
|
||||
@Override
|
||||
public boolean accept(F file) {
|
||||
String key = buildKey(file);
|
||||
synchronized (this.monitor) {
|
||||
String newValue = value(file);
|
||||
String oldValue = this.store.putIfAbsent(key, newValue);
|
||||
if (oldValue == null) { // not in store
|
||||
flushIfNeeded();
|
||||
return fileStillExists(file);
|
||||
}
|
||||
// same value in store
|
||||
if (!isEqual(file, oldValue) && this.store.replace(key, oldValue, newValue)) {
|
||||
flushIfNeeded();
|
||||
return fileStillExists(file);
|
||||
}
|
||||
return false;
|
||||
String newValue = value(file);
|
||||
String oldValue = this.store.putIfAbsent(key, newValue);
|
||||
if (oldValue == null) { // not in store
|
||||
flushIfNeeded();
|
||||
return fileStillExists(file);
|
||||
}
|
||||
// same value in store
|
||||
if (!isEqual(file, oldValue) && this.store.replace(key, oldValue, newValue)) {
|
||||
flushIfNeeded();
|
||||
return fileStillExists(file);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,7 +147,7 @@ public abstract class AbstractPersistentAcceptOnceFileListFilter<F> extends Abst
|
||||
* @return true if equal.
|
||||
*/
|
||||
protected boolean isEqual(F file, String value) {
|
||||
return Long.valueOf(value) == modified(file);
|
||||
return Long.parseLong(value) == modified(file);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -205,6 +205,7 @@ public abstract class AbstractRemoteFileStreamingMessageSource<F>
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE, session)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, file.getRemoteDirectory())
|
||||
.setHeader(FileHeaders.REMOTE_FILE, file.getFilename())
|
||||
.setHeader(FileHeaders.REMOTE_HOST_PORT, session.getHostPort())
|
||||
.setHeader(FileHeaders.REMOTE_FILE_INFO,
|
||||
this.fileInfoJson ? file.toJson() : file);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,6 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
|
||||
private final RemoteFileTemplate<F> remoteFileTemplate;
|
||||
|
||||
private final Command command;
|
||||
@@ -118,7 +117,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
public AbstractRemoteFileOutboundGateway(SessionFactory<F> sessionFactory,
|
||||
MessageSessionCallback<F, ?> messageSessionCallback) {
|
||||
|
||||
this(new RemoteFileTemplate<F>(sessionFactory), messageSessionCallback);
|
||||
this(new RemoteFileTemplate<>(sessionFactory), messageSessionCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +160,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
public AbstractRemoteFileOutboundGateway(SessionFactory<F> sessionFactory, Command command,
|
||||
@Nullable String expression) {
|
||||
|
||||
this(new RemoteFileTemplate<F>(sessionFactory), command, expression);
|
||||
this(new RemoteFileTemplate<>(sessionFactory), command, expression);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,7 +316,7 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
* @since 4.3
|
||||
*/
|
||||
public void setRenameExpression(Expression renameExpression) {
|
||||
this.renameProcessor = new ExpressionEvaluatingMessageProcessor<String>(renameExpression);
|
||||
this.renameProcessor = new ExpressionEvaluatingMessageProcessor<>(renameExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -490,10 +489,14 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
dir += this.remoteFileTemplate.getRemoteFileSeparator();
|
||||
}
|
||||
final String fullDir = dir;
|
||||
List<?> payload = this.remoteFileTemplate.execute(session -> ls(requestMessage, session, fullDir));
|
||||
return getMessageBuilderFactory()
|
||||
.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, dir);
|
||||
return this.remoteFileTemplate.execute(session -> {
|
||||
List<?> payload = ls(requestMessage, session, fullDir);
|
||||
return getMessageBuilderFactory()
|
||||
.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, fullDir)
|
||||
.setHeader(FileHeaders.REMOTE_HOST_PORT, session.getHostPort());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private Object doNlst(Message<?> requestMessage) {
|
||||
@@ -504,11 +507,13 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
dir += this.remoteFileTemplate.getRemoteFileSeparator();
|
||||
}
|
||||
final String fullDir = dir;
|
||||
List<?> payload = this.remoteFileTemplate.execute(session -> nlst(requestMessage, session, fullDir));
|
||||
|
||||
return getMessageBuilderFactory()
|
||||
.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, dir);
|
||||
return this.remoteFileTemplate.execute(session -> {
|
||||
List<?> payload = nlst(requestMessage, session, fullDir);
|
||||
return getMessageBuilderFactory()
|
||||
.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, fullDir)
|
||||
.setHeader(FileHeaders.REMOTE_HOST_PORT, session.getHostPort());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -541,6 +546,12 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
session = this.remoteFileTemplate.getSessionFactory().getSession();
|
||||
try {
|
||||
payload = session.readRaw(remoteFilePath);
|
||||
return getMessageBuilderFactory()
|
||||
.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.setHeader(FileHeaders.REMOTE_HOST_PORT, session.getHostPort())
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE, session);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new MessageHandlingException(requestMessage,
|
||||
@@ -549,26 +560,30 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
}
|
||||
}
|
||||
else {
|
||||
payload = this.remoteFileTemplate.execute(session1 ->
|
||||
get(requestMessage, session1, remoteDir, remoteFilePath, remoteFilename, null));
|
||||
return this.remoteFileTemplate.execute(session1 -> {
|
||||
Object getPayload = get(requestMessage, session1, remoteDir, remoteFilePath, remoteFilename, null);
|
||||
return getMessageBuilderFactory()
|
||||
.withPayload(getPayload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.setHeader(FileHeaders.REMOTE_HOST_PORT, session1.getHostPort());
|
||||
});
|
||||
}
|
||||
return getMessageBuilderFactory()
|
||||
.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.setHeader(IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE, session);
|
||||
}
|
||||
|
||||
private Object doMget(final Message<?> requestMessage) {
|
||||
String remoteFilePath = obtainRemoteFilePath(requestMessage);
|
||||
final String remoteFilename = getRemoteFilename(remoteFilePath);
|
||||
final String remoteDir = getRemoteDirectory(remoteFilePath, remoteFilename);
|
||||
List<File> payload = this.remoteFileTemplate.execute(session ->
|
||||
mGet(requestMessage, session, remoteDir, remoteFilename));
|
||||
return getMessageBuilderFactory()
|
||||
.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename);
|
||||
String remoteFilename = getRemoteFilename(remoteFilePath);
|
||||
String remoteDir = getRemoteDirectory(remoteFilePath, remoteFilename);
|
||||
return this.remoteFileTemplate.execute(session -> {
|
||||
List<File> payload = mGet(requestMessage, session, remoteDir, remoteFilename);
|
||||
return getMessageBuilderFactory()
|
||||
.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.setHeader(FileHeaders.REMOTE_HOST_PORT, session.getHostPort());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private Object doRm(Message<?> requestMessage) {
|
||||
@@ -576,12 +591,14 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
String remoteFilename = getRemoteFilename(remoteFilePath);
|
||||
String remoteDir = getRemoteDirectory(remoteFilePath, remoteFilename);
|
||||
|
||||
boolean payload = this.remoteFileTemplate.execute(session -> rm(requestMessage, session, remoteFilePath));
|
||||
|
||||
return getMessageBuilderFactory()
|
||||
.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename);
|
||||
return this.remoteFileTemplate.execute(session -> {
|
||||
boolean payload = rm(requestMessage, session, remoteFilePath);
|
||||
return getMessageBuilderFactory()
|
||||
.withPayload(payload)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.setHeader(FileHeaders.REMOTE_HOST_PORT, session.getHostPort());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -606,15 +623,16 @@ public abstract class AbstractRemoteFileOutboundGateway<F> extends AbstractReply
|
||||
String remoteFileNewPath = this.renameProcessor.processMessage(requestMessage);
|
||||
Assert.hasLength(remoteFileNewPath, "New filename cannot be empty");
|
||||
|
||||
Boolean result =
|
||||
this.remoteFileTemplate.execute(session ->
|
||||
mv(requestMessage, session, remoteFilePath, remoteFileNewPath));
|
||||
|
||||
return getMessageBuilderFactory()
|
||||
.withPayload(result)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.setHeader(FileHeaders.RENAME_TO, remoteFileNewPath);
|
||||
return this.remoteFileTemplate.execute(session -> {
|
||||
Boolean result = mv(requestMessage, session, remoteFilePath, remoteFileNewPath);
|
||||
return getMessageBuilderFactory()
|
||||
.withPayload(result)
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, remoteDir)
|
||||
.setHeader(FileHeaders.REMOTE_FILE, remoteFilename)
|
||||
.setHeader(FileHeaders.RENAME_TO, remoteFileNewPath)
|
||||
.setHeader(FileHeaders.REMOTE_HOST_PORT, session.getHostPort());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private String obtainRemoteFilePath(Message<?> requestMessage) {
|
||||
|
||||
@@ -37,11 +37,13 @@ import org.springframework.util.Assert;
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @author Alen Turkovic
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBean {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(CachingSessionFactory.class);
|
||||
private static final Log LOGGER = LogFactory.getLog(CachingSessionFactory.class);
|
||||
|
||||
private final SessionFactory<F> sessionFactory;
|
||||
|
||||
@@ -55,7 +57,6 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
|
||||
|
||||
/**
|
||||
* Create a CachingSessionFactory with an unlimited number of sessions.
|
||||
*
|
||||
* @param sessionFactory the underlying session factory.
|
||||
*/
|
||||
public CachingSessionFactory(SessionFactory<F> sessionFactory) {
|
||||
@@ -66,11 +67,9 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
|
||||
* Create a CachingSessionFactory with the specified session limit. By default, if
|
||||
* no sessions are available in the cache, and the size limit has been reached,
|
||||
* calling threads will block until a session is available.
|
||||
* <p>
|
||||
* Do not cache a {@link DelegatingSessionFactory}, cache each delegate therein instead.
|
||||
* <p> Do not cache a {@link DelegatingSessionFactory}, cache each delegate therein instead.
|
||||
* @see #setSessionWaitTimeout(long)
|
||||
* @see #setPoolSize(int)
|
||||
*
|
||||
* @param sessionFactory The underlying session factory.
|
||||
* @param sessionCacheSize The maximum cache size.
|
||||
*/
|
||||
@@ -78,7 +77,7 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
|
||||
Assert.isTrue(!(sessionFactory instanceof DelegatingSessionFactory),
|
||||
"'sessionFactory' cannot be a 'DelegatingSessionFactory'; cache each delegate instead");
|
||||
this.sessionFactory = sessionFactory;
|
||||
this.pool = new SimplePool<Session<F>>(sessionCacheSize, new SimplePool.PoolItemCallback<Session<F>>() {
|
||||
this.pool = new SimplePool<>(sessionCacheSize, new SimplePool.PoolItemCallback<Session<F>>() {
|
||||
@Override
|
||||
public Session<F> createForPool() {
|
||||
return CachingSessionFactory.this.sessionFactory.getSession();
|
||||
@@ -100,7 +99,6 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
|
||||
|
||||
/**
|
||||
* Sets the limit of how long to wait for a session to become available.
|
||||
*
|
||||
* @param sessionWaitTimeout the session wait timeout.
|
||||
* @throws IllegalStateException if the wait expires prior to a Session becoming available.
|
||||
*/
|
||||
@@ -111,7 +109,6 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
|
||||
/**
|
||||
* Modify the target session pool size; the actual pool size will adjust up/down
|
||||
* to this size as and when sessions are requested or retrieved.
|
||||
*
|
||||
* @param poolSize The pool size.
|
||||
*/
|
||||
public void setPoolSize(int poolSize) {
|
||||
@@ -148,9 +145,7 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
|
||||
* returned to the cache.
|
||||
*/
|
||||
public synchronized void resetCache() {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Cache reset; idle sessions will be removed, in-use sessions will be closed when returned");
|
||||
}
|
||||
LOGGER.debug("Cache reset; idle sessions will be removed, in-use sessions will be closed when returned");
|
||||
if (this.isSharedSessionCapable && ((SharedSessionCapable) this.sessionFactory).isSharedSession()) {
|
||||
((SharedSessionCapable) this.sessionFactory).resetSharedSession();
|
||||
}
|
||||
@@ -169,7 +164,7 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
|
||||
this.pool.removeAllIdleItems();
|
||||
}
|
||||
|
||||
public class CachedSession implements Session<F> { //NOSONAR (final)
|
||||
public class CachedSession implements Session<F> { //NOSONAR must be final, but can't for mocking in tests
|
||||
|
||||
private final Session<F> targetSession;
|
||||
|
||||
@@ -190,17 +185,17 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (this.released) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Session " + this.targetSession + " already released.");
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Session " + this.targetSession + " already released.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Releasing Session " + this.targetSession + " back to the pool.");
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Releasing Session " + this.targetSession + " back to the pool.");
|
||||
}
|
||||
if (this.sharedSessionEpoch != CachingSessionFactory.this.sharedSessionEpoch) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Closing session " + this.targetSession + " after reset.");
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Closing session " + this.targetSession + " after reset.");
|
||||
}
|
||||
this.targetSession.close();
|
||||
}
|
||||
@@ -295,6 +290,11 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
|
||||
return this.targetSession.getClientInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHostPort() {
|
||||
return this.targetSession.getHostPort();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -108,6 +108,13 @@ public interface Session<F> extends Closeable {
|
||||
*/
|
||||
Object getClientInstance();
|
||||
|
||||
/**
|
||||
* Return the host:port pair this session is connected to.
|
||||
* @return the host:port pair this session is connected to.
|
||||
* @since 5.2
|
||||
*/
|
||||
String getHostPort();
|
||||
|
||||
/**
|
||||
* Test the session is still alive, e.g. when checking out from a pool.
|
||||
* The default implementation simply delegates to {@link #isOpen()}.
|
||||
|
||||
@@ -22,6 +22,8 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
@@ -34,6 +36,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
@@ -48,10 +51,13 @@ import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.file.support.FileUtils;
|
||||
import org.springframework.integration.metadata.MetadataStore;
|
||||
import org.springframework.integration.metadata.SimpleMetadataStore;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Base class charged with knowing how to connect to a remote file system,
|
||||
@@ -72,7 +78,7 @@ import org.springframework.util.ObjectUtils;
|
||||
* @since 2.0
|
||||
*/
|
||||
public abstract class AbstractInboundFileSynchronizer<F>
|
||||
implements InboundFileSynchronizer, BeanFactoryAware, InitializingBean, Closeable {
|
||||
implements InboundFileSynchronizer, BeanFactoryAware, BeanNameAware, InitializingBean, Closeable {
|
||||
|
||||
protected static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
|
||||
|
||||
@@ -99,7 +105,7 @@ public abstract class AbstractInboundFileSynchronizer<F>
|
||||
/**
|
||||
* The current evaluation of the expression.
|
||||
*/
|
||||
private volatile String evaluatedRemoteDirectory;
|
||||
private String evaluatedRemoteDirectory;
|
||||
|
||||
/**
|
||||
* An {@link FileListFilter} that runs against the <em>remote</em> file system view.
|
||||
@@ -124,9 +130,14 @@ public abstract class AbstractInboundFileSynchronizer<F>
|
||||
@Nullable
|
||||
private Comparator<F> comparator;
|
||||
|
||||
private MetadataStore remoteFileMetadataStore = new SimpleMetadataStore();
|
||||
|
||||
private String metadataStorePrefix;
|
||||
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Create a synchronizer with the {@link SessionFactory} used to acquire {@link Session} instances.
|
||||
*
|
||||
* @param sessionFactory The session factory.
|
||||
*/
|
||||
public AbstractInboundFileSynchronizer(SessionFactory<F> sessionFactory) {
|
||||
@@ -250,11 +261,37 @@ public abstract class AbstractInboundFileSynchronizer<F>
|
||||
this.preserveTimestamp = preserveTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a {@link MetadataStore} to hold a remote file info (host, port, remote directory)
|
||||
* to transfer downstream in message headers when local file is pulled.
|
||||
* @param remoteFileMetadataStore the {@link MetadataStore} to use.
|
||||
* @since 5.2
|
||||
*/
|
||||
public void setRemoteFileMetadataStore(MetadataStore remoteFileMetadataStore) {
|
||||
this.remoteFileMetadataStore = remoteFileMetadataStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a prefix for keys in metadata store do not clash with other keys in the shared store.
|
||||
* @param metadataStorePrefix the prefix to use.
|
||||
* @since 5.2
|
||||
* @see #setRemoteFileMetadataStore(MetadataStore)
|
||||
*/
|
||||
public void setMetadataStorePrefix(String metadataStorePrefix) {
|
||||
this.metadataStorePrefix = metadataStorePrefix;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void afterPropertiesSet() {
|
||||
Assert.state(this.remoteDirectoryExpression != null, "'remoteDirectoryExpression' must not be null");
|
||||
@@ -262,6 +299,9 @@ public abstract class AbstractInboundFileSynchronizer<F>
|
||||
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.beanFactory);
|
||||
}
|
||||
evaluateRemoteDirectory();
|
||||
if (!StringUtils.hasText(this.metadataStorePrefix)) {
|
||||
this.metadataStorePrefix = this.name;
|
||||
}
|
||||
doInit();
|
||||
}
|
||||
|
||||
@@ -334,7 +374,8 @@ public abstract class AbstractInboundFileSynchronizer<F>
|
||||
|
||||
for (F file : filteredFiles) {
|
||||
if (filteringOneByOne) {
|
||||
if ((maxFetchSize < 0 || accepted < maxFetchSize) && this.filter.accept(file)) { // NOSONAR never null
|
||||
if ((maxFetchSize < 0 || accepted < maxFetchSize) && this.filter
|
||||
.accept(file)) { // NOSONAR never null
|
||||
accepted++;
|
||||
}
|
||||
else {
|
||||
@@ -358,7 +399,7 @@ public abstract class AbstractInboundFileSynchronizer<F>
|
||||
try {
|
||||
if (file != null && !copyFileToLocalDirectory(this.evaluatedRemoteDirectory, file,
|
||||
localDirectory, session)) {
|
||||
renamedFailed = false;
|
||||
renamedFailed = true;
|
||||
}
|
||||
}
|
||||
catch (RuntimeException | IOException e1) {
|
||||
@@ -452,6 +493,17 @@ public abstract class AbstractInboundFileSynchronizer<F>
|
||||
if (this.preserveTimestamp && !localFile.setLastModified(modified)) {
|
||||
throw new IllegalStateException("Could not sent last modified on file: " + localFile);
|
||||
}
|
||||
String[] hostPort = session.getHostPort().split(":");
|
||||
try {
|
||||
String remoteFileMetadata =
|
||||
new URI(protocol(), null, hostPort[0], Integer.parseInt(hostPort[1]),
|
||||
'/' + remoteDirectoryPath, null, remoteFileName)
|
||||
.toString();
|
||||
this.remoteFileMetadataStore.put(buildMetadataKey(localFile), remoteFileMetadata);
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
throw new IllegalStateException("Cannot create a remote file metadata", ex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
@@ -528,10 +580,44 @@ public abstract class AbstractInboundFileSynchronizer<F>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a metadata for remote file associated with the provided local file.
|
||||
* @param localFile the local file to retrieve metadata for.
|
||||
* @return the metadata for remove file in the URI style:
|
||||
* {@code protocol://host:port/remoteDirectory#remoteFileName}
|
||||
* @since 5.2
|
||||
*/
|
||||
@Nullable
|
||||
public String getRemoteFileMetadata(File localFile) {
|
||||
String metadataKey = buildMetadataKey(localFile);
|
||||
return this.remoteFileMetadataStore.get(metadataKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a metadata for remote file associated with the provided local file.
|
||||
* @param localFile the local file to remove metadata for.
|
||||
* @since 5.2
|
||||
*/
|
||||
public void removeRemoteFileMetadata(File localFile) {
|
||||
String metadataKey = buildMetadataKey(localFile);
|
||||
this.remoteFileMetadataStore.remove(metadataKey);
|
||||
}
|
||||
|
||||
private String buildMetadataKey(File file) {
|
||||
return this.metadataStorePrefix + file.getAbsolutePath();
|
||||
}
|
||||
|
||||
protected abstract boolean isFile(F file);
|
||||
|
||||
protected abstract String getFilename(F file);
|
||||
|
||||
protected abstract long getModified(F file);
|
||||
|
||||
/**
|
||||
* Return the protocol this synchronizer works with.
|
||||
* @return the protocol this synchronizer works with.
|
||||
* @since 5.2
|
||||
*/
|
||||
protected abstract String protocol();
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.integration.file.remote.synchronizer;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -28,6 +29,7 @@ import org.springframework.context.Lifecycle;
|
||||
import org.springframework.integration.endpoint.AbstractFetchLimitingMessageSource;
|
||||
import org.springframework.integration.file.DefaultDirectoryScanner;
|
||||
import org.springframework.integration.file.DirectoryScanner;
|
||||
import org.springframework.integration.file.FileHeaders;
|
||||
import org.springframework.integration.file.FileReadingMessageSource;
|
||||
import org.springframework.integration.file.filters.CompositeFileListFilter;
|
||||
import org.springframework.integration.file.filters.FileListFilter;
|
||||
@@ -95,7 +97,6 @@ public abstract class AbstractInboundFileSynchronizingMessageSource<F>
|
||||
|
||||
private volatile boolean running;
|
||||
|
||||
|
||||
public AbstractInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer<F> synchronizer) {
|
||||
this(synchronizer, null);
|
||||
}
|
||||
@@ -192,7 +193,7 @@ public abstract class AbstractInboundFileSynchronizingMessageSource<F>
|
||||
this.fileSource.setDirectory(this.localDirectory);
|
||||
initFiltersAndScanner();
|
||||
if (this.getBeanFactory() != null) {
|
||||
this.fileSource.setBeanFactory(this.getBeanFactory());
|
||||
this.fileSource.setBeanFactory(getBeanFactory());
|
||||
}
|
||||
this.fileSource.afterPropertiesSet();
|
||||
this.synchronizer.afterPropertiesSet();
|
||||
@@ -265,6 +266,16 @@ public abstract class AbstractInboundFileSynchronizingMessageSource<F>
|
||||
messageBuilder = this.fileSource.doReceive();
|
||||
}
|
||||
|
||||
if (messageBuilder != null) {
|
||||
String remoteFileUri = this.synchronizer.getRemoteFileMetadata(messageBuilder.getPayload());
|
||||
if (remoteFileUri != null) {
|
||||
URI uri = URI.create(remoteFileUri);
|
||||
messageBuilder.setHeader(FileHeaders.REMOTE_HOST_PORT, uri.getHost() + ':' + uri.getPort())
|
||||
.setHeader(FileHeaders.REMOTE_DIRECTORY, uri.getPath())
|
||||
.setHeader(FileHeaders.REMOTE_FILE, uri.getFragment());
|
||||
}
|
||||
}
|
||||
|
||||
return messageBuilder;
|
||||
}
|
||||
|
||||
@@ -274,7 +285,6 @@ public abstract class AbstractInboundFileSynchronizingMessageSource<F>
|
||||
Arrays.asList(this.localFileListFilter, new RegexPatternFileListFilter(completePattern)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The {@link FileReadingMessageSource} extension to increase visibility
|
||||
* for the {@link FileReadingMessageSource#doReceive()}
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
package org.springframework.integration.file.remote.gateway;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
@@ -58,6 +59,7 @@ import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.file.support.FileExistsMode;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageDeliveryException;
|
||||
import org.springframework.messaging.MessageHandlingException;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
@@ -81,11 +83,11 @@ public class RemoteFileOutboundGatewayTests {
|
||||
public final TemporaryFolder tempFolder = new TemporaryFolder();
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void testBad() {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway(sessionFactory, "bad", "payload");
|
||||
gw.afterPropertiesSet();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new TestRemoteFileOutboundGateway(sessionFactory, "bad", "payload"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -93,13 +95,10 @@ public class RemoteFileOutboundGatewayTests {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway(sessionFactory, "get", "payload");
|
||||
gw.setFilter(new TestPatternFilter(""));
|
||||
try {
|
||||
gw.afterPropertiesSet();
|
||||
fail("Exception expected");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage().startsWith("Filters are not supported")).isTrue();
|
||||
}
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(gw::afterPropertiesSet)
|
||||
.withMessageStartingWith("Filters are not supported");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -107,13 +106,10 @@ public class RemoteFileOutboundGatewayTests {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway(sessionFactory, "rm", "payload");
|
||||
gw.setFilter(new TestPatternFilter(""));
|
||||
try {
|
||||
gw.afterPropertiesSet();
|
||||
fail("Exception expected");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage().startsWith("Filters are not supported")).isTrue();
|
||||
}
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(gw::afterPropertiesSet)
|
||||
.withMessageStartingWith("Filters are not supported");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -139,11 +135,6 @@ public class RemoteFileOutboundGatewayTests {
|
||||
testMGetWildGuts("f1", "f2");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a wildcard mget where the full path is returned for each file
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testMGetWildFullPath() {
|
||||
testMGetWildGuts("testremote/f1", "testremote/f2");
|
||||
@@ -547,7 +538,7 @@ public class RemoteFileOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet() throws Exception {
|
||||
public void testGet() {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway(sessionFactory, "get", "payload");
|
||||
gw.setLocalDirectory(new File(this.tmpDir));
|
||||
@@ -609,22 +600,16 @@ public class RemoteFileOutboundGatewayTests {
|
||||
|
||||
// default (null)
|
||||
MessageBuilder<File> out;
|
||||
try {
|
||||
out = (MessageBuilder<File>) gw.handleRequestMessage(new GenericMessage<>("f1"));
|
||||
fail("Exception expected");
|
||||
}
|
||||
catch (MessageHandlingException e) {
|
||||
assertThat(e.getMessage()).contains("already exists");
|
||||
}
|
||||
|
||||
assertThatExceptionOfType(MessageHandlingException.class)
|
||||
.isThrownBy(() -> gw.handleRequestMessage(new GenericMessage<>("f1")))
|
||||
.withMessageContaining("already exists");
|
||||
|
||||
gw.setFileExistsMode(FileExistsMode.FAIL);
|
||||
try {
|
||||
out = (MessageBuilder<File>) gw.handleRequestMessage(new GenericMessage<>("f1"));
|
||||
fail("Exception expected");
|
||||
}
|
||||
catch (MessageHandlingException e) {
|
||||
assertThat(e.getMessage()).contains("already exists");
|
||||
}
|
||||
|
||||
assertThatExceptionOfType(MessageHandlingException.class)
|
||||
.isThrownBy(() -> gw.handleRequestMessage(new GenericMessage<>("f1")))
|
||||
.withMessageContaining("already exists");
|
||||
|
||||
gw.setFileExistsMode(FileExistsMode.IGNORE);
|
||||
out = (MessageBuilder<File>) gw.handleRequestMessage(new GenericMessage<>("f1"));
|
||||
@@ -652,7 +637,8 @@ public class RemoteFileOutboundGatewayTests {
|
||||
|
||||
@Test
|
||||
public void testGetTempFileDelete() {
|
||||
SessionFactory sessionFactory = mock(SessionFactory.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
SessionFactory<TestLsEntry> sessionFactory = mock(SessionFactory.class);
|
||||
TestRemoteFileOutboundGateway gw = new TestRemoteFileOutboundGateway(sessionFactory, "get", "payload");
|
||||
gw.setLocalDirectory(new File(this.tmpDir));
|
||||
gw.afterPropertiesSet();
|
||||
@@ -672,18 +658,15 @@ public class RemoteFileOutboundGatewayTests {
|
||||
}
|
||||
|
||||
});
|
||||
try {
|
||||
gw.handleRequestMessage(new GenericMessage<>("f1"));
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (MessagingException e) {
|
||||
assertThat(e.getCause()).isInstanceOf(RuntimeException.class);
|
||||
assertThat(e.getCause().getMessage()).isEqualTo("test remove .writing");
|
||||
@SuppressWarnings("unchecked")
|
||||
RemoteFileTemplate template = new RemoteFileTemplate(sessionFactory);
|
||||
File outFile = new File(this.tmpDir + "/f1" + template.getTemporaryFileSuffix());
|
||||
assertThat(outFile.exists()).isFalse();
|
||||
}
|
||||
|
||||
assertThatExceptionOfType(MessagingException.class)
|
||||
.isThrownBy(() -> gw.handleRequestMessage(new GenericMessage<>("f1")))
|
||||
.withCauseInstanceOf(RuntimeException.class)
|
||||
.withMessageContaining("test remove .writing");
|
||||
|
||||
RemoteFileTemplate<?> template = new RemoteFileTemplate<>(sessionFactory);
|
||||
File outFile = new File(this.tmpDir + "/f1" + template.getTemporaryFileSuffix());
|
||||
assertThat(outFile.exists()).isFalse();
|
||||
}
|
||||
|
||||
|
||||
@@ -743,8 +726,7 @@ public class RemoteFileOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(String source, OutputStream outputStream)
|
||||
throws IOException {
|
||||
public void read(String source, OutputStream outputStream) throws IOException {
|
||||
outputStream.write("testfile".getBytes());
|
||||
}
|
||||
|
||||
@@ -844,13 +826,10 @@ public class RemoteFileOutboundGatewayTests {
|
||||
verify(session).rename("foo/bar.txt.writing", "foo/bar.txt");
|
||||
|
||||
gw.setFileExistsMode(FileExistsMode.FAIL);
|
||||
try {
|
||||
path = (String) gw.handleRequestMessage(requestMessage);
|
||||
fail("Exception expected");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e.getMessage()).contains("The destination file already exists");
|
||||
}
|
||||
|
||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
||||
.isThrownBy(() -> gw.handleRequestMessage(requestMessage))
|
||||
.withMessageContaining("The destination file already exists");
|
||||
|
||||
gw.setFileExistsMode(FileExistsMode.REPLACE);
|
||||
path = (String) gw.handleRequestMessage(requestMessage);
|
||||
@@ -876,10 +855,9 @@ public class RemoteFileOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testMput() throws Exception {
|
||||
@SuppressWarnings("unchecked")
|
||||
SessionFactory<TestLsEntry> sessionFactory = mock(SessionFactory.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Session<TestLsEntry> session = mock(Session.class);
|
||||
RemoteFileTemplate<TestLsEntry> template = new RemoteFileTemplate<>(sessionFactory);
|
||||
template.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
|
||||
@@ -897,7 +875,6 @@ public class RemoteFileOutboundGatewayTests {
|
||||
tempFolder.newFile("qux.txt");
|
||||
Message<File> requestMessage = MessageBuilder.withPayload(tempFolder.getRoot())
|
||||
.build();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> out = (List<String>) gw.handleRequestMessage(requestMessage);
|
||||
assertThat(out.size()).isEqualTo(2);
|
||||
assertThat(out.get(0)).isNotEqualTo(out.get(1));
|
||||
@@ -906,10 +883,9 @@ public class RemoteFileOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testMputRecursive() throws Exception {
|
||||
@SuppressWarnings("unchecked")
|
||||
SessionFactory<TestLsEntry> sessionFactory = mock(SessionFactory.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Session<TestLsEntry> session = mock(Session.class);
|
||||
RemoteFileTemplate<TestLsEntry> template = new RemoteFileTemplate<>(sessionFactory);
|
||||
template.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
|
||||
@@ -931,7 +907,6 @@ public class RemoteFileOutboundGatewayTests {
|
||||
|
||||
Message<File> requestMessage = MessageBuilder.withPayload(tempFolder.getRoot())
|
||||
.build();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> out = (List<String>) gw.handleRequestMessage(requestMessage);
|
||||
assertThat(out.size()).isEqualTo(3);
|
||||
assertThat(out.get(0)).isNotEqualTo(out.get(1));
|
||||
@@ -941,10 +916,9 @@ public class RemoteFileOutboundGatewayTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testMputCollection() throws Exception {
|
||||
@SuppressWarnings("unchecked")
|
||||
SessionFactory<TestLsEntry> sessionFactory = mock(SessionFactory.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Session<TestLsEntry> session = mock(Session.class);
|
||||
RemoteFileTemplate<TestLsEntry> template = new RemoteFileTemplate<>(sessionFactory);
|
||||
template.setRemoteDirectoryExpression(new LiteralExpression("foo/"));
|
||||
@@ -963,7 +937,6 @@ public class RemoteFileOutboundGatewayTests {
|
||||
files.add(tempFolder.newFile("buz.txt"));
|
||||
Message<List<File>> requestMessage = MessageBuilder.withPayload(files)
|
||||
.build();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> out = (List<String>) gw.handleRequestMessage(requestMessage);
|
||||
assertThat(out.size()).isEqualTo(2);
|
||||
assertThat(out.get(0)).isNotEqualTo(out.get(1));
|
||||
@@ -1022,7 +995,7 @@ public class RemoteFileOutboundGatewayTests {
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
return this.open;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1050,6 +1023,11 @@ public class RemoteFileOutboundGatewayTests {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHostPort() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -17,13 +17,12 @@
|
||||
package org.springframework.integration.file.remote.session;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@@ -31,13 +30,15 @@ import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.file.remote.InputStreamCallback;
|
||||
import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
*/
|
||||
@@ -46,7 +47,7 @@ public class CachingSessionFactoryTests {
|
||||
@Test
|
||||
public void testCacheAndReset() {
|
||||
TestSessionFactory factory = new TestSessionFactory();
|
||||
CachingSessionFactory<String> cache = new CachingSessionFactory<String>(factory);
|
||||
CachingSessionFactory<String> cache = new CachingSessionFactory<>(factory);
|
||||
cache.setTestSession(true);
|
||||
Session<String> sess1 = cache.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(sess1, "targetSession.id")).isEqualTo("session:1");
|
||||
@@ -83,25 +84,24 @@ public class CachingSessionFactoryTests {
|
||||
when(factory.getSession()).thenReturn(session);
|
||||
when(session.readRaw("foo")).thenReturn(new ByteArrayInputStream("".getBytes()));
|
||||
when(session.finalizeRaw()).thenReturn(true);
|
||||
CachingSessionFactory<Object> ccf = new CachingSessionFactory<Object>(factory);
|
||||
RemoteFileTemplate<Object> template = new RemoteFileTemplate<Object>(ccf);
|
||||
CachingSessionFactory<Object> ccf = new CachingSessionFactory<>(factory);
|
||||
RemoteFileTemplate<Object> template = new RemoteFileTemplate<>(ccf);
|
||||
template.setFileNameExpression(new LiteralExpression("foo"));
|
||||
template.setBeanFactory(mock(BeanFactory.class));
|
||||
template.afterPropertiesSet();
|
||||
try {
|
||||
template.get(new GenericMessage<String>("foo"), (InputStreamCallback) stream -> {
|
||||
throw new RuntimeException("bar");
|
||||
});
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e.getCause()).isInstanceOf(RuntimeException.class);
|
||||
assertThat(e.getCause().getMessage()).isEqualTo("bar");
|
||||
}
|
||||
|
||||
assertThatExceptionOfType(MessagingException.class)
|
||||
.isThrownBy(() ->
|
||||
template.get(new GenericMessage<>("foo"),
|
||||
stream -> {
|
||||
throw new RuntimeException("bar");
|
||||
}))
|
||||
.withCauseInstanceOf(RuntimeException.class)
|
||||
.withMessageContaining("bar");
|
||||
verify(session).close();
|
||||
}
|
||||
|
||||
private class TestSessionFactory implements SessionFactory<String> {
|
||||
private static class TestSessionFactory implements SessionFactory<String> {
|
||||
|
||||
private int n;
|
||||
|
||||
@@ -112,7 +112,7 @@ public class CachingSessionFactoryTests {
|
||||
|
||||
}
|
||||
|
||||
private class TestSession implements Session<String> {
|
||||
private static class TestSession implements Session<String> {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final String id;
|
||||
@@ -127,39 +127,39 @@ public class CachingSessionFactoryTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(String path) throws IOException {
|
||||
public boolean remove(String path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] list(String path) throws IOException {
|
||||
public String[] list(String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(String source, OutputStream outputStream) throws IOException {
|
||||
public void read(String source, OutputStream outputStream) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(InputStream inputStream, String destination) throws IOException {
|
||||
public void write(InputStream inputStream, String destination) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(InputStream inputStream, String destination) throws IOException {
|
||||
public void append(InputStream inputStream, String destination) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mkdir(String directory) throws IOException {
|
||||
public boolean mkdir(String directory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rmdir(String directory) throws IOException {
|
||||
public boolean rmdir(String directory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(String pathFrom, String pathTo) throws IOException {
|
||||
public void rename(String pathFrom, String pathTo) {
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -173,22 +173,22 @@ public class CachingSessionFactoryTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String path) throws IOException {
|
||||
public boolean exists(String path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] listNames(String path) throws IOException {
|
||||
public String[] listNames(String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream readRaw(String source) throws IOException {
|
||||
public InputStream readRaw(String source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean finalizeRaw() throws IOException {
|
||||
public boolean finalizeRaw() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -197,6 +197,11 @@ public class CachingSessionFactoryTests {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHostPort() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test() {
|
||||
this.testCalled = true;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package org.springframework.integration.file.remote.synchronizer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.io.File;
|
||||
@@ -72,6 +72,11 @@ public class AbstractRemoteFileSynchronizerTests {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String protocol() {
|
||||
return "file";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean copyFileToLocalDirectory(String remoteDirectoryPath, String remoteFile,
|
||||
File localDirectory, Session<String> session) throws IOException {
|
||||
@@ -83,19 +88,14 @@ public class AbstractRemoteFileSynchronizerTests {
|
||||
}
|
||||
|
||||
};
|
||||
sync.setFilter(new AcceptOnceFileListFilter<String>());
|
||||
sync.setFilter(new AcceptOnceFileListFilter<>());
|
||||
sync.setRemoteDirectory("foo");
|
||||
|
||||
try {
|
||||
sync.synchronizeToLocalDirectory(mock(File.class));
|
||||
assertThat(count.get()).isEqualTo(1);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (MessagingException e) {
|
||||
assertThat(e.getCause()).isInstanceOf(MessagingException.class);
|
||||
assertThat(e.getCause().getCause()).isInstanceOf(IOException.class);
|
||||
assertThat(e.getCause().getCause().getMessage()).isEqualTo("fail");
|
||||
}
|
||||
assertThatExceptionOfType(MessagingException.class)
|
||||
.isThrownBy(() -> sync.synchronizeToLocalDirectory(mock(File.class)))
|
||||
.withRootCauseInstanceOf(IOException.class)
|
||||
.withMessageContaining("fail");
|
||||
|
||||
sync.synchronizeToLocalDirectory(mock(File.class));
|
||||
assertThat(count.get()).isEqualTo(3);
|
||||
sync.close();
|
||||
@@ -261,6 +261,11 @@ public class AbstractRemoteFileSynchronizerTests {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String protocol() {
|
||||
return "file";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean copyFileToLocalDirectory(String remoteDirectoryPath, String remoteFile,
|
||||
File localDirectory, Session<String> session) {
|
||||
@@ -269,13 +274,13 @@ public class AbstractRemoteFileSynchronizerTests {
|
||||
}
|
||||
|
||||
};
|
||||
sync.setFilter(new AcceptOnceFileListFilter<String>());
|
||||
sync.setFilter(new AcceptOnceFileListFilter<>());
|
||||
sync.setRemoteDirectory("foo");
|
||||
sync.setBeanFactory(mock(BeanFactory.class));
|
||||
return sync;
|
||||
}
|
||||
|
||||
private class StringSessionFactory implements SessionFactory<String> {
|
||||
private static class StringSessionFactory implements SessionFactory<String> {
|
||||
|
||||
@Override
|
||||
public Session<String> getSession() {
|
||||
@@ -284,7 +289,7 @@ public class AbstractRemoteFileSynchronizerTests {
|
||||
|
||||
}
|
||||
|
||||
private class StringSession implements Session<String> {
|
||||
private static class StringSession implements Session<String> {
|
||||
|
||||
StringSession() {
|
||||
super();
|
||||
@@ -360,6 +365,11 @@ public class AbstractRemoteFileSynchronizerTests {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHostPort() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user