INT-3633: Add MessageSourceAdvice
JIRA: https://jira.spring.io/browse/INT-3633 INT-3633: Add SimpleActiveIdleMessageSourceAdvice Also resolve package tangle. Polishing and Docs Polishing according PR comments: * Fix JavaDocs vulnerabilities * Fix typos in docs * Remove unnecessary `AopUtils.canApply` check
This commit is contained in:
committed by
Artem Bilan
parent
0f05512e9a
commit
75ec449c0b
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2015 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.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* Advice for a {@link MessageSource#receive()} method to decide whether a poll
|
||||
* should be ignored and/or take action after the receive.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 4.2
|
||||
*/
|
||||
public abstract class AbstractMessageSourceAdvice implements MethodInterceptor {
|
||||
|
||||
@Override
|
||||
public final Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
Object target = invocation.getThis();
|
||||
if (!(target instanceof MessageSource)
|
||||
|| invocation.getMethod().getName() != "receive") {
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
Message<?> result = null;
|
||||
if (beforeReceive((MessageSource<?>) target)) {
|
||||
result = (Message<?>) invocation.proceed();
|
||||
}
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2015 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.integration.util.DynamicPeriodicTrigger;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* A simple advice that polls at one rate when messages exist and another when
|
||||
* there are no messages.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 4.2
|
||||
* @see DynamicPeriodicTrigger
|
||||
*/
|
||||
public class SimpleActiveIdleMessageSourceAdvice extends AbstractMessageSourceAdvice {
|
||||
|
||||
private final DynamicPeriodicTrigger trigger;
|
||||
|
||||
private volatile long idlePollPeriod;
|
||||
|
||||
private volatile long activePollPeriod;
|
||||
|
||||
|
||||
public SimpleActiveIdleMessageSourceAdvice(DynamicPeriodicTrigger trigger) {
|
||||
this.trigger = trigger;
|
||||
this.idlePollPeriod = trigger.getPeriod();
|
||||
this.activePollPeriod = trigger.getPeriod();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the poll period when messages are not returned. Defaults to the
|
||||
* trigger's period.
|
||||
* @param idlePollPeriod the period in milliseconds.
|
||||
*/
|
||||
public void setIdlePollPeriod(long idlePollPeriod) {
|
||||
this.idlePollPeriod = idlePollPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the poll period when messages are returned. Defaults to the
|
||||
* trigger's period.
|
||||
* @param activePollPeriod the period in milliseconds.
|
||||
*/
|
||||
public void setActivePollPeriod(long activePollPeriod) {
|
||||
this.activePollPeriod = activePollPeriod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean beforeReceive(MessageSource<?> source) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<?> afterReceive(Message<?> result, MessageSource<?> aource) {
|
||||
if (result == null) {
|
||||
this.trigger.setPeriod(this.idlePollPeriod);
|
||||
}
|
||||
else {
|
||||
this.trigger.setPeriod(this.activePollPeriod);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.integration.endpoint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -109,6 +111,27 @@ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implement
|
||||
this.transactionSynchronizationFactory = transactionSynchronizationFactory;
|
||||
}
|
||||
|
||||
protected ClassLoader getBeanClassLoader() {
|
||||
return beanClassLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this advice should be applied only to the {@link #receiveMessage()} operation
|
||||
* rather than the whole poll.
|
||||
* @param advice The advice.
|
||||
* @return true to only advise the receive operation.
|
||||
*/
|
||||
protected boolean isReceiveOnlyAdvice(Advice advice) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the advice chain to the component that responds to {@link #receiveMessage()} calls.
|
||||
* @param chain the advice chain {@code Collection}.
|
||||
*/
|
||||
protected void applyReceiveOnlyAdviceChain(Collection<Advice> chain) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
synchronized (this.initializationMonitor) {
|
||||
@@ -138,6 +161,14 @@ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implement
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Runnable createPoller() throws Exception {
|
||||
List<Advice> receiveOnlyAdviceChain = new ArrayList<Advice>();
|
||||
if (!CollectionUtils.isEmpty(adviceChain)) {
|
||||
for (Advice advice : adviceChain) {
|
||||
if (isReceiveOnlyAdvice(advice)) {
|
||||
receiveOnlyAdviceChain.add(advice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Callable<Boolean> pollingTask = new Callable<Boolean>() {
|
||||
@Override
|
||||
@@ -151,11 +182,16 @@ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implement
|
||||
ProxyFactory proxyFactory = new ProxyFactory(pollingTask);
|
||||
if (!CollectionUtils.isEmpty(adviceChain)) {
|
||||
for (Advice advice : adviceChain) {
|
||||
proxyFactory.addAdvice(advice);
|
||||
if (!isReceiveOnlyAdvice(advice)) {
|
||||
proxyFactory.addAdvice(advice);
|
||||
}
|
||||
}
|
||||
}
|
||||
pollingTask = (Callable<Boolean>) proxyFactory.getProxy(this.beanClassLoader);
|
||||
}
|
||||
if (receiveOnlyAdviceChain.size() > 0) {
|
||||
applyReceiveOnlyAdviceChain(receiveOnlyAdviceChain);
|
||||
}
|
||||
return new Poller(pollingTask);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,16 @@
|
||||
|
||||
package org.springframework.integration.endpoint;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.integration.aop.AbstractMessageSourceAdvice;
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.integration.core.MessagingTemplate;
|
||||
import org.springframework.integration.history.MessageHistory;
|
||||
@@ -92,6 +101,29 @@ public class SourcePollingChannelAdapter extends AbstractPollingEndpoint
|
||||
((NamedComponent) this.source).getComponentType() : "inbound-channel-adapter";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReceiveOnlyAdvice(Advice advice) {
|
||||
return advice instanceof AbstractMessageSourceAdvice;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyReceiveOnlyAdviceChain(Collection<Advice> chain) {
|
||||
if (AopUtils.isAopProxy(this.source)) {
|
||||
for (Advice advice : chain) {
|
||||
NameMatchMethodPointcutAdvisor sourceAdvice = new NameMatchMethodPointcutAdvisor(advice);
|
||||
sourceAdvice.addMethodName("receive");
|
||||
((Advised) this.source).addAdvice(advice);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ProxyFactory proxyFactory = new ProxyFactory(this.source);
|
||||
for (Advice advice : chain) {
|
||||
proxyFactory.addAdvice(advice);
|
||||
}
|
||||
this.source = (MessageSource<?>) proxyFactory.getProxy(getBeanClassLoader());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() {
|
||||
if (this.source instanceof Lifecycle) {
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.util;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.scheduling.Trigger;
|
||||
import org.springframework.scheduling.TriggerContext;
|
||||
import org.springframework.scheduling.support.PeriodicTrigger;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* This is a dynamically changeable {@link Trigger}. It is based on the
|
||||
* {@link PeriodicTrigger} implementations. However, the fields of this dynamic
|
||||
* trigger are not final and the properties can be inspected and set via
|
||||
* explicit getters and setters.
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
* @since 4.2
|
||||
*/
|
||||
public class DynamicPeriodicTrigger implements Trigger {
|
||||
|
||||
private volatile long period;
|
||||
|
||||
private volatile TimeUnit timeUnit;
|
||||
|
||||
private volatile long initialDelay = 0;
|
||||
|
||||
private volatile boolean fixedRate = false;
|
||||
|
||||
/**
|
||||
* Create a trigger with the given period in milliseconds. The underlying
|
||||
* {@link TimeUnit} will be initialized to TimeUnit.MILLISECONDS.
|
||||
* @param period Must not be negative
|
||||
*/
|
||||
public DynamicPeriodicTrigger(long period) {
|
||||
this(period, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a trigger with the given period and time unit. The time unit will
|
||||
* apply not only to the period but also to any 'initialDelay' value, if
|
||||
* configured on this Trigger later via {@link #setInitialDelay(long)}.
|
||||
* @param period Must not be negative
|
||||
* @param timeUnit Must not be null
|
||||
*/
|
||||
public DynamicPeriodicTrigger(long period, TimeUnit timeUnit) {
|
||||
Assert.isTrue(period >= 0, "period must not be negative");
|
||||
Assert.notNull(timeUnit, "timeUnit must not be null");
|
||||
|
||||
this.timeUnit = timeUnit;
|
||||
this.period = this.timeUnit.toMillis(period);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the delay for the initial execution. It will be evaluated in
|
||||
* terms of this trigger's {@link TimeUnit}. If no time unit was explicitly
|
||||
* provided upon instantiation, the default is milliseconds.
|
||||
* @param initialDelay the initial delay in milliseconds.
|
||||
*/
|
||||
public void setInitialDelay(long initialDelay) {
|
||||
Assert.isTrue(initialDelay >= 0, "initialDelay must not be negative");
|
||||
this.initialDelay = this.timeUnit.toMillis(initialDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether the periodic interval should be measured between the
|
||||
* scheduled start times rather than between actual completion times.
|
||||
* The latter, "fixed delay" behavior, is the default.
|
||||
* @param fixedRate the fixed rate {@code boolean} flag.
|
||||
*/
|
||||
public void setFixedRate(boolean fixedRate) {
|
||||
this.fixedRate = fixedRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the time after which a task should run again.
|
||||
* @param triggerContext the trigger context to determine the previous state of schedule.
|
||||
* @return the the next schedule date.
|
||||
*/
|
||||
@Override
|
||||
public Date nextExecutionTime(TriggerContext triggerContext) {
|
||||
if (triggerContext.lastScheduledExecutionTime() == null) {
|
||||
return new Date(System.currentTimeMillis() + this.initialDelay);
|
||||
}
|
||||
else if (this.fixedRate) {
|
||||
return new Date(triggerContext.lastScheduledExecutionTime().getTime() + this.period);
|
||||
}
|
||||
return new Date(triggerContext.lastCompletionTime().getTime() + this.period);
|
||||
}
|
||||
|
||||
public long getPeriod() {
|
||||
return period;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the period of the trigger. It will be evaluated in
|
||||
* terms of this trigger's {@link TimeUnit}. If no time unit was explicitly
|
||||
* provided upon instantiation, the default is milliseconds.
|
||||
* @param period Must not be negative
|
||||
*/
|
||||
public void setPeriod(long period) {
|
||||
Assert.isTrue(period >= 0, "period must not be negative");
|
||||
this.period = this.timeUnit.toMillis(period);
|
||||
}
|
||||
|
||||
public TimeUnit getTimeUnit() {
|
||||
return timeUnit;
|
||||
}
|
||||
|
||||
public void setTimeUnit(TimeUnit timeUnit) {
|
||||
Assert.notNull(timeUnit, "timeUnit must not be null");
|
||||
this.timeUnit = timeUnit;
|
||||
}
|
||||
|
||||
public long getInitialDelay() {
|
||||
return initialDelay;
|
||||
}
|
||||
|
||||
public boolean isFixedRate() {
|
||||
return fixedRate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof DynamicPeriodicTrigger)) {
|
||||
return false;
|
||||
}
|
||||
DynamicPeriodicTrigger other = (DynamicPeriodicTrigger) obj;
|
||||
return this.fixedRate == other.fixedRate
|
||||
&& this.initialDelay == other.initialDelay
|
||||
&& this.period == other.period;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (this.fixedRate ? 14 : 41) +
|
||||
(int) (38 * this.period) +
|
||||
(int) (43 * this.initialDelay);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2014-2015 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.
|
||||
@@ -16,25 +16,37 @@
|
||||
|
||||
package org.springframework.integration.endpoint;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.integration.aop.AbstractMessageSourceAdvice;
|
||||
import org.springframework.integration.aop.SimpleActiveIdleMessageSourceAdvice;
|
||||
import org.springframework.integration.channel.NullChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.core.MessageSource;
|
||||
import org.springframework.integration.scheduling.PollSkipAdvice;
|
||||
import org.springframework.integration.scheduling.PollSkipStrategy;
|
||||
import org.springframework.integration.util.DynamicPeriodicTrigger;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.scheduling.Trigger;
|
||||
import org.springframework.scheduling.TriggerContext;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
@@ -46,6 +58,8 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
*/
|
||||
public class PollerAdviceTests {
|
||||
|
||||
public Message<?> receiveAdviceResult;
|
||||
|
||||
@Test
|
||||
public void testDefaultDontSkip() throws Exception {
|
||||
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
|
||||
@@ -60,9 +74,13 @@ public class PollerAdviceTests {
|
||||
});
|
||||
adapter.setTrigger(new Trigger() {
|
||||
|
||||
private boolean done;
|
||||
|
||||
@Override
|
||||
public Date nextExecutionTime(TriggerContext triggerContext) {
|
||||
return new Date(System.currentTimeMillis() + 10);
|
||||
Date date = done ? null : new Date(System.currentTimeMillis() + 10);
|
||||
done = true;
|
||||
return date;
|
||||
}
|
||||
});
|
||||
configure(adapter);
|
||||
@@ -90,9 +108,13 @@ public class PollerAdviceTests {
|
||||
});
|
||||
adapter.setTrigger(new Trigger() {
|
||||
|
||||
private boolean done;
|
||||
|
||||
@Override
|
||||
public Date nextExecutionTime(TriggerContext triggerContext) {
|
||||
return new Date(System.currentTimeMillis() + 10);
|
||||
Date date = done ? null : new Date(System.currentTimeMillis() + 10);
|
||||
done = true;
|
||||
return date;
|
||||
}
|
||||
});
|
||||
configure(adapter);
|
||||
@@ -113,6 +135,108 @@ public class PollerAdviceTests {
|
||||
adapter.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixedAdvice() throws Exception {
|
||||
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
|
||||
final List<String> callOrder = new ArrayList<String>();
|
||||
final CountDownLatch latch = new CountDownLatch(4);// advice + advice + source + advice
|
||||
adapter.setSource(new MessageSource<Object>() {
|
||||
|
||||
@Override
|
||||
public Message<Object> receive() {
|
||||
callOrder.add("c");
|
||||
latch.countDown();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
adapter.setTrigger(new Trigger() {
|
||||
|
||||
private boolean done;
|
||||
|
||||
@Override
|
||||
public Date nextExecutionTime(TriggerContext triggerContext) {
|
||||
Date date = done ? null : new Date(System.currentTimeMillis() + 10);
|
||||
done = true;
|
||||
return date;
|
||||
}
|
||||
});
|
||||
configure(adapter);
|
||||
List<Advice> adviceChain = new ArrayList<Advice>();
|
||||
|
||||
class TestGeneralAdvice implements MethodInterceptor {
|
||||
|
||||
@Override
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
callOrder.add("a");
|
||||
latch.countDown();
|
||||
return invocation.proceed();
|
||||
}
|
||||
|
||||
}
|
||||
adviceChain.add(new TestGeneralAdvice());
|
||||
|
||||
class TestSourceAdvice extends AbstractMessageSourceAdvice {
|
||||
|
||||
@Override
|
||||
public boolean beforeReceive(MessageSource<?> target) {
|
||||
callOrder.add("b");
|
||||
latch.countDown();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<?> afterReceive(Message<?> result, MessageSource<?> target) {
|
||||
callOrder.add("d");
|
||||
latch.countDown();
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
adviceChain.add(new TestSourceAdvice());
|
||||
|
||||
adapter.setAdviceChain(adviceChain);
|
||||
adapter.afterPropertiesSet();
|
||||
adapter.start();
|
||||
assertTrue(latch.await(10, TimeUnit.SECONDS));
|
||||
assertThat(callOrder, contains("a", "b", "c", "d")); // advice + advice + source + advice
|
||||
adapter.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActiveIdleAdvice() throws Exception {
|
||||
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
|
||||
final CountDownLatch latch = new CountDownLatch(5);
|
||||
final LinkedList<Long> triggerPeriods = new LinkedList<Long>();
|
||||
final DynamicPeriodicTrigger trigger = new DynamicPeriodicTrigger(10);
|
||||
adapter.setSource(new MessageSource<Object>() {
|
||||
|
||||
@Override
|
||||
public Message<Object> receive() {
|
||||
triggerPeriods.add(trigger.getPeriod());
|
||||
Message<Object> m = null;
|
||||
if (latch.getCount() % 2 == 0) {
|
||||
m = new GenericMessage<Object>("foo");
|
||||
}
|
||||
latch.countDown();
|
||||
return m;
|
||||
}
|
||||
});
|
||||
QueueChannel channel = new QueueChannel();
|
||||
SimpleActiveIdleMessageSourceAdvice toggling = new SimpleActiveIdleMessageSourceAdvice(trigger);
|
||||
toggling.setActivePollPeriod(11);
|
||||
toggling.setIdlePollPeriod(12);
|
||||
adapter.setAdviceChain(Collections.singletonList(toggling));
|
||||
configure(adapter);
|
||||
adapter.afterPropertiesSet();
|
||||
adapter.start();
|
||||
assertTrue(latch.await(10, TimeUnit.SECONDS));
|
||||
adapter.stop();
|
||||
while (triggerPeriods.size() > 5) {
|
||||
triggerPeriods.removeLast();
|
||||
}
|
||||
assertThat(triggerPeriods, Matchers.contains(10L, 12L, 11L, 12L, 11L));
|
||||
}
|
||||
|
||||
private void configure(SourcePollingChannelAdapter adapter) {
|
||||
adapter.setOutputChannel(new NullChannel());
|
||||
adapter.setBeanFactory(mock(BeanFactory.class));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="polling-consumer"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Poller (Polling Consumer)</title>
|
||||
<title>Poller (Polling Consumer, Polling Message Source)</title>
|
||||
<para>
|
||||
When Message Endpoints (Channel Adapters) are connected to channels and
|
||||
instantiated, they produce one of the following 2 instances:
|
||||
@@ -66,7 +66,8 @@
|
||||
of the next poll. The <classname>PollSkipAdvice</classname> can be used to suppress (skip) a
|
||||
poll, perhaps because there is some downstream condition that would prevent the message to
|
||||
be processed properly. To use this advice, you have to provide it with an implementation
|
||||
of a <interfacename>PollSkipStrategy</interfacename>.
|
||||
of a <interfacename>PollSkipStrategy</interfacename>. <emphasis>Version 4.2</emphasis> added
|
||||
more flexibility in this area - see <xref linkend="conditional-pollers"/>.
|
||||
</note>
|
||||
<para>
|
||||
This chapter is meant to only give a high-level overview regarding Polling Consumers
|
||||
@@ -76,5 +77,72 @@
|
||||
Messaging Endpoints in general and Polling Consumers in particular, please see
|
||||
<xref linkend="endpoint"/>.
|
||||
</para>
|
||||
<section id="conditional-pollers">
|
||||
<title>Conditional Pollers</title>
|
||||
<para><emphasis role="bold">Background</emphasis></para>
|
||||
<para>
|
||||
<interfacename>Advice</interfacename> objects, in an <code>advice-chain</code> on a poller, advise
|
||||
the whole polling task (message retrieval and processing). These "around advice" objects do not
|
||||
have access to any context for the poll, just the poll itself. This is fine for requirements
|
||||
such as making a task transactional, or skipping a poll due to some external condition
|
||||
as discussed above. What if we wish to take some action depending on
|
||||
the result of the <code>receive</code> part of the poll, or if we want to adjust the poller
|
||||
depending on conditions?
|
||||
</para>
|
||||
<para><emphasis role="bold">"Smart" Polling</emphasis></para>
|
||||
<para>
|
||||
<emphasis>Version 4.2</emphasis> introduced the <classname>AbstractMessageSourceAdvice</classname>.
|
||||
Any <interfacename>Advice</interfacename> objects in the <code>advice-chain</code> that
|
||||
subclass this class, are applied to just the receive operation. Such classes implement the
|
||||
following methods:
|
||||
</para>
|
||||
<programlisting language="java"><![CDATA[boolean beforeReceive(MessageSource<?> source)]]></programlisting>
|
||||
<para>
|
||||
This method is called before the <interfacename>MessageSource</interfacename> <code>receive()</code>
|
||||
method. It enables you to examine and or reconfigure the source at this time. Returning
|
||||
false cancels this poll (similar to the <classname>PollSkipAdvice</classname> above).
|
||||
</para>
|
||||
<programlisting language="java"><![CDATA[
|
||||
Message<?> afterReceive(Message<?> result, MessageSource<?> source)]]></programlisting>
|
||||
<para>
|
||||
This method is called after the <code>receive()</code> method; again, you can reconfigure the
|
||||
source, or take any action perhaps depending on the result (<code>null</code> for no message).
|
||||
You can even return a different message!
|
||||
</para>
|
||||
<para><emphasis role="bold">SimpleActiveIdleMessageSourceAdvice</emphasis></para>
|
||||
<para>
|
||||
This advice is a simple implementation of <classname>AbstractMessageSourceAdvice</classname>,
|
||||
when used in conjunction with a <classname>DynamicPeriodicTrigger</classname> it adjusts
|
||||
the polling frequency depending on whether or not the previous poll resulted in a
|
||||
message or not. The poller must also have a reference to the same
|
||||
<classname>DynamicPeriodicTrigger</classname>.
|
||||
</para>
|
||||
<important>
|
||||
This advice modifies the trigger based on the receive result. This will only work if the
|
||||
advice is called on the poller thread. It will <emphasis role="bold">not</emphasis>
|
||||
work if the poller has a <code>task-executor</code>. To use this advice where you wish to
|
||||
use async operations after the result of a poll, do the async handoff later, perhaps
|
||||
by using an <classname>ExecutorChannel</classname>.
|
||||
</important>
|
||||
<para><emphasis role="bold">Advice Chain Ordering</emphasis></para>
|
||||
<important>
|
||||
<para>
|
||||
It is important to understand how the advice chain is processed during initialization.
|
||||
<interface>Advice</interface> objects that do not extend <classname>AbstractMessageSourceAdvice</classname>
|
||||
are applied to the whole poll process and are all invoked first, in order, before any
|
||||
<classname>AbstractMessageSourceAdvice</classname>; then <classname>AbstractMessageSourceAdvice</classname>
|
||||
objects are invoked in order around the <interfacename>MessageSource</interfacename> <code>receive()</code>
|
||||
method. If you have, say <interfacename>Advice</interfacename> objects <code>a, b, c, d</code>, where
|
||||
<code>b</code> and <code>d</code> are <classname>AbstractMessageSourceAdvice</classname>, they will be
|
||||
applied in the order <code>a, c, b, d</code>.
|
||||
</para>
|
||||
<para>
|
||||
Also, if a <interfacename>MessageSource</interfacename> is already a <interfacename>Proxy</interfacename>,
|
||||
the <classname>AbstractMessageSourceAdvice</classname> will be invoked after any existing
|
||||
<interfacename>Advice</interfacename> objects. If you wish to change the order, you should wire
|
||||
up the proxy yourself.
|
||||
</para>
|
||||
</important>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
@@ -118,5 +118,14 @@
|
||||
See <xref linkend="jms-message-driven-channel-adapter"/> for more information.
|
||||
</para>
|
||||
</section>
|
||||
<section id="4.2-conditional-pollers">
|
||||
<title>Conditional Pollers</title>
|
||||
<para>
|
||||
Much more flexibility is now provided for dynamic polling.
|
||||
</para>
|
||||
<para>
|
||||
See <xref linkend="conditional-pollers"/> for more information.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
Reference in New Issue
Block a user