INT-4487: Allow reconfiguring ARPMH.adviceChain

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

Currently the `AbstractReplyProducingMessageHandler` applies advices
only in the `afterPropertiesSet()` phase.
When we use bean references for `MessageHandler`s in Java DSL, the
`.advice()` provided in the flow definition is not applied to the
`AbstractReplyProducingMessageHandler`.

* For consistency with the endpoint configured by the DSL definition,
override and reconfigure advices in the `AbstractReplyProducingMessageHandler`
bean reference during DSL parsing and processing.

Fix `TwitterSearchOutboundGatewayParserTests` for the proper assert
This commit is contained in:
Artem Bilan
2018-06-21 13:05:15 -04:00
committed by Gary Russell
parent 0735ddb0ed
commit e6a35c4a60
7 changed files with 141 additions and 43 deletions

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.
@@ -75,26 +75,28 @@ public abstract class IntegrationObjectSupport implements BeanNameAware, NamedCo
private final ConversionService defaultConversionService = DefaultConversionService.getSharedInstance();
private volatile DestinationResolver<MessageChannel> channelResolver;
private DestinationResolver<MessageChannel> channelResolver;
private volatile String beanName;
private String beanName;
private volatile String componentName;
private String componentName;
private volatile BeanFactory beanFactory;
private BeanFactory beanFactory;
private volatile TaskScheduler taskScheduler;
private TaskScheduler taskScheduler;
private volatile Properties integrationProperties = IntegrationProperties.defaults();
private Properties integrationProperties = IntegrationProperties.defaults();
private volatile ConversionService conversionService;
private ConversionService conversionService;
private volatile ApplicationContext applicationContext;
private ApplicationContext applicationContext;
private volatile MessageBuilderFactory messageBuilderFactory;
private MessageBuilderFactory messageBuilderFactory;
private Expression expression;
private boolean initialized;
@Override
public final void setBeanName(String beanName) {
this.beanName = beanName;
@@ -181,6 +183,8 @@ public abstract class IntegrationObjectSupport implements BeanNameAware, NamedCo
}
throw new BeanInitializationException("failed to initialize", e);
}
this.initialized = true;
}
/**
@@ -190,6 +194,14 @@ public abstract class IntegrationObjectSupport implements BeanNameAware, NamedCo
protected void onInit() throws Exception {
}
/**
* Return the status of this component if it has been initialized already.
* @return the flag if this component has been initialized already.
*/
protected boolean isInitialized() {
return this.initialized;
}
protected BeanFactory getBeanFactory() {
return this.beanFactory;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2017 the original author or authors.
* Copyright 2016-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.
@@ -96,7 +96,9 @@ public class BarrierSpec extends ConsumerEndpointSpec<BarrierSpec, BarrierMessag
@Override
public Tuple2<ConsumerEndpointFactoryBean, BarrierMessageHandler> doGet() {
this.handler = new BarrierMessageHandler(this.timeout, this.outputProcessor, this.correlationStrategy);
this.handler.setAdviceChain(this.adviceChain);
if (!this.adviceChain.isEmpty()) {
this.handler.setAdviceChain(this.adviceChain);
}
this.handler.setRequiresReply(this.requiresReply);
this.handler.setSendTimeout(this.sendTimeout);
this.handler.setAsync(this.async);

View File

@@ -267,7 +267,7 @@ public abstract class ConsumerEndpointSpec<S extends ConsumerEndpointSpec<S, H>,
@Override
protected Tuple2<ConsumerEndpointFactoryBean, H> doGet() {
this.endpointFactoryBean.setAdviceChain(this.adviceChain);
if (this.handler instanceof AbstractReplyProducingMessageHandler) {
if (this.handler instanceof AbstractReplyProducingMessageHandler && !this.adviceChain.isEmpty()) {
((AbstractReplyProducingMessageHandler) this.handler).setAdviceChain(this.adviceChain);
}
this.endpointFactoryBean.setHandler(this.handler);

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.
@@ -16,6 +16,7 @@
package org.springframework.integration.handler;
import java.util.LinkedList;
import java.util.List;
import org.aopalliance.aop.Advice;
@@ -26,7 +27,6 @@ import org.springframework.integration.handler.advice.HandleMessageAdvice;
import org.springframework.messaging.Message;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
/**
* Base class for MessageHandlers that are capable of producing replies.
@@ -41,14 +41,14 @@ import org.springframework.util.CollectionUtils;
public abstract class AbstractReplyProducingMessageHandler extends AbstractMessageProducingHandler
implements BeanClassLoaderAware {
private final List<Advice> adviceChain = new LinkedList<>();
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private boolean requiresReply = false;
private volatile RequestHandler advisedRequestHandler;
private volatile List<Advice> adviceChain;
private volatile ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private volatile boolean requiresReply = false;
/**
* Flag whether a reply is required. If true an incoming message MUST result in a reply message being sent.
* If false an incoming message MAY result in a reply message being sent. Default is false.
@@ -62,13 +62,23 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
return this.requiresReply;
}
/**
* Configure a list of {@link Advice}s to proxy a {@link #handleRequestMessage(Message)} method.
* @param adviceChain the list of {@link Advice}s to use.
*/
public void setAdviceChain(List<Advice> adviceChain) {
Assert.notNull(adviceChain, "adviceChain cannot be null");
this.adviceChain = adviceChain;
Assert.notEmpty(adviceChain, "adviceChain cannot be empty");
synchronized (this.adviceChain) {
this.adviceChain.clear();
this.adviceChain.addAll(adviceChain);
if (isInitialized()) {
initAdvisedRequestHandlerIfAny();
}
}
}
protected boolean hasAdviceChain() {
return this.adviceChain != null && this.adviceChain.size() > 0;
return this.adviceChain.size() > 0;
}
@Override
@@ -80,7 +90,12 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
@Override
protected final void onInit() throws Exception {
super.onInit();
if (!CollectionUtils.isEmpty(this.adviceChain)) {
initAdvisedRequestHandlerIfAny();
doInit();
}
private void initAdvisedRequestHandlerIfAny() {
if (!this.adviceChain.isEmpty()) {
ProxyFactory proxyFactory = new ProxyFactory(new AdvisedRequestHandler());
boolean advised = false;
for (Advice advice : this.adviceChain) {
@@ -93,7 +108,6 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
this.advisedRequestHandler = (RequestHandler) proxyFactory.getProxy(this.beanClassLoader);
}
}
doInit();
}
protected void doInit() {
@@ -154,9 +168,6 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa
Object handleRequestMessage(Message<?> requestMessage);
@Override
String toString();
/**
* Utility method, intended for use in message handler advice classes to get
* information about the advised object. For example:

View File

@@ -25,7 +25,9 @@ import static org.junit.Assert.assertTrue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.aopalliance.intercept.MethodInterceptor;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,11 +39,12 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.Transformers;
import org.springframework.integration.dsl.context.IntegrationFlowContext;
import org.springframework.integration.dsl.context.IntegrationFlowContext.IntegrationFlowRegistration;
import org.springframework.integration.ip.tcp.TcpOutboundGateway;
import org.springframework.integration.ip.tcp.TcpReceivingChannelAdapter;
import org.springframework.integration.ip.tcp.TcpSendingMessageHandler;
import org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory;
@@ -60,6 +63,7 @@ import org.springframework.test.context.junit4.SpringRunner;
/**
* @author Gary Russell
* @author Artem Bilan
*
* @since 5.0
*
@@ -71,6 +75,9 @@ public class IpIntegrationTests {
@Autowired
private AbstractServerConnectionFactory server1;
@Autowired
private AbstractClientConnectionFactory client1;
@Autowired
private IntegrationFlowContext flowContext;
@@ -78,6 +85,10 @@ public class IpIntegrationTests {
@Qualifier("outUdpAdapter.input")
private MessageChannel udpOut;
@Autowired
@Qualifier("clientTcpFlow.input")
private MessageChannel clientTcpFlowInput;
@Autowired
private UnicastReceivingChannelAdapter udpInbound;
@@ -87,8 +98,11 @@ public class IpIntegrationTests {
@Autowired
private Config config;
@Autowired
private AtomicBoolean adviceCalled;
@Test
public void testTcpAdapters() throws Exception {
public void testTcpAdapters() {
ApplicationEventPublisher publisher = e -> { };
AbstractServerConnectionFactory server = Tcp.netServer(0).backlog(2).soTimeout(5000).id("server").get();
assertEquals("server", server.getComponentName());
@@ -117,15 +131,15 @@ public class IpIntegrationTests {
@Test
public void testTcpGateways() {
TestingUtilities.waitListening(this.server1, null);
IntegrationFlow flow = f -> f
.handle(Tcp.outboundGateway(Tcp.netClient("localhost", this.server1.getPort())
.serializer(TcpCodecs.crlf())
.deserializer(TcpCodecs.lengthHeader1())
.id("client1"))
.remoteTimeout(m -> 5000))
.transform(Transformers.objectToString());
IntegrationFlowRegistration theFlow = this.flowContext.registration(flow).register();
assertThat(theFlow.getMessagingTemplate().convertSendAndReceive("foo", String.class), equalTo("FOO"));
this.client1.stop();
this.client1.setPort(this.server1.getPort());
this.client1.start();
MessagingTemplate messagingTemplate = new MessagingTemplate(this.clientTcpFlowInput);
assertThat(messagingTemplate.convertSendAndReceive("foo", String.class), equalTo("FOO"));
assertTrue(this.adviceCalled.get());
}
@Test
@@ -204,6 +218,41 @@ public class IpIntegrationTests {
};
}
@Bean
public AbstractClientConnectionFactory client1() {
return Tcp.netClient("localhost", server1().getPort())
.serializer(TcpCodecs.crlf())
.deserializer(TcpCodecs.lengthHeader1())
.get();
}
@Bean
public TcpOutboundGateway tcpOut() {
return Tcp.outboundGateway(client1())
.remoteTimeout(m -> 5000)
.get();
}
@Bean
public AtomicBoolean adviceCalled() {
return new AtomicBoolean();
}
@Bean
public MethodInterceptor testAdvice() {
return invocation -> {
adviceCalled().set(true);
return invocation.proceed();
};
}
@Bean
public IntegrationFlow clientTcpFlow() {
return f -> f
.handle(tcpOut(), e -> e.advice(testAdvice()))
.transform(Transformers.objectToString());
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2014-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.
@@ -20,7 +20,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.Test;
@@ -39,6 +39,8 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Gary Russell
* @author Artem Bilan
*
* @since 4.0
*
*/
@@ -75,7 +77,7 @@ public class TwitterSearchOutboundGatewayParserTests {
@Test
public void testAdvised() {
assertSame(twitter, TestUtils.getPropertyValue(polledAndAdvisedTSOG, "handler.twitter"));
assertThat(TestUtils.getPropertyValue(polledAndAdvisedTSOG, "handler.adviceChain", ArrayList.class).get(0),
assertThat(TestUtils.getPropertyValue(polledAndAdvisedTSOG, "handler.adviceChain", List.class).get(0),
Matchers.instanceOf(RequestHandlerRetryAdvice.class));
}

View File

@@ -236,6 +236,28 @@ public IntegrationFlow flow2() {
In addition the `EndpointSpec` provides an `id()` method to allow you to register an endpoint bean with a given bean name, rather than a generated one.
If the `MessageHandler` is referenced as a bean, then any existing `adviceChain` configuration will be overridden if the `.advice()` method is present in the DSL definition:
[source,java]
----
@Bean
public TcpOutboundGateway tcpOut() {
TcpOutboundGateway gateway = new TcpOutboundGateway();
gateway.setConnectionFactory(cf());
gateway.setAdviceChain(Collections.singletonList(fooAdvice()));
return gateway;
}
@Bean
public IntegrationFlow clientTcpFlow() {
return f -> f
.handle(tcpOut(), e -> e.advice(testAdvice()))
.transform(Transformers.objectToString());
}
----
i.e. they are not merged, only the `testAdvice()` bean is used in this case.
[[java-dsl-transformers]]
=== Transformers