INT-4491: (S)FTP inbound rotate dirs/servers

JIRA: https://jira.spring.io/browse/INT-4491

Add Rotating Server/Directory Polling Advice.

**cherry-pick to 5.0.x**

* Polishing - PR Comments.

* Polishing

* Polishing; revert `KeyDirectory`; WARN about `TaskExecutor` and `MessageSoureMutator`(s).

* More polishing - PR comments

* Apply stashed changes.

* Fix WARN log - the `SyncTaskExecutor` is wrapped.

# Conflicts:
#	spring-integration-core/src/main/java/org/springframework/integration/endpoint/SourcePollingChannelAdapter.java
#	src/reference/asciidoc/whats-new.adoc
This commit is contained in:
Gary Russell
2018-06-27 13:43:41 -04:00
committed by Artem Bilan
parent 170cc37270
commit 9f937f54cd
20 changed files with 930 additions and 68 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2018 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.
@@ -27,9 +27,10 @@ import org.springframework.messaging.Message;
* should be ignored and/or take action after the receive.
*
* @author Gary Russell
*
* @since 4.2
*/
public abstract class AbstractMessageSourceAdvice implements MethodInterceptor {
public abstract class AbstractMessageSourceAdvice implements MethodInterceptor, MessageSourceMutator {
@Override
public final Object invoke(MethodInvocation invocation) throws Throwable {
@@ -45,20 +46,4 @@ public abstract class AbstractMessageSourceAdvice implements MethodInterceptor {
return afterReceive(result, (MessageSource<?>) target);
}
/**
* Subclasses can decide whether to proceed with this poll.
* @param source the message source.
* @return true to proceed.
*/
public abstract boolean beforeReceive(MessageSource<?> source);
/**
* Subclasses can take actions based on the result of the poll; e.g.
* adjust the {@code trigger}. The message can also be replaced with a new one.
* @param result the received message.
* @param source the message source.
* @return a message to continue to process the result, null to discard whatever the poll returned.
*/
public abstract Message<?> afterReceive(Message<?> result, MessageSource<?> source);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2016 the original author or authors.
* Copyright 2015-2018 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.
@@ -47,11 +47,6 @@ public class CompoundTriggerAdvice extends AbstractMessageSourceAdvice {
this.override = overrideTrigger;
}
@Override
public boolean beforeReceive(MessageSource<?> source) {
return true;
}
@Override
public Message<?> afterReceive(Message<?> result, MessageSource<?> source) {
if (result == null) {

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2018 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.aop;
import org.springframework.integration.core.MessageSource;
import org.springframework.messaging.Message;
/**
* An object that can mutate a {@link MessageSource} before and/or after
* {@link MessageSource#receive()} is called.
*
* @author Gary Russell
*
* @since 5.0.7.
*
*/
@FunctionalInterface
public interface MessageSourceMutator {
/**
* Subclasses can decide whether to proceed with this poll.
* @param source the message source.
* @return true to proceed (default).
*/
default boolean beforeReceive(MessageSource<?> source) {
return true;
}
/**
* Subclasses can take actions based on the result of the poll; e.g.
* adjust the {@code trigger}. The message can also be replaced with a new one.
* @param result the received message.
* @param source the message source.
* @return a message to continue to process the result, null to discard whatever the poll returned.
*/
Message<?> afterReceive(Message<?> result, MessageSource<?> source);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2018 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.
@@ -61,11 +61,6 @@ public class SimpleActiveIdleMessageSourceAdvice extends AbstractMessageSourceAd
this.activePollPeriod = activePollPeriod;
}
@Override
public boolean beforeReceive(MessageSource<?> source) {
return true;
}
@Override
public Message<?> afterReceive(Message<?> result, MessageSource<?> source) {
if (result == null) {

View File

@@ -75,7 +75,7 @@ public final class PollerSpec extends IntegrationComponentSpec<PollerSpec, Polle
* to the {@link org.springframework.integration.util.ErrorHandlingTaskExecutor}.
* @param errorHandler the {@link ErrorHandler} to use.
* @return the spec.
* @see #taskExecutor
* @see #taskExecutor(Executor)
*/
public PollerSpec errorHandler(ErrorHandler errorHandler) {
this.target.setErrorHandler(errorHandler);

View File

@@ -60,17 +60,25 @@ import org.springframework.util.ErrorHandler;
*/
public abstract class AbstractPollingEndpoint extends AbstractEndpoint implements BeanClassLoaderAware {
private volatile Executor taskExecutor = new SyncTaskExecutor();
private final Object initializationMonitor = new Object();
private volatile ErrorHandler errorHandler;
private Executor taskExecutor = new SyncTaskExecutor();
private volatile boolean errorHandlerIsDefault;
private boolean syncExecutor = true;
private volatile Trigger trigger = new PeriodicTrigger(10);
private ErrorHandler errorHandler;
private volatile List<Advice> adviceChain;
private boolean errorHandlerIsDefault;
private volatile ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private Trigger trigger = new PeriodicTrigger(10);
private List<Advice> adviceChain;
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private long maxMessagesPerPoll = -1;
private TransactionSynchronizationFactory transactionSynchronizationFactory;
private volatile ScheduledFuture<?> runningTask;
@@ -78,18 +86,23 @@ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implement
private volatile boolean initialized;
private volatile long maxMessagesPerPoll = -1;
private final Object initializationMonitor = new Object();
private volatile TransactionSynchronizationFactory transactionSynchronizationFactory;
public AbstractPollingEndpoint() {
this.setPhase(Integer.MAX_VALUE / 2);
}
public void setTaskExecutor(Executor taskExecutor) {
this.taskExecutor = (taskExecutor != null ? taskExecutor : new SyncTaskExecutor());
this.syncExecutor = this.taskExecutor instanceof SyncTaskExecutor
|| (this.taskExecutor instanceof ErrorHandlingTaskExecutor
&& ((ErrorHandlingTaskExecutor) this.taskExecutor).isSyncExecutor());
}
protected Executor getTaskExecutor() {
return this.taskExecutor;
}
protected boolean isSyncExecutor() {
return this.syncExecutor;
}
public void setTrigger(Trigger trigger) {

View File

@@ -27,7 +27,7 @@ import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.Lifecycle;
import org.springframework.integration.aop.AbstractMessageSourceAdvice;
import org.springframework.integration.aop.MessageSourceMutator;
import org.springframework.integration.context.ExpressionCapable;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.core.MessagingTemplate;
@@ -137,7 +137,7 @@ public class SourcePollingChannelAdapter extends AbstractPollingEndpoint
@Override
protected boolean isReceiveOnlyAdvice(Advice advice) {
return advice instanceof AbstractMessageSourceAdvice;
return advice instanceof MessageSourceMutator;
}
@Override
@@ -159,6 +159,13 @@ public class SourcePollingChannelAdapter extends AbstractPollingEndpoint
}
this.appliedAdvices.clear();
this.appliedAdvices.addAll(chain);
if (!(isSyncExecutor()) && logger.isWarnEnabled()) {
logger.warn(getComponentName() + ": A task executor is supplied and " + chain.size()
+ "MessageSourceMutator(s) is/are provided. If an advice mutates the source, such "
+ "mutations are not thread safe and could cause unexpected results, especially with "
+ "high frequency pollers. Consider using a downstream ExecutorChannel instead of "
+ "adding an executor to the poller");
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
@@ -18,6 +18,7 @@ package org.springframework.integration.util;
import java.util.concurrent.Executor;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.util.Assert;
import org.springframework.util.ErrorHandler;
@@ -45,6 +46,9 @@ public class ErrorHandlingTaskExecutor implements TaskExecutor {
this.errorHandler = errorHandler;
}
public boolean isSyncExecutor() {
return this.executor instanceof SyncTaskExecutor;
}
@Override
public void execute(final Runnable task) {