From 08cbab08c2d2761fddb2143d5005794235f1f91e Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Sat, 14 Jul 2012 13:00:38 -0400 Subject: [PATCH] INT-2214, INT-343, INT-2250 MessageHandler Advice Add general capability to advise just the handleRequestMessage part of an AbstractReplyProducingMessageHandler. This is to advise just the immediate operation, and not the entire downstream flow. Uses include: * outbound gateway post processing * adding retry behavior using spring-retry * adding circuit breaker functionality Initial commit for review. Also need to advise simple message handlers (such as file etc) to allow them to post-process file operations with payload.delete(), payload.renameTo(...) etc. INT-2250 Add Circuit Breaker Advice INT-343 Add Retry Advice Stateless and Stateful retry using spring-retry. Stateless means the RetryTemplate performs the retries internally. Stateful means the exception is thrown (e.g. to JMS container) and the retry state is maintained by spring-retry. INT-2215, INT-343, INT-2250 Refactoring Factor out common abstract Advice class. INT-2214 Catch Evaluation Expression Exceptions If an onSuccess expression evaluation fails, add an option so the user can decide whether such an exception is caught, or propagated to the caller. INT-2214 etc PR Review Polishing INT-2214 etc Namespace Core, File, FTP Add to outbound endpoints. INT-2214 etc. More Namespace Support amqp, event, gemfire, groovy, http, ip, jdbc, jms, jmx, jpa, mail, rmi, sftp, twitter, ws, xmpp INT-2214 etc Polishing PR Review INT-2214 etc Polishing Don't catch Throwable. Move Advice classes to handler.advice package. --- build.gradle | 3 + .../config/spring-integration-amqp-2.2.xsd | 3 + ...boundChannelAdapterParserTests-context.xml | 6 +- ...AmqpOutboundChannelAdapterParserTests.java | 35 +- ...AmqpOutboundGatewayParserTests-context.xml | 6 +- .../AmqpOutboundGatewayParserTests.java | 26 +- .../integration/MessageHeaders.java | 10 +- ...stractSimpleMessageHandlerFactoryBean.java | 27 +- .../config/ConsumerEndpointFactoryBean.java | 52 +- .../xml/AbstractConsumerEndpointParser.java | 12 +- .../AbstractOutboundChannelAdapterParser.java | 70 ++- .../config/xml/IntegrationNamespaceUtils.java | 57 +++ .../integration/config/xml/PollerParser.java | 52 +- .../AbstractReplyProducingMessageHandler.java | 71 ++- .../handler/RetryStateGenerator.java | 31 ++ .../advice/AbstractRequestHandlerAdvice.java | 87 ++++ ...ressionEvaluatingRequestHandlerAdvice.java | 166 ++++++ .../RequestHandlerCircuitBreakerAdvice.java | 95 ++++ .../advice/RequestHandlerRetryAdvice.java | 87 ++++ .../handler/advice/package-info.java | 6 + .../config/xml/spring-integration-2.2.xsd | 48 +- .../config/FilterParserTests-context.xml | 6 +- .../integration/config/FilterParserTests.java | 14 +- .../xml/EnricherParserTests-context.xml | 4 + .../config/xml/EnricherParserTests.java | 29 +- .../ServiceActivatorParserTests-context.xml | 5 + .../xml/ServiceActivatorParserTests.java | 20 +- .../handler/AdvisedMessageHandlerTests.java | 477 ++++++++++++++++++ .../transformer/TransformerContextTests.java | 17 +- .../transformer/transformerContextTests.xml | 6 +- .../config/spring-integration-event-2.2.xsd | 5 +- ...boundChannelAdapterParserTests-context.xml | 8 + ...ventOutboundChannelAdapterParserTests.java | 42 +- .../file/FileWritingMessageHandler.java | 2 + .../FileOutboundChannelAdapterParser.java | 11 +- .../config/spring-integration-file-2.2.xsd | 5 +- ...boundChannelAdapterParserTests-context.xml | 6 +- ...FileOutboundChannelAdapterParserTests.java | 16 +- ...FileOutboundGatewayParserTests-context.xml | 6 +- .../FileOutboundGatewayParserTests.java | 14 + .../ftp/config/spring-integration-ftp-2.2.xsd | 6 + ...boundChannelAdapterParserTests-context.xml | 11 +- .../FtpOutboundChannelAdapterParserTests.java | 24 +- .../FtpOutboundGatewayParserTests-context.xml | 6 +- .../config/FtpOutboundGatewayParserTests.java | 17 + .../xml/spring-integration-gemfire-2.2.xsd | 5 +- ...boundChannelAdapterParserTests-context.xml | 21 + ...fireOutboundChannelAdapterParserTests.java | 49 +- .../config/spring-integration-groovy-2.2.xsd | 5 +- .../config/GroovyControlBusTests-context.xml | 6 +- .../groovy/config/GroovyControlBusTests.java | 17 +- .../config/spring-integration-http-2.2.xsd | 12 +- ...boundChannelAdapterParserTests-context.xml | 6 + ...HttpOutboundChannelAdapterParserTests.java | 26 +- ...HttpOutboundGatewayParserTests-context.xml | 6 + .../HttpOutboundGatewayParserTests.java | 25 +- .../ip/config/spring-integration-ip-2.2.xsd | 9 + .../ip/config/ParserUnitTests-context.xml | 35 ++ .../ip/config/ParserUnitTests.java | 49 ++ .../integration/jdbc/JdbcOutboundGateway.java | 1 + .../config/spring-integration-jdbc-2.2.xsd | 12 +- .../config/JdbcMessageHandlerParserTests.java | 31 +- .../JdbcOutboundGatewayParserTests.java | 14 + .../StoredProcMessageHandlerParserTests.java | 26 + .../StoredProcOutboundGatewayParserTests.java | 26 + ...edStoredProcOutboundChannelAdapterTest.xml | 33 ++ ...sedStoredProcOutboundGatewayParserTest.xml | 39 ++ ...dlingMapPayloadJdbcOutboundGatewayTest.xml | 6 +- ...erationsJdbcOutboundChannelAdapterTest.xml | 6 +- .../integration/jms/JmsOutboundGateway.java | 20 +- .../jms/config/spring-integration-jms-2.2.xsd | 10 +- .../JmsOutboundChannelAdapterParserTests.java | 30 +- .../config/JmsOutboundGatewayParserTests.java | 37 +- ...sOutboundGatewayWithDeliveryPersistent.xml | 11 +- ...undWithConnectionFactoryAndDestination.xml | 9 + .../jmx/OperationInvokingMessageHandler.java | 1 + ...icationPublishingChannelAdapterParser.java | 11 +- ...OperationInvokingChannelAdapterParser.java | 10 +- .../jmx/config/spring-integration-jmx-2.2.xsd | 9 + ...shingChannelAdapterParserTests-context.xml | 6 +- ...onPublishingChannelAdapterParserTests.java | 17 + ...okingChannelAdapterParserTests-context.xml | 6 +- ...tionInvokingChannelAdapterParserTests.java | 14 + ...onInvokingOutboundGatewayTests-context.xml | 6 +- ...OperationInvokingOutboundGatewayTests.java | 14 + .../xml/JpaOutboundChannelAdapterParser.java | 7 +- .../JpaOutboundGatewayFactoryBean.java | 78 +-- .../config/xml/spring-integration-jpa-2.2.xsd | 5 +- .../xml/JpaMessageHandlerParserTests.java | 58 ++- .../xml/JpaMessageHandlerParserTests.xml | 33 ++ .../xml/JpaOutboundGatewayParserTests.java | 25 + .../xml/JpaOutboundGatewayParserTests.xml | 15 + .../config/spring-integration-mail-2.2.xsd | 5 +- ...MailOutboundChannelAdapterParserTests.java | 38 +- .../mailOutboundChannelAdapterParserTests.xml | 7 + .../rmi/config/spring-integration-rmi-2.2.xsd | 5 +- .../config/RmiOutboundGatewayParserTests.java | 17 +- .../config/rmiOutboundGatewayParserTests.xml | 11 + .../config/spring-integration-sftp-2.2.xsd | 8 +- ...boundChannelAdapterParserTests-context.xml | 11 + .../OutboundChannelAdapterParserTests.java | 26 +- ...SftpOutboundGatewayParserTests-context.xml | 18 + .../SftpOutboundGatewayParserTests.java | 26 + .../config/spring-integration-twitter-2.2.xsd | 3 + ...estSendingMessageHandlerParser-context.xml | 14 + .../TestSendingMessageHandlerParserTests.java | 29 +- .../ws/config/spring-integration-ws-2.2.xsd | 1 + .../WebServiceOutboundGatewayParserTests.java | 37 +- ...leWebServiceOutboundGatewayParserTests.xml | 8 + .../config/spring-integration-xmpp-2.2.xsd | 5 +- ...boundChannelAdapterParserTests-context.xml | 7 + ...sageOutboundChannelAdapterParserTests.java | 43 +- ...boundChannelAdapterParserTests-context.xml | 10 + ...enceOutboundChannelAdapterParserTests.java | 24 +- 114 files changed, 2661 insertions(+), 296 deletions(-) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/handler/RetryStateGenerator.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/handler/advice/AbstractRequestHandlerAdvice.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/handler/advice/ExpressionEvaluatingRequestHandlerAdvice.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/handler/advice/RequestHandlerCircuitBreakerAdvice.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/handler/advice/RequestHandlerRetryAdvice.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/handler/advice/package-info.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/handler/AdvisedMessageHandlerTests.java create mode 100644 spring-integration-gemfire/src/test/java/org/springframework/integration/gemfire/config/xml/GemfireOutboundChannelAdapterParserTests-context.xml create mode 100644 spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/advisedStoredProcOutboundChannelAdapterTest.xml create mode 100644 spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/advisedStoredProcOutboundGatewayParserTest.xml diff --git a/build.gradle b/build.gradle index 79e81a0f62..6b6e68e5fa 100644 --- a/build.gradle +++ b/build.gradle @@ -57,6 +57,7 @@ subprojects { subproject -> springSecurityVersion = '3.1.0.RELEASE' springSocialTwitterVersion = '1.0.1.RELEASE' springWsVersion = '2.1.0.RELEASE' + springRetryVersion = '1.0.2.RELEASE' } eclipse { @@ -176,6 +177,7 @@ project('spring-integration-core') { compile "org.springframework:spring-aop:$springVersion" compile "org.springframework:spring-context:$springVersion" compile "org.springframework:spring-tx:$springVersion" + compile "org.springframework.retry:spring-retry:$springRetryVersion" compile("org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion", optional) testCompile "org.aspectj:aspectjrt:$aspectjVersion" testCompile "org.aspectj:aspectjweaver:$aspectjVersion" @@ -185,6 +187,7 @@ project('spring-integration-core') { importTemplate += [ 'org.springframework.*;version="[3.1.1, 4.0.0)"', 'org.springframework.transaction;version="[3.1.1, 4.0.0)";resolution:=optional', + 'org.springframework.retry;version="[1.0.2, 2.0.0)"', 'org.apache.commons.logging;version="[1.1.1, 2.0.0)"', 'org.aopalliance.*;version="[1.0.0, 2.0.0)"', 'org.codehaus.jackson.*;version="[1.0.0, 2.0.0)";resolution:=optional', diff --git a/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp-2.2.xsd b/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp-2.2.xsd index 9871a16ea4..67612af0e8 100644 --- a/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp-2.2.xsd +++ b/spring-integration-amqp/src/main/resources/org/springframework/integration/amqp/config/spring-integration-amqp-2.2.xsd @@ -368,6 +368,9 @@ Base type for the 'outbound-channel-adapter' and 'outbound-gateway' elements. + + + diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests-context.xml index 056f6c7bdd..ceaa5e15f4 100644 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests-context.xml @@ -9,7 +9,11 @@ http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> - + + + + + diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests.java index c9673f071d..9cad51a2c6 100644 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests.java +++ b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundChannelAdapterParserTests.java @@ -20,12 +20,13 @@ import static junit.framework.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.lang.reflect.Field; -import java.util.List; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.Test; @@ -48,10 +49,12 @@ import org.springframework.integration.amqp.AmqpHeaders; import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.NullChannel; +import org.springframework.integration.context.NamedComponent; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.core.PollableChannel; import org.springframework.integration.endpoint.EventDrivenConsumer; -import org.springframework.integration.handler.MessageHandlerChain; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; import org.springframework.test.context.ContextConfiguration; @@ -72,6 +75,8 @@ import com.rabbitmq.client.Channel; @RunWith(SpringJUnit4ClassRunner.class) public class AmqpOutboundChannelAdapterParserTests { + private static volatile int adviceCalled; + @Autowired private ApplicationContext context; @@ -82,16 +87,18 @@ public class AmqpOutboundChannelAdapterParserTests { assertEquals(DirectChannel.class, channel.getClass()); assertEquals(EventDrivenConsumer.class, adapter.getClass()); MessageHandler handler = TestUtils.getPropertyValue(adapter, "handler", MessageHandler.class); - assertEquals(AmqpOutboundEndpoint.class, handler.getClass()); - assertEquals("amqp:outbound-channel-adapter", ((AmqpOutboundEndpoint) handler).getComponentType()); + assertTrue(handler instanceof NamedComponent); + assertEquals("amqp:outbound-channel-adapter", ((NamedComponent) handler).getComponentType()); + handler.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); } - + @Test public void withHeaderMapperCustomHeaders() { Object eventDrivenConsumer = context.getBean("withHeaderMapperCustomHeaders"); - + AmqpOutboundEndpoint endpoint = TestUtils.getPropertyValue(eventDrivenConsumer, "handler", AmqpOutboundEndpoint.class); - + Field amqpTemplateField = ReflectionUtils.findField(AmqpOutboundEndpoint.class, "amqpTemplate"); amqpTemplateField.setAccessible(true); RabbitTemplate amqpTemplate = TestUtils.getPropertyValue(endpoint, "amqpTemplate", RabbitTemplate.class); @@ -110,8 +117,8 @@ public class AmqpOutboundChannelAdapterParserTests { .when(amqpTemplate).send(Mockito.any(String.class), Mockito.any(String.class), Mockito.any(org.springframework.amqp.core.Message.class), Mockito.any(CorrelationData.class)); ReflectionUtils.setField(amqpTemplateField, endpoint, amqpTemplate); - - + + MessageChannel requestChannel = context.getBean("requestChannel", MessageChannel.class); Message message = MessageBuilder.withPayload("hello").setHeader("foo", "foo").setHeader("bar", "bar").setHeader("foobar", "foobar").build(); requestChannel.send(message); @@ -214,4 +221,14 @@ public class AmqpOutboundChannelAdapterParserTests { assertEquals("bar", returned.getHeaders().get(AmqpHeaders.RETURN_ROUTING_KEY)); assertEquals("hello", returned.getPayload()); } + + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests-context.xml index db2ed30102..7b1e716385 100644 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests-context.xml +++ b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests-context.xml @@ -55,7 +55,11 @@ amqp-template="amqpTemplate" order="5" mapped-request-headers="" - mapped-reply-headers=""/> + mapped-reply-headers=""> + + + + diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests.java index d029f26bf2..ba8d596f60 100644 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests.java +++ b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/config/AmqpOutboundGatewayParserTests.java @@ -15,6 +15,12 @@ */ package org.springframework.integration.amqp.config; +import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + import java.lang.reflect.Field; import java.util.List; @@ -31,17 +37,11 @@ import org.springframework.integration.MessageChannel; import org.springframework.integration.amqp.AmqpHeaders; import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint; import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; import org.springframework.util.ReflectionUtils; -import static junit.framework.Assert.assertEquals; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - /** * @author Oleg Zhurakousky * @author Gary Russell @@ -53,6 +53,8 @@ import static org.junit.Assert.assertTrue; */ public class AmqpOutboundGatewayParserTests { + private static volatile int adviceCalled; + @Test public void testGatewayConfig(){ ApplicationContext context = new ClassPathXmlApplicationContext("AmqpOutboundGatewayParserTests-context.xml", this.getClass()); @@ -209,6 +211,7 @@ public class AmqpOutboundGatewayParserTests { assertNull(replyMessage.getHeaders().get(AmqpHeaders.DELIVERY_MODE)); assertNull(replyMessage.getHeaders().get(AmqpHeaders.CONTENT_TYPE)); assertNull(replyMessage.getHeaders().get(AmqpHeaders.APP_ID)); + assertEquals(1, adviceCalled); } @Test //INT-1029 @@ -261,4 +264,13 @@ public class AmqpOutboundGatewayParserTests { } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return callback.execute(); + } + + } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/MessageHeaders.java b/spring-integration-core/src/main/java/org/springframework/integration/MessageHeaders.java index 170df014d8..d6d38e18de 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/MessageHeaders.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/MessageHeaders.java @@ -91,6 +91,8 @@ public final class MessageHeaders implements Map, Serializable { public static final String DISPOSITION_RESULT = "dispositionResult"; + public static final String POSTPROCESS_RESULT = "postProcessResult"; + private final Map headers; @@ -164,12 +166,12 @@ public final class MessageHeaders implements Map, Serializable { } @Override - public boolean equals(Object obj) { - if (this == obj) { + public boolean equals(Object object) { + if (this == object) { return true; } - if (obj != null && obj instanceof MessageHeaders) { - MessageHeaders other = (MessageHeaders) obj; + if (object != null && object instanceof MessageHeaders) { + MessageHeaders other = (MessageHeaders) object; return this.headers.equals(other.headers); } return false; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractSimpleMessageHandlerFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractSimpleMessageHandlerFactoryBean.java index 3012642196..92e6bba117 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractSimpleMessageHandlerFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractSimpleMessageHandlerFactoryBean.java @@ -1,11 +1,11 @@ /* - * Copyright 2002-2011 the original author or authors. - * + * Copyright 2002-2012 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. @@ -13,6 +13,9 @@ package org.springframework.integration.config; +import java.util.List; + +import org.aopalliance.aop.Advice; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -23,11 +26,14 @@ import org.springframework.integration.MessageChannel; import org.springframework.integration.context.Orderable; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.core.MessageProducer; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; /** * @author Dave Syer * @author Oleg Zhurakousky + * @author Gary Russell */ public abstract class AbstractSimpleMessageHandlerFactoryBean implements FactoryBean, BeanFactoryAware { @@ -43,6 +49,8 @@ public abstract class AbstractSimpleMessageHandlerFactoryBean adviceChain; + public AbstractSimpleMessageHandlerFactoryBean() { super(); @@ -64,6 +72,10 @@ public abstract class AbstractSimpleMessageHandlerFactoryBean adviceChain) { + this.adviceChain = adviceChain; + } + public H getObject() throws Exception { if (this.handler == null) { this.handler = this.createHandlerInternal(); @@ -71,9 +83,6 @@ public abstract class AbstractSimpleMessageHandlerFactoryBean adviceChain; + public void setHandler(MessageHandler handler) { Assert.notNull(handler, "handler must not be null"); synchronized (this.handlerMonitor) { @@ -94,7 +103,7 @@ public class ConsumerEndpointFactoryBean public void setPollerMetadata(PollerMetadata pollerMetadata) { this.pollerMetadata = pollerMetadata; } - + public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; } @@ -112,6 +121,11 @@ public class ConsumerEndpointFactoryBean this.beanFactory = (ConfigurableBeanFactory) beanFactory; } + public void setAdviceChain(List adviceChain) { + Assert.notNull(adviceChain, "adviceChain must not be null"); + this.adviceChain = adviceChain; + } + public void afterPropertiesSet() throws Exception { try { if (!this.beanName.startsWith("org.springframework")) { @@ -132,6 +146,32 @@ public class ConsumerEndpointFactoryBean + this.handler + " for " + this.beanName + " :" + e.getMessage()); } } + if (!CollectionUtils.isEmpty(this.adviceChain)) { + /* + * ARPMHs advise the handleRequesMessage method internally and already have the advice chain injected. + * So we only advise handlers that are not reply-producing. If the handler is already advised, + * add the configured advices to its chain, otherwise create a proxy. + */ + if (!(this.handler instanceof AbstractReplyProducingMessageHandler)) { + if (AopUtils.isAopProxy(this.handler) && this.handler instanceof Advised) { + Class targetClass = AopUtils.getTargetClass(this.handler); + for (Advice advice : this.adviceChain) { + NameMatchMethodPointcutAdvisor handlerAdvice = new NameMatchMethodPointcutAdvisor(advice); + handlerAdvice.addMethodName("handleMessage"); + if (AopUtils.canApply(handlerAdvice.getPointcut(), targetClass)) { + ((Advised) this.handler).addAdvice(advice); + } + } + } + else { + ProxyFactory proxyFactory = new ProxyFactory(this.handler); + for (Advice advice : this.adviceChain) { + proxyFactory.addAdvice(advice); + } + this.handler = (MessageHandler) proxyFactory.getProxy(this.beanClassLoader); + } + } + } this.initializeEndpoint(); } @@ -184,9 +224,9 @@ public class ConsumerEndpointFactoryBean pollingConsumer.setTrigger(this.pollerMetadata.getTrigger()); pollingConsumer.setAdviceChain(this.pollerMetadata.getAdviceChain()); pollingConsumer.setMaxMessagesPerPoll(this.pollerMetadata.getMaxMessagesPerPoll()); - + pollingConsumer.setErrorHandler(this.pollerMetadata.getErrorHandler()); - + pollingConsumer.setReceiveTimeout(this.pollerMetadata.getReceiveTimeout()); pollingConsumer.setBeanClassLoader(beanClassLoader); pollingConsumer.setBeanFactory(beanFactory); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractConsumerEndpointParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractConsumerEndpointParser.java index 88995538c8..4629ff3874 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractConsumerEndpointParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractConsumerEndpointParser.java @@ -19,8 +19,7 @@ package org.springframework.integration.config.xml; import java.util.Collection; import java.util.List; -import org.w3c.dom.Element; - +import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; @@ -32,10 +31,10 @@ import org.springframework.beans.factory.support.ManagedSet; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.ConsumerEndpointFactoryBean; -import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; /** * Base class parser for elements that create Message Endpoints. @@ -92,6 +91,11 @@ public abstract class AbstractConsumerEndpointParser extends AbstractBeanDefinit return handlerBeanDefinition; } + Element adviceChainElement = DomUtils.getChildElementByTagName(element, + IntegrationNamespaceUtils.REQUEST_HANDLER_ADVICE_CHAIN); + IntegrationNamespaceUtils.configureAndSetAdviceChainIfPresent(adviceChainElement, null, + handlerBuilder, parserContext); + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerEndpointFactoryBean.class); String handlerBeanName = BeanDefinitionReaderUtils.generateBeanName(handlerBeanDefinition, parserContext.getRegistry()); @@ -99,6 +103,7 @@ public abstract class AbstractConsumerEndpointParser extends AbstractBeanDefinit parserContext.registerBeanComponent(new BeanComponentDefinition(handlerBeanDefinition, handlerBeanName, handlerAlias)); builder.addPropertyReference("handler", handlerBeanName); + String inputChannelName = element.getAttribute(inputChannelAttributeName); if (!parserContext.getRegistry().containsBeanDefinition(inputChannelName)){ @@ -136,4 +141,5 @@ public abstract class AbstractConsumerEndpointParser extends AbstractBeanDefinit parserContext.registerBeanComponent(new BeanComponentDefinition(beanDefinition, beanName)); return null; } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractOutboundChannelAdapterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractOutboundChannelAdapterParser.java index 00386f3b7d..6d77f21953 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractOutboundChannelAdapterParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractOutboundChannelAdapterParser.java @@ -16,16 +16,18 @@ package org.springframework.integration.config.xml; -import org.w3c.dom.Element; - +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.ConsumerEndpointFactoryBean; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; /** * Base class for outbound Channel Adapter parsers. @@ -34,7 +36,7 @@ import org.springframework.util.xml.DomUtils; * an {@link org.springframework.integration.endpoint.AbstractEndpoint} depending on the channel type. * If this component is defined as nested element (e.g., inside of the chain) it will produce * a {@link org.springframework.integration.core.MessageHandler}. - * + * * @author Mark Fisher * @author Gary Russell * @author Artem Bilan @@ -48,7 +50,8 @@ public abstract class AbstractOutboundChannelAdapterParser extends AbstractChann } BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerEndpointFactoryBean.class); Element pollerElement = DomUtils.getChildElementByTagName(element, "poller"); - builder.addPropertyReference("handler", this.parseAndRegisterConsumer(element, parserContext)); + BeanComponentDefinition handlerBeanComponentDefinition = this.doParseAndRegisterConsumer(element, parserContext); + builder.addPropertyReference("handler", handlerBeanComponentDefinition.getBeanName()); if (pollerElement != null) { if (!StringUtils.hasText(channelName)) { parserContext.getReaderContext().error( @@ -58,6 +61,40 @@ public abstract class AbstractOutboundChannelAdapterParser extends AbstractChann } builder.addPropertyValue("inputChannelName", channelName); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-startup"); + + Element adviceChainElement = DomUtils.getChildElementByTagName(element, + IntegrationNamespaceUtils.REQUEST_HANDLER_ADVICE_CHAIN); + @SuppressWarnings("rawtypes") + ManagedList adviceChain = IntegrationNamespaceUtils.configureAdviceChain(adviceChainElement, null, + builder, parserContext); + if (adviceChain != null) { + BeanDefinition handlerBeanDefinition = handlerBeanComponentDefinition.getBeanDefinition(); + /* + * For ARPMH, the advice chain is injected so just the handleRequestMessage method is advised. + * Sometime ARPMHs do double duty as a gateway and a channel adapter. The parser subclass + * can indicate this by overriding isUsingReplyProducer(), or we can try to determine it from + * the bean class. + */ + boolean isReplyProducer = this.isUsingReplyProducer(); + if (!isReplyProducer) { + Class beanClass = null; + if (handlerBeanDefinition instanceof AbstractBeanDefinition) { + AbstractBeanDefinition abstractBeanDefinition = (AbstractBeanDefinition) handlerBeanDefinition; + if (abstractBeanDefinition.hasBeanClass()) { + beanClass = abstractBeanDefinition.getBeanClass(); + } + } + isReplyProducer = beanClass != null && AbstractReplyProducingMessageHandler.class.isAssignableFrom(beanClass); + } + + if (isReplyProducer) { + handlerBeanDefinition.getPropertyValues().add("adviceChain", adviceChain); + } + else { + builder.addPropertyValue("adviceChain", adviceChain); + } + } + return builder.getBeanDefinition(); } @@ -65,8 +102,19 @@ public abstract class AbstractOutboundChannelAdapterParser extends AbstractChann * Override this method to control the registration process and return the bean name. * If parsing a bean definition whose name can be auto-generated, consider using * {@link #parseConsumer(Element, ParserContext)} instead. + * @deprecated Use {@link #doParseAndRegisterConsumer(Element, ParserContext)} */ + @Deprecated protected String parseAndRegisterConsumer(Element element, ParserContext parserContext) { + return doParseAndRegisterConsumer(element, parserContext).getBeanName(); + } + + /** + * Override this method to control the registration process and return the bean name. + * If parsing a bean definition whose name can be auto-generated, consider using + * {@link #parseConsumer(Element, ParserContext)} instead. + */ + protected BeanComponentDefinition doParseAndRegisterConsumer(Element element, ParserContext parserContext) { AbstractBeanDefinition definition = this.parseConsumer(element, parserContext); if (definition == null) { parserContext.getReaderContext().error("Consumer parsing must return an AbstractBeanDefinition.", element); @@ -77,8 +125,9 @@ public abstract class AbstractOutboundChannelAdapterParser extends AbstractChann } String beanName = BeanDefinitionReaderUtils.generateBeanName(definition, parserContext.getRegistry()); String[] handlerAlias = IntegrationNamespaceUtils.generateAlias(element); - parserContext.registerBeanComponent(new BeanComponentDefinition(definition, beanName, handlerAlias)); - return beanName; + BeanComponentDefinition beanComponentDefinition = new BeanComponentDefinition(definition, beanName, handlerAlias); + parserContext.registerBeanComponent(beanComponentDefinition); + return beanComponentDefinition; } /** @@ -87,4 +136,13 @@ public abstract class AbstractOutboundChannelAdapterParser extends AbstractChann */ protected abstract AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext); + /** + * Override this to signal that this channel adapter is actually using a AbstractReplyProducingMessageHandler + * while it is not possible for this parser to determine that because, say, a FactoryBean is being used. + * @return false, unless overridden. + */ + protected boolean isUsingReplyProducer() { + return false; + } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java index eb86a327ad..2c2fe14fe9 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceUtils.java @@ -19,9 +19,11 @@ import java.util.List; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.core.Conventions; @@ -33,6 +35,8 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; /** * Shared utility methods for integration namespace parsers. @@ -54,6 +58,7 @@ public abstract class IntegrationNamespaceUtils { static final String ORDER = "order"; static final String EXPRESSION_ATTRIBUTE = "expression"; public static final String HANDLER_ALIAS_SUFFIX = ".handler"; + public static final String REQUEST_HANDLER_ADVICE_CHAIN = "request-handler-advice-chain"; /** * Property name on ChannelInitializer used to configure the default max subscribers for @@ -308,4 +313,56 @@ public abstract class IntegrationNamespaceUtils { } return handlerAlias; } + + @SuppressWarnings({ "rawtypes" }) + public static void configureAndSetAdviceChainIfPresent(Element adviceChainElement, Element txElement, + BeanDefinitionBuilder parentBuilder, ParserContext parserContext) { + ManagedList adviceChain = configureAdviceChain(adviceChainElement, txElement, parentBuilder, parserContext); + if (adviceChain != null) { + parentBuilder.addPropertyValue("adviceChain", adviceChain); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static ManagedList configureAdviceChain(Element adviceChainElement, Element txElement, + BeanDefinitionBuilder parentBuilder, ParserContext parserContext) { + ManagedList adviceChain = null; + // Schema validation ensures txElement and adviceChainElement are mutually exclusive + if (txElement != null) { + adviceChain = new ManagedList(); + adviceChain.add(IntegrationNamespaceUtils.configureTransactionAttributes(txElement)); + } + if (adviceChainElement != null) { + adviceChain = new ManagedList(); + NodeList childNodes = adviceChainElement.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node child = childNodes.item(i); + if (child.getNodeType() == Node.ELEMENT_NODE) { + Element childElement = (Element) child; + String localName = child.getLocalName(); + if ("bean".equals(localName)) { + BeanDefinitionHolder holder = parserContext.getDelegate().parseBeanDefinitionElement( + childElement, parentBuilder.getBeanDefinition()); + parserContext.registerBeanComponent(new BeanComponentDefinition(holder)); + adviceChain.add(new RuntimeBeanReference(holder.getBeanName())); + } + else if ("ref".equals(localName)) { + String ref = childElement.getAttribute("bean"); + adviceChain.add(new RuntimeBeanReference(ref)); + } + else { + BeanDefinition customBeanDefinition = parserContext.getDelegate().parseCustomElement( + childElement, parentBuilder.getBeanDefinition()); + if (customBeanDefinition == null) { + parserContext.getReaderContext().error( + "failed to parse custom element '" + localName + "'", childElement); + } + adviceChain.add(customBeanDefinition); + } + } + } + } + return adviceChain; + } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PollerParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PollerParser.java index 19bc9d56a9..9d609eada3 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PollerParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PollerParser.java @@ -20,14 +20,9 @@ import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanDefinitionHolder; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; -import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; @@ -40,8 +35,6 @@ import org.springframework.scheduling.support.PeriodicTrigger; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; /** * Parser for the <poller> element. @@ -94,7 +87,8 @@ public class PollerParser extends AbstractBeanDefinitionParser { Element txElement = DomUtils.getChildElementByTagName(element, "transactional"); Element adviceChainElement = DomUtils.getChildElementByTagName(element, "advice-chain"); - configureAdviceChain(adviceChainElement, txElement, metadataBuilder, parserContext); + IntegrationNamespaceUtils.configureAndSetAdviceChainIfPresent(adviceChainElement, txElement, + metadataBuilder, parserContext); Element pseudoTxElement = DomUtils.getChildElementByTagName(element, "psuedo-transactional"); if (pseudoTxElement != null && txElement != null) { @@ -174,48 +168,6 @@ public class PollerParser extends AbstractBeanDefinitionParser { targetBuilder.addPropertyReference("trigger", triggerBeanNames.get(0)); } - /** - * Parses the 'advice-chain' element's sub-elements. - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - private void configureAdviceChain(Element adviceChainElement, Element txElement, BeanDefinitionBuilder targetBuilder, ParserContext parserContext) { - ManagedList adviceChain = new ManagedList(); - // Schema validation ensures txElement and adviceChainElement are mutually exclusive - if (txElement != null) { - adviceChain.add(IntegrationNamespaceUtils.configureTransactionAttributes(txElement)); - } - if (adviceChainElement != null) { - NodeList childNodes = adviceChainElement.getChildNodes(); - for (int i = 0; i < childNodes.getLength(); i++) { - Node child = childNodes.item(i); - if (child.getNodeType() == Node.ELEMENT_NODE) { - Element childElement = (Element) child; - String localName = child.getLocalName(); - if ("bean".equals(localName)) { - BeanDefinitionHolder holder = parserContext.getDelegate().parseBeanDefinitionElement( - childElement, targetBuilder.getBeanDefinition()); - parserContext.registerBeanComponent(new BeanComponentDefinition(holder)); - adviceChain.add(new RuntimeBeanReference(holder.getBeanName())); - } - else if ("ref".equals(localName)) { - String ref = childElement.getAttribute("bean"); - adviceChain.add(new RuntimeBeanReference(ref)); - } - else { - BeanDefinition customBeanDefinition = parserContext.getDelegate().parseCustomElement( - childElement, targetBuilder.getBeanDefinition()); - if (customBeanDefinition == null) { - parserContext.getReaderContext().error( - "failed to parse custom element '" + localName + "'", childElement); - } - adviceChain.add(customBeanDefinition); - } - } - } - } - targetBuilder.addPropertyValue("adviceChain", adviceChain); - } - private void configureTransactionSync(Element element, BeanDefinitionBuilder metadataBuilder, ParserContext parserContext) { if (element != null) { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractReplyProducingMessageHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractReplyProducingMessageHandler.java index 75307c73cd..80a304a448 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractReplyProducingMessageHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractReplyProducingMessageHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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,11 @@ package org.springframework.integration.handler; +import java.util.List; + +import org.aopalliance.aop.Advice; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.MessageDeliveryException; @@ -26,6 +31,8 @@ import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.support.channel.ChannelResolutionException; import org.springframework.integration.support.channel.ChannelResolver; 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. @@ -33,8 +40,10 @@ import org.springframework.util.Assert; * @author Mark Fisher * @author Iwein Fuld * @author Oleg Zhurakousky + * @author Gary Russell */ -public abstract class AbstractReplyProducingMessageHandler extends AbstractMessageHandler implements MessageProducer { +public abstract class AbstractReplyProducingMessageHandler extends AbstractMessageHandler + implements MessageProducer, BeanClassLoaderAware { private MessageChannel outputChannel; @@ -42,6 +51,13 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa private final MessagingTemplate messagingTemplate; + private volatile RequestHandler advisedRequestHandler; + + private volatile List adviceChain; + + private volatile ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + public AbstractReplyProducingMessageHandler() { this.messagingTemplate = new MessagingTemplate(); @@ -82,11 +98,30 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa return this.messagingTemplate; } + + public void setAdviceChain(List adviceChain) { + Assert.notNull(adviceChain, "adviceChain cannot be null"); + this.adviceChain = adviceChain; + } + + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + @Override protected void onInit() { if (this.getBeanFactory() != null) { this.messagingTemplate.setBeanFactory(getBeanFactory()); } + if (!CollectionUtils.isEmpty(this.adviceChain)) { + ProxyFactory proxyFactory = new ProxyFactory(new AdvisedRequestHandler()); + for (Advice advice : this.adviceChain) { + proxyFactory.addAdvice(advice); + } + this.advisedRequestHandler = (RequestHandler) proxyFactory.getProxy(this.beanClassLoader); + } } /** @@ -94,7 +129,13 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa */ @Override protected final void handleMessageInternal(Message message) { - Object result = this.handleRequestMessage(message); + Object result; + if (this.advisedRequestHandler == null) { + result = this.handleRequestMessage(message); + } + else { + result = this.advisedRequestHandler.handleRequestMessage(message); + } if (result != null) { MessageHeaders requestHeaders = message.getHeaders(); this.handleResult(result, requestHeaders); @@ -149,7 +190,7 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa * 'outputChannel' is null. In that case, the header value must not also be * null, and it must be an instance of either String or {@link MessageChannel}. * @param replyMessage the reply Message to send - * @param replyChannelHeaderValue the 'replyChannel' header value from the original request + * @param replyChannelHeaderValue the 'replyChannel' header value from the original request */ private final void sendReplyMessage(Message replyMessage, final Object replyChannelHeaderValue) { if (logger.isDebugEnabled()) { @@ -207,4 +248,26 @@ public abstract class AbstractReplyProducingMessageHandler extends AbstractMessa */ protected abstract Object handleRequestMessage(Message requestMessage); + + private interface RequestHandler { + + Object handleRequestMessage(Message requestMessage); + + String toString(); + } + + private class AdvisedRequestHandler implements RequestHandler { + + public Object handleRequestMessage(Message requestMessage) { + return AbstractReplyProducingMessageHandler.this.handleRequestMessage(requestMessage); + } + + @Override + public String toString() { + return AbstractReplyProducingMessageHandler.this.toString(); + } + + + } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/RetryStateGenerator.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/RetryStateGenerator.java new file mode 100644 index 0000000000..e8430d8d62 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/RetryStateGenerator.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2012 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.handler; + +import org.springframework.integration.Message; +import org.springframework.retry.RetryState; + +/** + * Strategy interface for generating a {@link RetryState} instance + * based on a message. + * @author Gary Russell + * @since 2.2 + * + */ +public interface RetryStateGenerator { + + RetryState determineRetryState(Message message); +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/AbstractRequestHandlerAdvice.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/AbstractRequestHandlerAdvice.java new file mode 100644 index 0000000000..4f17c89d7f --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/AbstractRequestHandlerAdvice.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2012 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.handler.advice; + +import java.lang.reflect.Method; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.integration.Message; +import org.springframework.integration.core.MessageHandler; + +/** + * Base class for {@link MessageHandler} advice classes. + * @author Gary Russell + * @since 2.2 + * + */ +public abstract class AbstractRequestHandlerAdvice implements MethodInterceptor { + + protected final Log logger = LogFactory.getLog(this.getClass()); + + public final Object invoke(final MethodInvocation invocation) throws Throwable { + + Method method = invocation.getMethod(); + Object[] arguments = invocation.getArguments(); + boolean isMessageMethod = (method.getName().equals("handleRequestMessage") || method.getName().equals("handleMessage")) + && (arguments.length == 1 && arguments[0] instanceof Message); + + if (!isMessageMethod) { + return invocation.proceed(); + } + else { + Message message = (Message) arguments[0]; + try { + return doInvoke(new ExecutionCallback(){ + + public Object execute() throws Exception { + try { + return invocation.proceed(); + } + catch (Throwable e) { + throw new ThrowableHolderException(e); + } + } + }, invocation.getThis(), message); + } + catch (Exception e) { + if (e instanceof ThrowableHolderException) { + throw e.getCause(); + } + else { + throw e; + } + } + } + } + + protected abstract Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception; + + protected interface ExecutionCallback { + + Object execute() throws Exception; + } + + @SuppressWarnings("serial") + private class ThrowableHolderException extends RuntimeException { + + public ThrowableHolderException(Throwable cause) { + super(cause); + } + } +} \ No newline at end of file diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/ExpressionEvaluatingRequestHandlerAdvice.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/ExpressionEvaluatingRequestHandlerAdvice.java new file mode 100644 index 0000000000..72a56825af --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/ExpressionEvaluatingRequestHandlerAdvice.java @@ -0,0 +1,166 @@ +/* + * Copyright 2002-2012 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.handler.advice; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.expression.Expression; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.MessageHeaders; +import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.core.MessagingTemplate; +import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; +import org.springframework.integration.support.MessageBuilder; + +/** + * Used to advise {@link MessageHandler}s. + * Two expressions 'onSuccessExpression' and 'onFailureExpression' are evaluated when + * appropriate. If the evaluation returns a result, a message is sent to the onSuccessChannel + * or onFailureChannel as appropriate; the message is the input message with a header + * {@link MessageHeaders#POSTPROCESS_RESULT} containing the evaluation result. + * The failure expression is NOT evaluated if the success expression throws an exception. + * @author Gary Russell + * @since 2.2 + * + */ +public class ExpressionEvaluatingRequestHandlerAdvice extends AbstractRequestHandlerAdvice + implements BeanFactoryAware { + + private final ExpressionEvaluatingMessageProcessor onSuccessMessageProcessor; + + private final MessageChannel successChannel; + + private final ExpressionEvaluatingMessageProcessor onFailureMessageProcessor; + + private final MessageChannel failureChannel; + + private final MessagingTemplate messagingTemplate = new MessagingTemplate(); + + private volatile boolean trapException = false; + + private volatile boolean returnFailureExpressionResult = false; + + private volatile BeanFactory beanFactory; + + private volatile boolean propagateOnSuccessEvaluationFailures; + + /** + * @param onSuccessExpression + * @param successChannel + * @param onFailureExpression + * @param failureChannel + */ + public ExpressionEvaluatingRequestHandlerAdvice(Expression onSuccessExpression, MessageChannel successChannel, + Expression onFailureExpression, MessageChannel failureChannel) { + if (onSuccessExpression != null) { + this.onSuccessMessageProcessor = new ExpressionEvaluatingMessageProcessor(onSuccessExpression); + this.onSuccessMessageProcessor.setBeanFactory(this.beanFactory); + } + else { + this.onSuccessMessageProcessor = null; + } + this.successChannel = successChannel; + if (onFailureExpression != null) { + this.onFailureMessageProcessor = new ExpressionEvaluatingMessageProcessor(onFailureExpression); + onFailureMessageProcessor.setBeanFactory(this.beanFactory); + } + else { + this.onFailureMessageProcessor = null; + } + this.failureChannel = failureChannel; + + } + + /** + * If true, any exception will be caught and null returned. + * Default false. + * @param trapException + */ + public void setTrapException(boolean trapException) { + this.trapException = trapException; + } + + /** + * If true, the result of evaluating the onFailureExpression will + * be returned as the result of AbstractReplyProducingMessageHandler.handleRequestMessage(Message). + * @param returnFailureExpressionResult + */ + public void setReturnFailureExpressionResult(boolean returnFailureExpressionResult) { + this.returnFailureExpressionResult = returnFailureExpressionResult; + } + + /** + * If true and an onSuccess expression evaluation fails with an exception, the exception will be thrown to the + * caller. If false, the exception is caught. Default false. Ignored for onFailure expression evaluation - the + * original exception will be propagated (unless trapException is true). + * @param propagateOnSuccessEvaluationFailures + */ + public void setPropagateEvaluationFailures(boolean propagateOnSuccessEvaluationFailures) { + this.propagateOnSuccessEvaluationFailures = propagateOnSuccessEvaluationFailures; + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + try { + Object result = callback.execute(); + if (onSuccessMessageProcessor != null) { + evaluateExpression(message, this.onSuccessMessageProcessor, this.successChannel, this.propagateOnSuccessEvaluationFailures); + } + return result; + } + catch (Exception e) { + Object evalResult = evaluateExpression(message, this.onFailureMessageProcessor, this.failureChannel, false); + if (this.returnFailureExpressionResult) { + return evalResult; + } + if (!this.trapException) { + throw e; + } + return null; + } + } + + private Object evaluateExpression(Message message, + ExpressionEvaluatingMessageProcessor expressionEvaluatingMessageProcessor, + MessageChannel resultChannel, boolean propagateEvaluationFailure) throws Exception { + Object evalResult; + boolean evaluationFailed = false; + try { + evalResult = expressionEvaluatingMessageProcessor.processMessage(message); + } + catch (Exception e) { + evalResult = e; + evaluationFailed = true; + } + if (evalResult != null && resultChannel != null) { + message = MessageBuilder.fromMessage(message) + .setHeader(MessageHeaders.POSTPROCESS_RESULT, evalResult) + .build(); + this.messagingTemplate.send(resultChannel, message); + } + if (evaluationFailed && propagateEvaluationFailure) { + throw (Exception) evalResult; + } + return evalResult; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/RequestHandlerCircuitBreakerAdvice.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/RequestHandlerCircuitBreakerAdvice.java new file mode 100644 index 0000000000..1def2a8aa1 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/RequestHandlerCircuitBreakerAdvice.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2012 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.handler.advice; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.springframework.integration.Message; +import org.springframework.integration.MessagingException; + +/** + * A circuit breaker that stops calling a failing service after threshold + * failures, until halfOpenAfter milliseconds has elapsed. A successful + * call resets the failure counter. + * + * @author Gary Russell + * @since 2.2 + * + */ +public class RequestHandlerCircuitBreakerAdvice extends AbstractRequestHandlerAdvice { + + private volatile int threshold = 5; + + private volatile long halfOpenAfter = 1000; + + private final ConcurrentMap metadataMap = new ConcurrentHashMap(); + + public void setThreshold(int threshold) { + this.threshold = threshold; + } + + public void setHalfOpenAfter(long halfOpenAfter) { + this.halfOpenAfter = halfOpenAfter; + } + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + AdvisedMetadata metadata = this.metadataMap.get(target); + if (metadata == null) { + this.metadataMap.putIfAbsent(target, new AdvisedMetadata()); + metadata = this.metadataMap.get(target); + } + if (metadata.getFailures().get() >= this.threshold && + System.currentTimeMillis() - metadata.getLastFailure() < this.halfOpenAfter) { + throw new MessagingException("Circuit Breaker is Open for " + target); + } + try { + Object result = callback.execute(); + if (logger.isDebugEnabled() && metadata.getFailures().get() > 0) { + logger.debug("Closing Circuit Breaker for " + target); + } + metadata.getFailures().set(0); + return result; + } + catch (Exception e) { + metadata.getFailures().incrementAndGet(); + metadata.setLastFailure(System.currentTimeMillis()); + throw e; + } + } + + private class AdvisedMetadata { + + private final AtomicInteger failures = new AtomicInteger(); + + private volatile long lastFailure; + + private long getLastFailure() { + return lastFailure; + } + + private void setLastFailure(long lastFailure) { + this.lastFailure = lastFailure; + } + + private AtomicInteger getFailures() { + return failures; + } + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/RequestHandlerRetryAdvice.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/RequestHandlerRetryAdvice.java new file mode 100644 index 0000000000..9a7eb59590 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/RequestHandlerRetryAdvice.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2012 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.handler.advice; + +import org.springframework.integration.Message; +import org.springframework.integration.MessagingException; +import org.springframework.integration.handler.RetryStateGenerator; +import org.springframework.retry.RecoveryCallback; +import org.springframework.retry.RetryCallback; +import org.springframework.retry.RetryContext; +import org.springframework.retry.RetryState; +import org.springframework.retry.support.RetryTemplate; + +/** + * Uses spring-retry to perform stateless or stateful retry. + * Stateless retry means the retries are performed internally + * by the {@link RetryTemplate}; stateful retry means the + * exception is thrown but state is maintained to support + * the retry policies. Stateful retry requires a + * {@link RetryStateGenerator}. + * @author Gary Russell + * @since 2.2 + * + */ +public class RequestHandlerRetryAdvice extends AbstractRequestHandlerAdvice { + + private volatile RetryTemplate retryTemplate = new RetryTemplate(); + + private volatile RecoveryCallback recoveryCallback; + + // Stateless unless a state generator is provided + private volatile RetryStateGenerator retryStateGenerator = + new RetryStateGenerator() { + public RetryState determineRetryState(Message message) { + return null; + } + }; + + public void setRetryTemplate(RetryTemplate retryTemplate) { + this.retryTemplate = retryTemplate; + } + + public void setRecoveryCallback(RecoveryCallback recoveryCallback) { + this.recoveryCallback = recoveryCallback; + } + + public void setRetryStateGenerator(RetryStateGenerator retryStateGenerator) { + this.retryStateGenerator = retryStateGenerator; + } + + @Override + protected Object doInvoke(final ExecutionCallback callback, Object target, final Message message) throws Exception { + RetryState retryState = null; + retryState = this.retryStateGenerator.determineRetryState(message); + + return retryTemplate.execute(new RetryCallback(){ + public Object doWithRetry(RetryContext context) throws Exception { + try { + return callback.execute(); + } + catch (MessagingException e) { + if (e.getFailedMessage() == null) { + e.setFailedMessage(message); + } + throw e; + } + catch (Exception e) { + throw new MessagingException(message, "Failed to invoke handler", e); + } + } + }, this.recoveryCallback, retryState); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/package-info.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/package-info.java new file mode 100644 index 0000000000..3c3dbb01fd --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/advice/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides classes that are used to advise + * {@link org.springframework.integration.core.MessageHandler}s with + * cross-cutting concerns. + */ +package org.springframework.integration.handler.advice; \ No newline at end of file diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.2.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.2.xsd index 6e4f27d390..951bc7a012 100644 --- a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.2.xsd +++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.2.xsd @@ -1124,6 +1124,7 @@ + @@ -1481,28 +1482,7 @@ - - - - - - - - - - - - - - - - - - - - - - + @@ -3422,12 +3402,34 @@ is provided, the return value is expected to match a channel name exactly. + + + + + + + + + + + + + + + + + + + + + - + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/FilterParserTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/FilterParserTests-context.xml index 79fa610dfc..90b06b9931 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/FilterParserTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/FilterParserTests-context.xml @@ -23,7 +23,11 @@ - + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/FilterParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/FilterParserTests.java index 95b95ced8b..6ac12485c8 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/FilterParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/FilterParserTests.java @@ -22,7 +22,6 @@ import static org.junit.Assert.assertNull; import org.junit.Test; import org.junit.runner.RunWith; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.integration.Message; @@ -30,6 +29,7 @@ import org.springframework.integration.MessageChannel; import org.springframework.integration.MessageRejectedException; import org.springframework.integration.core.MessageSelector; import org.springframework.integration.core.PollableChannel; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.message.GenericMessage; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -69,13 +69,16 @@ public class FilterParserTests { @Autowired @Qualifier("discardAndExceptionOutput") PollableChannel discardAndExceptionOutput; + private static volatile int adviceCalled; @Test public void filterWithSelectorAdapterAccepts() { + adviceCalled = 0; adapterInput.send(new GenericMessage("test")); Message reply = adapterOutput.receive(0); assertNotNull(reply); assertEquals("test", reply.getPayload()); + assertEquals(1, adviceCalled); } @Test @@ -156,4 +159,13 @@ public class FilterParserTests { } } + public static class FooFilter extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return callback.execute(); + } + + } } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests-context.xml index 20b6dfb392..5b2a3f9a66 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests-context.xml @@ -22,6 +22,10 @@ + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests.java index 87fa08bb9c..e70ef0739e 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/EnricherParserTests.java @@ -35,6 +35,7 @@ import org.springframework.integration.core.PollableChannel; import org.springframework.integration.core.SubscribableChannel; import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; import org.springframework.integration.transformer.ContentEnricher; @@ -44,7 +45,8 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @author Mark Fisher * @author Gunnar Hillert - * + * @author Gary Russell + * * @since 2.1 */ @RunWith(SpringJUnit4ClassRunner.class) @@ -54,6 +56,7 @@ public class EnricherParserTests { @Autowired private ApplicationContext context; + private static volatile int adviceCalled; @Test @SuppressWarnings("unchecked") @@ -84,13 +87,14 @@ public class EnricherParserTests { throw new IllegalStateException("expected 'name', 'age', and 'gender' only, not: " + e.getKey().getExpressionString()); } } + } @Test public void configurationCheckTimeoutParameters() { - + Object endpoint = context.getBean("enricher"); - + Long requestTimeout = TestUtils.getPropertyValue(endpoint, "handler.requestTimeout", Long.class); Long replyTimeout = TestUtils.getPropertyValue(endpoint, "handler.replyTimeout", Long.class); @@ -98,18 +102,18 @@ public class EnricherParserTests { assertEquals(Long.valueOf(9876L), replyTimeout); } - + @Test public void configurationCheckRequiresReply() { - + Object endpoint = context.getBean("enricher"); - + boolean requiresReply = TestUtils.getPropertyValue(endpoint, "handler.requiresReply", Boolean.class); assertTrue("Was expecting requiresReply to be 'false'", requiresReply); } - + @Test public void integrationTest() { SubscribableChannel requests = context.getBean("requests", SubscribableChannel.class); @@ -128,6 +132,7 @@ public class EnricherParserTests { assertEquals(42, enriched.getAge()); assertEquals("male", enriched.getGender()); assertNotSame(original, enriched); + assertEquals(1, adviceCalled); } private static class Source { @@ -176,6 +181,7 @@ public class EnricherParserTests { this.gender = gender; } + @Override public Object clone() { Target copy = new Target(); copy.setName(this.name); @@ -184,4 +190,13 @@ public class EnricherParserTests { } } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return callback.execute(); + } + + } } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ServiceActivatorParserTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ServiceActivatorParserTests-context.xml index 16950067e9..f0b46f7961 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ServiceActivatorParserTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ServiceActivatorParserTests-context.xml @@ -21,4 +21,9 @@ + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ServiceActivatorParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ServiceActivatorParserTests.java index d87b9a045c..6dca7a109f 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ServiceActivatorParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ServiceActivatorParserTests.java @@ -20,12 +20,13 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; import org.junit.runner.RunWith; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.handler.ServiceActivatingHandler; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -56,6 +57,9 @@ public class ServiceActivatorParserTests { @Autowired private MessageChannel multipleArgsFromPayloadInput; + @Autowired + private MessageChannel advisedInput; + @SuppressWarnings("unused") // testing auto wiring only @Autowired @Qualifier("org.springframework.integration.config.ServiceActivatorFactoryBean#0") @@ -102,6 +106,11 @@ public class ServiceActivatorParserTests { assertEquals("JohnDoe", result); } + @Test + public void advised() { + Object result = this.sendAndReceive(advisedInput, "hello"); + assertEquals("bar", result); + } private Object sendAndReceive(MessageChannel channel, Object payload) { MessagingTemplate template = new MessagingTemplate(channel); @@ -152,4 +161,13 @@ public class ServiceActivatorParserTests { } } + public static class BarAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + callback.execute(); + return "bar"; + } + + } } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/AdvisedMessageHandlerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/AdvisedMessageHandlerTests.java new file mode 100644 index 0000000000..6e187893b0 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/AdvisedMessageHandlerTests.java @@ -0,0 +1,477 @@ +/* + * Copyright 2002-2012 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.handler; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.aopalliance.aop.Advice; +import org.junit.Test; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.integration.Message; +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.MessageHeaders; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.core.PollableChannel; +import org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice; +import org.springframework.integration.handler.advice.RequestHandlerCircuitBreakerAdvice; +import org.springframework.integration.handler.advice.RequestHandlerRetryAdvice; +import org.springframework.integration.message.GenericMessage; +import org.springframework.retry.RecoveryCallback; +import org.springframework.retry.RetryContext; +import org.springframework.retry.RetryState; +import org.springframework.retry.support.DefaultRetryState; + +/** + * @author Gary Russell + * @since 2.2 + * + */ +public class AdvisedMessageHandlerTests { + + @Test + public void successFailureAdvice() { + final AtomicBoolean doFail = new AtomicBoolean(); + AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { + @Override + protected Object handleRequestMessage(Message requestMessage) { + if (doFail.get()) { + throw new RuntimeException("qux"); + } + return "baz"; + } + }; + QueueChannel replies = new QueueChannel(); + handler.setOutputChannel(replies); + Message message = new GenericMessage("Hello, world!"); + + // no advice + handler.handleMessage(message); + Message reply = replies.receive(1000); + assertNotNull(reply); + assertEquals("baz", reply.getPayload()); + + PollableChannel successChannel = new QueueChannel(); + PollableChannel failureChannel = new QueueChannel(); + ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice( + new SpelExpressionParser().parseExpression("'foo'"), successChannel, + new SpelExpressionParser().parseExpression("'bar'"), failureChannel); + + List adviceChain = new ArrayList(); + adviceChain.add(advice); + handler.setAdviceChain(adviceChain); + handler.afterPropertiesSet(); + + // advice with success + handler.handleMessage(message); + reply = replies.receive(1000); + assertNotNull(reply); + assertEquals("baz", reply.getPayload()); + + Message success = successChannel.receive(1000); + assertNotNull(success); + assertEquals("Hello, world!", success.getPayload()); + assertEquals("foo", success.getHeaders().get(MessageHeaders.POSTPROCESS_RESULT)); + + // advice with failure, not trapped + doFail.set(true); + try { + handler.handleMessage(message); + fail("Expected exception"); + } + catch (Exception e) { + assertEquals("qux", e.getCause().getMessage()); + } + + Message failure = failureChannel.receive(1000); + assertNotNull(failure); + assertEquals("Hello, world!", failure.getPayload()); + assertEquals("bar", failure.getHeaders().get(MessageHeaders.POSTPROCESS_RESULT)); + + // advice with failure, trapped + advice.setTrapException(true); + handler.handleMessage(message); + failure = failureChannel.receive(1000); + assertNotNull(failure); + assertEquals("Hello, world!", failure.getPayload()); + assertEquals("bar", failure.getHeaders().get(MessageHeaders.POSTPROCESS_RESULT)); + assertNull(replies.receive(1)); + + // advice with failure, eval is result + advice.setReturnFailureExpressionResult(true); + handler.handleMessage(message); + failure = failureChannel.receive(1000); + assertNotNull(failure); + assertEquals("Hello, world!", failure.getPayload()); + assertEquals("bar", failure.getHeaders().get(MessageHeaders.POSTPROCESS_RESULT)); + + reply = replies.receive(1000); + assertNotNull(reply); + assertEquals("bar", reply.getPayload()); + + } + + @Test + public void propagateOnSuccessExpressionFailures() { + final AtomicBoolean doFail = new AtomicBoolean(); + AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { + @Override + protected Object handleRequestMessage(Message requestMessage) { + if (doFail.get()) { + throw new RuntimeException("qux"); + } + return "baz"; + } + }; + QueueChannel replies = new QueueChannel(); + handler.setOutputChannel(replies); + Message message = new GenericMessage("Hello, world!"); + + PollableChannel successChannel = new QueueChannel(); + PollableChannel failureChannel = new QueueChannel(); + ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice( + new SpelExpressionParser().parseExpression("1/0"), successChannel, + new SpelExpressionParser().parseExpression("1/0"), failureChannel); + + List adviceChain = new ArrayList(); + adviceChain.add(advice); + handler.setAdviceChain(adviceChain); + handler.afterPropertiesSet(); + + // failing advice with success + handler.handleMessage(message); + Message reply = replies.receive(1000); + assertNotNull(reply); + assertEquals("baz", reply.getPayload()); + + Message success = successChannel.receive(1000); + assertNotNull(success); + assertEquals("Hello, world!", success.getPayload()); + assertEquals(MessageHandlingException.class, success.getHeaders().get(MessageHeaders.POSTPROCESS_RESULT).getClass()); + assertEquals("Expression evaluation failed: 1/0", ((Exception) success.getHeaders().get(MessageHeaders.POSTPROCESS_RESULT)).getMessage()); + + // propagate failing advice with success + advice.setPropagateEvaluationFailures(true); + try { + handler.handleMessage(message); + fail("Expected Exception"); + } + catch (MessageHandlingException e) { + assertEquals("Expression evaluation failed: 1/0", e.getMessage()); + } + reply = replies.receive(1); + assertNull(reply); + + success = successChannel.receive(1000); + assertNotNull(success); + assertEquals("Hello, world!", success.getPayload()); + assertEquals(MessageHandlingException.class, success.getHeaders().get(MessageHeaders.POSTPROCESS_RESULT).getClass()); + assertEquals("Expression evaluation failed: 1/0", ((Exception) success.getHeaders().get(MessageHeaders.POSTPROCESS_RESULT)).getMessage()); + + } + + @Test + public void propagateOnFailureExpressionFailures() { + final AtomicBoolean doFail = new AtomicBoolean(true); + AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { + @Override + protected Object handleRequestMessage(Message requestMessage) { + if (doFail.get()) { + throw new RuntimeException("qux"); + } + return "baz"; + } + }; + QueueChannel replies = new QueueChannel(); + handler.setOutputChannel(replies); + Message message = new GenericMessage("Hello, world!"); + + PollableChannel successChannel = new QueueChannel(); + PollableChannel failureChannel = new QueueChannel(); + ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice( + new SpelExpressionParser().parseExpression("1/0"), successChannel, + new SpelExpressionParser().parseExpression("1/0"), failureChannel); + + List adviceChain = new ArrayList(); + adviceChain.add(advice); + handler.setAdviceChain(adviceChain); + handler.afterPropertiesSet(); + + // failing advice with failure + try { + handler.handleMessage(message); + fail("Expected exception"); + } + catch (Exception e) { + assertEquals("qux", e.getCause().getMessage()); + } + Message reply = replies.receive(1); + assertNull(reply); + + Message failure = failureChannel.receive(1000); + assertNotNull(failure); + assertEquals("Hello, world!", failure.getPayload()); + assertEquals(MessageHandlingException.class, failure.getHeaders().get(MessageHeaders.POSTPROCESS_RESULT).getClass()); + assertEquals("Expression evaluation failed: 1/0", ((Exception) failure.getHeaders().get(MessageHeaders.POSTPROCESS_RESULT)).getMessage()); + + // propagate failing advice with failure; expect original exception + advice.setPropagateEvaluationFailures(true); + try { + handler.handleMessage(message); + fail("Expected Exception"); + } + catch (MessageHandlingException e) { + assertEquals("qux", e.getCause().getMessage()); + } + reply = replies.receive(1); + assertNull(reply); + + failure = failureChannel.receive(1000); + assertNotNull(failure); + assertEquals("Hello, world!", failure.getPayload()); + assertEquals(MessageHandlingException.class, failure.getHeaders().get(MessageHeaders.POSTPROCESS_RESULT).getClass()); + assertEquals("Expression evaluation failed: 1/0", ((Exception) failure.getHeaders().get(MessageHeaders.POSTPROCESS_RESULT)).getMessage()); + + } + + @Test + public void circuitBreakerTests() throws Exception { + final AtomicBoolean doFail = new AtomicBoolean(); + AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { + + @Override + protected Object handleRequestMessage(Message requestMessage) { + if (doFail.get()) { + throw new RuntimeException("foo"); + } + return "bar"; + } + + }; + handler.setBeanName("baz"); + handler.setOutputChannel(new QueueChannel()); + RequestHandlerCircuitBreakerAdvice advice = new RequestHandlerCircuitBreakerAdvice(); + /* + * Circuit breaker opens after 2 failures; allows a new attempt after 100ms and + * immediately opens again if that attempt fails. After a successful attempt, + * we reset the failure counter. + */ + advice.setThreshold(2); + advice.setHalfOpenAfter(100); + + List adviceChain = new ArrayList(); + adviceChain.add(advice); + handler.setAdviceChain(adviceChain); + handler.afterPropertiesSet(); + + doFail.set(true); + Message message = new GenericMessage("Hello, world!"); + try { + handler.handleMessage(message); + fail("Expected failure"); + } + catch (Exception e) { + assertEquals("foo", e.getCause().getMessage()); + } + try { + handler.handleMessage(message); + fail("Expected failure"); + } + catch (Exception e) { + assertEquals("foo", e.getCause().getMessage()); + } + try { + handler.handleMessage(message); + fail("Expected failure"); + } + catch (Exception e) { + assertEquals("Circuit Breaker is Open for baz", e.getMessage()); + } + Thread.sleep(100); + try { + handler.handleMessage(message); + fail("Expected failure"); + } + catch (Exception e) { + assertEquals("foo", e.getCause().getMessage()); + } + try { + handler.handleMessage(message); + fail("Expected failure"); + } + catch (Exception e) { + assertEquals("Circuit Breaker is Open for baz", e.getMessage()); + } + Thread.sleep(100); + doFail.set(false); + handler.handleMessage(message); + doFail.set(true); + try { + handler.handleMessage(message); + fail("Expected failure"); + } + catch (Exception e) { + assertEquals("foo", e.getCause().getMessage()); + } + try { + handler.handleMessage(message); + fail("Expected failure"); + } + catch (Exception e) { + assertEquals("foo", e.getCause().getMessage()); + } + try { + handler.handleMessage(message); + fail("Expected failure"); + } + catch (Exception e) { + assertEquals("Circuit Breaker is Open for baz", e.getMessage()); + } + } + + @Test + public void defaultRetrySucceedonThirdTry() { + final AtomicInteger counter = new AtomicInteger(2); + AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { + + @Override + protected Object handleRequestMessage(Message requestMessage) { + if (counter.getAndDecrement() > 0) { + throw new RuntimeException("foo"); + } + return "bar"; + } + }; + QueueChannel replies = new QueueChannel(); + handler.setOutputChannel(replies); + RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice(); + + List adviceChain = new ArrayList(); + adviceChain.add(advice); + handler.setAdviceChain(adviceChain); + handler.afterPropertiesSet(); + + Message message = new GenericMessage("Hello, world!"); + handler.handleMessage(message); + assertTrue(counter.get() == -1); + Message reply = replies.receive(1000); + assertNotNull(reply); + assertEquals("bar", reply.getPayload()); + + } + + @Test + public void defaultStatefulRetrySucceedonThirdTry() { + final AtomicInteger counter = new AtomicInteger(2); + AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { + + @Override + protected Object handleRequestMessage(Message requestMessage) { + if (counter.getAndDecrement() > 0) { + throw new RuntimeException("foo"); + } + return "bar"; + } + }; + QueueChannel replies = new QueueChannel(); + handler.setOutputChannel(replies); + RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice(); + + advice.setRetryStateGenerator(new RetryStateGenerator() { + public RetryState determineRetryState(Message message) { + return new DefaultRetryState(message.getHeaders().getId()); + } + }); + + List adviceChain = new ArrayList(); + adviceChain.add(advice); + handler.setAdviceChain(adviceChain); + handler.afterPropertiesSet(); + + Message message = new GenericMessage("Hello, world!"); + for (int i = 0; i < 3; i++) { + try { + handler.handleMessage(message); + } + catch (Exception e) { + assertTrue(i < 2); + } + } + assertTrue(counter.get() == -1); + Message reply = replies.receive(1000); + assertNotNull(reply); + assertEquals("bar", reply.getPayload()); + + } + + @Test + public void defaultStatefulRetryRecoverAfterThirdTry() { + final AtomicInteger counter = new AtomicInteger(3); + AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { + + @Override + protected Object handleRequestMessage(Message requestMessage) { + if (counter.getAndDecrement() > 0) { + throw new RuntimeException("foo"); + } + return "bar"; + } + }; + QueueChannel replies = new QueueChannel(); + handler.setOutputChannel(replies); + RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice(); + + advice.setRetryStateGenerator(new RetryStateGenerator() { + public RetryState determineRetryState(Message message) { + return new DefaultRetryState(message.getHeaders().getId()); + } + }); + + advice.setRecoveryCallback(new RecoveryCallback() { + + public Object recover(RetryContext context) throws Exception { + return "baz"; + } + }); + + List adviceChain = new ArrayList(); + adviceChain.add(advice); + handler.setAdviceChain(adviceChain); + handler.afterPropertiesSet(); + + Message message = new GenericMessage("Hello, world!"); + for (int i = 0; i < 4; i++) { + try { + handler.handleMessage(message); + } + catch (Exception e) { + } + } + assertTrue(counter.get() == 0); + Message reply = replies.receive(1000); + assertNotNull(reply); + assertEquals("baz", reply.getPayload()); + + } +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/transformer/TransformerContextTests.java b/spring-integration-core/src/test/java/org/springframework/integration/transformer/TransformerContextTests.java index 7bc55cccf4..2280203f14 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/transformer/TransformerContextTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/transformer/TransformerContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -19,19 +19,22 @@ package org.springframework.integration.transformer; import static org.junit.Assert.assertEquals; import org.junit.Test; - import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.core.PollableChannel; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.message.GenericMessage; /** * @author Mark Fisher + * @author Gary Russell */ public class TransformerContextTests { + private static volatile int adviceCalled; + @Test public void methodInvokingTransformer() { ApplicationContext context = new ClassPathXmlApplicationContext( @@ -41,6 +44,16 @@ public class TransformerContextTests { input.send(new GenericMessage("foo")); Message reply = output.receive(0); assertEquals("FOO", reply.getPayload()); + assertEquals(1, adviceCalled); } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return callback.execute(); + } + + } } \ No newline at end of file diff --git a/spring-integration-core/src/test/java/org/springframework/integration/transformer/transformerContextTests.xml b/spring-integration-core/src/test/java/org/springframework/integration/transformer/transformerContextTests.xml index 5679865023..2fbcdc1dd9 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/transformer/transformerContextTests.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/transformer/transformerContextTests.xml @@ -13,7 +13,11 @@ - + + + + + diff --git a/spring-integration-event/src/main/resources/org/springframework/integration/event/config/spring-integration-event-2.2.xsd b/spring-integration-event/src/main/resources/org/springframework/integration/event/config/spring-integration-event-2.2.xsd index 91a174aa06..1658e38d8d 100644 --- a/spring-integration-event/src/main/resources/org/springframework/integration/event/config/spring-integration-event-2.2.xsd +++ b/spring-integration-event/src/main/resources/org/springframework/integration/event/config/spring-integration-event-2.2.xsd @@ -81,9 +81,10 @@ - + - + + diff --git a/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventOutboundChannelAdapterParserTests-context.xml b/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventOutboundChannelAdapterParserTests-context.xml index 88903e44cd..b160d943f5 100644 --- a/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventOutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventOutboundChannelAdapterParserTests-context.xml @@ -11,6 +11,14 @@ + + + + + + + + diff --git a/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventOutboundChannelAdapterParserTests.java b/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventOutboundChannelAdapterParserTests.java index 5e64e9dfc6..675e9de37e 100644 --- a/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventOutboundChannelAdapterParserTests.java +++ b/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventOutboundChannelAdapterParserTests.java @@ -16,7 +16,11 @@ package org.springframework.integration.event.config; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; + import junit.framework.Assert; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.DirectFieldAccessor; @@ -31,16 +35,15 @@ import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.event.outbound.ApplicationEventPublishingMessageHandler; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.message.GenericMessage; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import java.util.concurrent.BrokenBarrierException; -import java.util.concurrent.CyclicBarrier; - /** * @author Oleg Zhurakousky * @author Artem Bilan + * @author Gary Russell * @since 2.0 */ @RunWith(SpringJUnit4ClassRunner.class) @@ -52,6 +55,7 @@ public class EventOutboundChannelAdapterParserTests { private volatile boolean receivedEvent; + private static volatile int adviceCalled; @Test public void validateEventParser() { @@ -82,8 +86,30 @@ public class EventOutboundChannelAdapterParserTests { Assert.assertTrue(receivedEvent); } + @Test + public void withAdvice() { + receivedEvent = false; + ApplicationListener listener = new ApplicationListener() { + public void onApplicationEvent(ApplicationEvent event) { + Object source = event.getSource(); + if (source instanceof Message){ + String payload = (String) ((Message) source).getPayload(); + if (payload.equals("hello")) { + receivedEvent = true; + } + } + } + }; + context.addApplicationListener(listener); + DirectChannel channel = context.getBean("inputAdvice", DirectChannel.class); + channel.send(new GenericMessage("hello")); + Assert.assertTrue(receivedEvent); + Assert.assertEquals(1, adviceCalled); + } + @Test //INT-2275 public void testInsideChain() { + receivedEvent = false; ApplicationListener listener = new ApplicationListener() { public void onApplicationEvent(ApplicationEvent event) { Object source = event.getSource(); @@ -103,6 +129,7 @@ public class EventOutboundChannelAdapterParserTests { @Test(timeout=2000) public void validateUsageWithPollableChannel() throws Exception { + receivedEvent = false; ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("EventOutboundChannelAdapterParserTestsWithPollable-context.xml", EventOutboundChannelAdapterParserTests.class); final CyclicBarrier barier = new CyclicBarrier(2); ApplicationListener listener = new ApplicationListener() { @@ -132,4 +159,13 @@ public class EventOutboundChannelAdapterParserTests { Assert.assertTrue(receivedEvent); } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return callback.execute(); + } + + } } diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java b/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java index 3bac5a81ed..052318d5c0 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/FileWritingMessageHandler.java @@ -215,6 +215,8 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand @Override public final void onInit() { + super.onInit(); + this.evaluationContext.addPropertyAccessor(new MapAccessor()); final BeanFactory beanFactory = this.getBeanFactory(); diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParser.java b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParser.java index 591b7de360..3815df9bd1 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParser.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParser.java @@ -16,17 +16,16 @@ package org.springframework.integration.file.config; -import org.w3c.dom.Element; - import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; +import org.w3c.dom.Element; /** * Parser for the <outbound-channel-adapter/> element of the 'file' * namespace. - * + * * @author Mark Fisher * @author Iwein Fuld * @author Oleg Zhurakousky @@ -40,4 +39,10 @@ public class FileOutboundChannelAdapterParser extends AbstractOutboundChannelAda return handlerBuilder.getBeanDefinition(); } + @Override + protected boolean isUsingReplyProducer() { + // cannot be automatically determined by superclass because we are using a factory bean. + return true; + } + } diff --git a/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-2.2.xsd b/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-2.2.xsd index 4fbca60514..524a4ded55 100644 --- a/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-2.2.xsd +++ b/spring-integration-file/src/main/resources/org/springframework/integration/file/config/spring-integration-file-2.2.xsd @@ -255,9 +255,10 @@ Only files matching this regular expression will be picked up by this adapter. - + - + + diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParserTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParserTests-context.xml index f4821a043f..6fed5a9bf8 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundChannelAdapterParserTests-context.xml @@ -47,7 +47,11 @@ + directory="test"> + + + + message) throws Exception { + adviceCalled++; + return callback.execute(); + } } } diff --git a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundGatewayParserTests-context.xml b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundGatewayParserTests-context.xml index 2afba0e123..b105a54cb0 100644 --- a/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundGatewayParserTests-context.xml +++ b/spring-integration-file/src/test/java/org/springframework/integration/file/config/FileOutboundGatewayParserTests-context.xml @@ -19,7 +19,11 @@ + auto-startup="false" order="777" filename-generator-expression="'foo.txt'"> + + + + ("foo")); + assertEquals(1, adviceCalled); } /** @@ -282,4 +287,13 @@ public class FileOutboundGatewayParserTests { } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.2.xsd b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.2.xsd index 24e064c1d7..5ac4c3e054 100644 --- a/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.2.xsd +++ b/spring-integration-ftp/src/main/resources/org/springframework/integration/ftp/config/spring-integration-ftp-2.2.xsd @@ -20,6 +20,9 @@ + + + @@ -232,6 +235,9 @@ + + + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml index 2adbd5ada3..ed5f351c6e 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests-context.xml @@ -59,7 +59,16 @@ channel="ftpChannel" session-factory="cachingSessionFactory" remote-directory="foo/bar"/> - + + + + + + + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java index 2cf4585dba..38fbce2e88 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundChannelAdapterParserTests.java @@ -26,16 +26,18 @@ import java.util.Iterator; import java.util.Set; import org.junit.Test; - import org.springframework.beans.factory.BeanCreationException; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; import org.springframework.integration.channel.PublishSubscribeChannel; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler; import org.springframework.integration.file.remote.session.CachingSessionFactory; import org.springframework.integration.ftp.session.DefaultFtpSessionFactory; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.test.util.TestUtils; /** @@ -45,6 +47,8 @@ import org.springframework.integration.test.util.TestUtils; */ public class FtpOutboundChannelAdapterParserTests { + private static volatile int adviceCalled; + @Test public void testFtpOutboundChannelAdapterComplete() throws Exception{ ApplicationContext ac = @@ -97,6 +101,15 @@ public class FtpOutboundChannelAdapterParserTests { assertEquals(DefaultFtpSessionFactory.class, innerSfProperty.getClass()); } + @Test + public void adviceChain() { + ApplicationContext ac = new ClassPathXmlApplicationContext( + "FtpOutboundChannelAdapterParserTests-context.xml", this.getClass()); + Object adapter = ac.getBean("advisedAdapter"); + MessageHandler handler = TestUtils.getPropertyValue(adapter, "handler", MessageHandler.class); + handler.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } @Test public void testTemporaryFileSuffix() { @@ -107,4 +120,13 @@ public class FtpOutboundChannelAdapterParserTests { assertFalse((Boolean)TestUtils.getPropertyValue(handler,"useTemporaryFileName")); } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests-context.xml b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests-context.xml index b5dd3c5fae..60145b3ced 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests-context.xml +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests-context.xml @@ -41,7 +41,11 @@ command-options="-P" expression="payload" order="2" - /> + > + + + + diff --git a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests.java b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests.java index 33f67bcf1d..78ffb5cbc6 100644 --- a/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests.java +++ b/spring-integration-ftp/src/test/java/org/springframework/integration/ftp/config/FtpOutboundGatewayParserTests.java @@ -26,9 +26,12 @@ import java.util.Set; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.Message; import org.springframework.integration.endpoint.AbstractEndpoint; import org.springframework.integration.file.remote.session.CachingSessionFactory; import org.springframework.integration.ftp.gateway.FtpOutboundGateway; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.test.util.TestUtils; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -50,6 +53,8 @@ public class FtpOutboundGatewayParserTests { @Autowired AbstractEndpoint gateway2; + private static volatile int adviceCalled; + @Test public void testGateway1() { FtpOutboundGateway gateway = TestUtils.getPropertyValue(gateway1, @@ -84,5 +89,17 @@ public class FtpOutboundGatewayParserTests { @SuppressWarnings("unchecked") Set options = TestUtils.getPropertyValue(gateway, "options", Set.class); assertTrue(options.contains("-P")); + gateway.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } + + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + } } diff --git a/spring-integration-gemfire/src/main/resources/org/springframework/integration/gemfire/config/xml/spring-integration-gemfire-2.2.xsd b/spring-integration-gemfire/src/main/resources/org/springframework/integration/gemfire/config/xml/spring-integration-gemfire-2.2.xsd index d5ceae6963..db4fb05c4e 100644 --- a/spring-integration-gemfire/src/main/resources/org/springframework/integration/gemfire/config/xml/spring-integration-gemfire-2.2.xsd +++ b/spring-integration-gemfire/src/main/resources/org/springframework/integration/gemfire/config/xml/spring-integration-gemfire-2.2.xsd @@ -132,7 +132,7 @@ writes Message to a Gemfire cache - + @@ -142,7 +142,8 @@ - + + diff --git a/spring-integration-gemfire/src/test/java/org/springframework/integration/gemfire/config/xml/GemfireOutboundChannelAdapterParserTests-context.xml b/spring-integration-gemfire/src/test/java/org/springframework/integration/gemfire/config/xml/GemfireOutboundChannelAdapterParserTests-context.xml new file mode 100644 index 0000000000..713a1a335e --- /dev/null +++ b/spring-integration-gemfire/src/test/java/org/springframework/integration/gemfire/config/xml/GemfireOutboundChannelAdapterParserTests-context.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/spring-integration-gemfire/src/test/java/org/springframework/integration/gemfire/config/xml/GemfireOutboundChannelAdapterParserTests.java b/spring-integration-gemfire/src/test/java/org/springframework/integration/gemfire/config/xml/GemfireOutboundChannelAdapterParserTests.java index 0a10899d35..d1304b339d 100644 --- a/spring-integration-gemfire/src/test/java/org/springframework/integration/gemfire/config/xml/GemfireOutboundChannelAdapterParserTests.java +++ b/spring-integration-gemfire/src/test/java/org/springframework/integration/gemfire/config/xml/GemfireOutboundChannelAdapterParserTests.java @@ -13,24 +13,51 @@ package org.springframework.integration.gemfire.config.xml; -import org.junit.Test; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.w3c.dom.Element; - +import static org.junit.Assert.assertEquals; import static org.springframework.integration.gemfire.config.xml.ParserTestUtil.createFakeParserContext; import static org.springframework.integration.gemfire.config.xml.ParserTestUtil.loadXMLFrom; +import org.junit.Test; +import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; +import org.springframework.integration.message.GenericMessage; +import org.w3c.dom.Element; + /** * @author Dan Oxlade */ public class GemfireOutboundChannelAdapterParserTests { - private GemfireOutboundChannelAdapterParser underTest = new GemfireOutboundChannelAdapterParser(); + private GemfireOutboundChannelAdapterParser underTest = new GemfireOutboundChannelAdapterParser(); - @Test(expected = BeanDefinitionParsingException.class) - public void regionIsARequiredAttribute() throws Exception { - String xml = ""; - Element element = loadXMLFrom(xml).getDocumentElement(); - underTest.parseConsumer(element, createFakeParserContext()); - } + private volatile static int adviceCalled; + + @Test(expected = BeanDefinitionParsingException.class) + public void regionIsARequiredAttribute() throws Exception { + String xml = ""; + Element element = loadXMLFrom(xml).getDocumentElement(); + underTest.parseConsumer(element, createFakeParserContext()); + } + + @Test + public void withAdvice() { + ApplicationContext ctx = new ClassPathXmlApplicationContext(this.getClass().getSimpleName() + "-context.xml", this.getClass()); + MessageChannel channel = ctx.getBean("input", MessageChannel.class); + channel.send(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } + + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.2.xsd b/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.2.xsd index 627fee4e21..dc60b9a20e 100644 --- a/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.2.xsd +++ b/spring-integration-groovy/src/main/resources/org/springframework/integration/groovy/config/spring-integration-groovy-2.2.xsd @@ -49,10 +49,11 @@ ]]> - + - + + diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests-context.xml b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests-context.xml index cc92b14d75..3272ca7af6 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests-context.xml +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests-context.xml @@ -21,7 +21,11 @@ + customizer="groovyCustomizer"> + + + + diff --git a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests.java b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests.java index dc197d238d..531846b50b 100644 --- a/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests.java +++ b/spring-integration-groovy/src/test/java/org/springframework/integration/groovy/config/GroovyControlBusTests.java @@ -20,12 +20,11 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; +import groovy.lang.GroovyObject; import java.util.HashMap; import java.util.Map; -import groovy.lang.GroovyObject; - import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.BeanCreationException; @@ -36,6 +35,7 @@ import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.MessageHandlingException; import org.springframework.integration.core.PollableChannel; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.support.MessageBuilder; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; @@ -49,6 +49,7 @@ import org.springframework.web.context.request.RequestContextHolder; /** * @author Dave Syer * @author Artem Bilan + * @author Gary Russell * @since 2.0 */ @ContextConfiguration @@ -64,6 +65,8 @@ public class GroovyControlBusTests { @Autowired private MyGroovyCustomizer groovyCustomizer; + private static volatile int adviceCalled; + @Test public void testOperationOfControlBus() { // long is > 3 this.groovyCustomizer.executed = false; @@ -72,6 +75,7 @@ public class GroovyControlBusTests { assertEquals("catbar", output.receive(0).getPayload()); assertNull(output.receive(0)); assertTrue(this.groovyCustomizer.executed); + assertEquals(1, adviceCalled); } @Test //INT-2567 @@ -201,4 +205,13 @@ public class GroovyControlBusTests { } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return callback.execute(); + } + + } } diff --git a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.2.xsd b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.2.xsd index 75902c0f34..63abaae351 100644 --- a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.2.xsd +++ b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.2.xsd @@ -1,11 +1,13 @@ + - + - + + @@ -509,9 +512,10 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re - + - + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests-context.xml index 77d0a29fbe..08e59d432d 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests-context.xml @@ -40,6 +40,12 @@ + + + + + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java index 0b93513d47..3535b4e885 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java @@ -28,7 +28,6 @@ import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; - import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -41,8 +40,12 @@ import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.integration.Message; +import org.springframework.integration.core.MessageHandler; import org.springframework.integration.endpoint.AbstractEndpoint; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.test.util.TestUtils; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -52,6 +55,7 @@ import org.springframework.web.client.RestTemplate; /** * @author Mark Fisher + * @author Gary Russell */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @@ -75,12 +79,16 @@ public class HttpOutboundChannelAdapterParserTests { @Autowired @Qualifier("withUrlExpression") private AbstractEndpoint withUrlExpression; + @Autowired @Qualifier("withAdvice") + private AbstractEndpoint withAdvice; + @Autowired @Qualifier("withUrlExpressionAndTemplate") private AbstractEndpoint withUrlExpressionAndTemplate; @Autowired private ApplicationContext applicationContext; + private static volatile int adviceCalled; @Test public void minimalConfig() { @@ -201,6 +209,13 @@ public class HttpOutboundChannelAdapterParserTests { assertEquals(true, handlerAccessor.getPropertyValue("extractPayload")); } + @Test + public void withAdvice() { + MessageHandler handler = TestUtils.getPropertyValue(this.withAdvice, "handler", MessageHandler.class); + handler.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } + @Test public void withUrlExpressionAndTemplate() { DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(this.withUrlExpressionAndTemplate); @@ -239,4 +254,13 @@ public class HttpOutboundChannelAdapterParserTests { } } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml index b86162eb4a..57fbdd3f8b 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml @@ -42,6 +42,12 @@ + + + + + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java index f70be245bb..37e15b8400 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java @@ -26,7 +26,6 @@ import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; - import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -37,9 +36,12 @@ import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.endpoint.AbstractEndpoint; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.test.util.TestUtils; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -48,6 +50,7 @@ import org.springframework.web.client.ResponseErrorHandler; /** * @author Mark Fisher + * @author Gary Russell */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @@ -62,9 +65,13 @@ public class HttpOutboundGatewayParserTests { @Autowired @Qualifier("withUrlExpression") private AbstractEndpoint withUrlExpressionEndpoint; + @Autowired @Qualifier("withAdvice") + private AbstractEndpoint withAdvice; + @Autowired private ApplicationContext applicationContext; + private static volatile int adviceCalled; @Test public void minimalConfig() { @@ -160,6 +167,13 @@ public class HttpOutboundGatewayParserTests { assertEquals(false, handlerAccessor.getPropertyValue("transferCookies")); } + @Test + public void withAdvice() { + HttpRequestExecutingMessageHandler handler = (HttpRequestExecutingMessageHandler) new DirectFieldAccessor( + this.withAdvice).getPropertyValue("handler"); + handler.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } public static class StubErrorHandler implements ResponseErrorHandler { @@ -171,4 +185,13 @@ public class HttpOutboundGatewayParserTests { } } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-ip/src/main/resources/org/springframework/integration/ip/config/spring-integration-ip-2.2.xsd b/spring-integration-ip/src/main/resources/org/springframework/integration/ip/config/spring-integration-ip-2.2.xsd index 16df61d304..5745e91552 100644 --- a/spring-integration-ip/src/main/resources/org/springframework/integration/ip/config/spring-integration-ip-2.2.xsd +++ b/spring-integration-ip/src/main/resources/org/springframework/integration/ip/config/spring-integration-ip-2.2.xsd @@ -84,6 +84,9 @@ message headers (ip_hostName). Default "true". + + + @@ -176,6 +179,9 @@ task executors such as a WorkManagerTaskExecutor. + + + @@ -298,6 +304,9 @@ task executors such as a WorkManagerTaskExecutor. + + + diff --git a/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests-context.xml b/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests-context.xml index e3bfc95b9d..cbfd3564aa 100644 --- a/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests-context.xml +++ b/spring-integration-ip/src/test/java/org/springframework/integration/ip/config/ParserUnitTests-context.xml @@ -138,6 +138,15 @@ order="12" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ("foo")); + assertEquals(1, adviceCalled); + } + + @Test + public void tcpAdvice() { + adviceCalled = 0; + this.tcpAdviceChannel.send(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } + + @Test + public void tcpGatewayAdvice() { + adviceCalled = 0; + this.tcpAdviceGateChannel.send(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } + @Test public void testOutTcp() { DirectFieldAccessor dfa = new DirectFieldAccessor(tcpOut); @@ -592,4 +631,14 @@ public class ParserUnitTests { assertSame(socketFactorySupport, dfa.getPropertyValue("tcpSocketFactorySupport")); assertSame(socketSupport, dfa.getPropertyValue("tcpSocketSupport")); } + + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java index 53474cbfdb..1a0639fb08 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/JdbcOutboundGateway.java @@ -106,6 +106,7 @@ public class JdbcOutboundGateway extends AbstractReplyProducingMessageHandler im @Override protected void onInit() { + super.onInit(); if (this.maxRowsPerPoll != null) { Assert.notNull(poller, "If you want to set 'maxRowsPerPoll', then you must provide a 'selectQuery'."); diff --git a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/config/spring-integration-jdbc-2.2.xsd b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/config/spring-integration-jdbc-2.2.xsd index 46198b45a6..ba12b7ef3f 100644 --- a/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/config/spring-integration-jdbc-2.2.xsd +++ b/spring-integration-jdbc/src/main/resources/org/springframework/integration/jdbc/config/spring-integration-jdbc-2.2.xsd @@ -245,9 +245,10 @@ - + - + + @@ -334,7 +335,7 @@ - + @@ -357,7 +358,8 @@ - + + @@ -652,6 +654,7 @@ + @@ -787,6 +790,7 @@ + diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcMessageHandlerParserTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcMessageHandlerParserTests.java index afd4ed0299..e460214d4a 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcMessageHandlerParserTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcMessageHandlerParserTests.java @@ -1,11 +1,11 @@ /* * Copyright 2002-2012 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. @@ -28,6 +28,7 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.jdbc.JdbcMessageHandler; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; @@ -45,11 +46,13 @@ import org.springframework.jdbc.core.JdbcTemplate; public class JdbcMessageHandlerParserTests { private JdbcTemplate jdbcTemplate; - + private MessageChannel channel; - + private ConfigurableApplicationContext context; - + + private static volatile int adviceCalled; + @Test public void testSimpleOutboundChannelAdapter(){ setUp("handlingWithJdbcOperationsJdbcOutboundChannelAdapterTest.xml", getClass()); @@ -60,6 +63,7 @@ public class JdbcMessageHandlerParserTests { assertEquals("Wrong id", "foo", map.get("name")); JdbcMessageHandler handler = context.getBean(JdbcMessageHandler.class); assertEquals(23, TestUtils.getPropertyValue(handler, "order")); + assertEquals(1, adviceCalled); } @Test @@ -103,7 +107,7 @@ public class JdbcMessageHandlerParserTests { assertEquals("Wrong id", message.getHeaders().getId().toString(), map.get("ID")); assertEquals("Wrong name", "bar", map.get("name")); } - + @Test public void testOutboundAdapterWithPoller() throws Exception{ ApplicationContext ac = new ClassPathXmlApplicationContext("JdbcOutboundAdapterWithPollerTest-context.xml", this.getClass()); @@ -132,11 +136,20 @@ public class JdbcMessageHandlerParserTests { context.close(); } } - + public void setUp(String name, Class cls){ context = new ClassPathXmlApplicationContext(name, cls); jdbcTemplate = new JdbcTemplate(this.context.getBean("dataSource",DataSource.class)); channel = this.context.getBean("target", MessageChannel.class); } - + + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return callback.execute(); + } + + } } diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayParserTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayParserTests.java index 75b4f0a40f..79a1bf233d 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayParserTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/JdbcOutboundGatewayParserTests.java @@ -32,6 +32,7 @@ import org.springframework.integration.MessageChannel; import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.core.PollableChannel; import org.springframework.integration.endpoint.PollingConsumer; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.jdbc.JdbcOutboundGateway; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; @@ -55,6 +56,8 @@ public class JdbcOutboundGatewayParserTests { private MessagingTemplate messagingTemplate; + private static volatile int adviceCalled; + @Test public void testMapPayloadMapReply() { setUp("handlingMapPayloadJdbcOutboundGatewayTest.xml", getClass()); @@ -71,6 +74,8 @@ public class JdbcOutboundGatewayParserTests { assertEquals("bar", payload.get("name")); JdbcOutboundGateway gateway = context.getBean(JdbcOutboundGateway.class); assertEquals(23, TestUtils.getPropertyValue(gateway, "order")); + Object gw = context.getBean("jdbcGateway"); + assertEquals(1, adviceCalled); } @Test @@ -230,4 +235,13 @@ public class JdbcOutboundGatewayParserTests { setupMessagingTemplate(); } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return callback.execute(); + } + + } } diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/StoredProcMessageHandlerParserTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/StoredProcMessageHandlerParserTests.java index 5ef56e90a3..13edd19973 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/StoredProcMessageHandlerParserTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/StoredProcMessageHandlerParserTests.java @@ -27,14 +27,20 @@ import org.springframework.beans.DirectFieldAccessor; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.expression.Expression; +import org.springframework.integration.Message; +import org.springframework.integration.core.MessageHandler; import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.jdbc.storedproc.ProcedureParameter; +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.test.util.TestUtils; import org.springframework.jdbc.core.SqlInOutParameter; import org.springframework.jdbc.core.SqlOutParameter; import org.springframework.jdbc.core.SqlParameter; /** * @author Gunnar Hillert + * @author Gary Russell * @since 2.1 * */ @@ -44,6 +50,8 @@ public class StoredProcMessageHandlerParserTests { private EventDrivenConsumer consumer; + private static volatile int adviceCalled; + @Test public void testProcedureNameIsSet() throws Exception { setUp("basicStoredProcOutboundChannelAdapterTest.xml", getClass()); @@ -149,6 +157,15 @@ public class StoredProcMessageHandlerParserTests { } + @Test + public void adviceCalled() throws Exception { + setUp("advisedStoredProcOutboundChannelAdapterTest.xml", getClass()); + + MessageHandler handler = TestUtils.getPropertyValue(this.consumer, "handler", MessageHandler.class); + handler.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } + @After public void tearDown(){ if(context != null){ @@ -161,4 +178,13 @@ public class StoredProcMessageHandlerParserTests { consumer = this.context.getBean("storedProcedureOutboundChannelAdapter", EventDrivenConsumer.class); } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/StoredProcOutboundGatewayParserTests.java b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/StoredProcOutboundGatewayParserTests.java index 027be53dbe..cfe9a4dfa0 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/StoredProcOutboundGatewayParserTests.java +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/StoredProcOutboundGatewayParserTests.java @@ -30,10 +30,15 @@ import org.springframework.beans.DirectFieldAccessor; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.expression.Expression; +import org.springframework.integration.Message; +import org.springframework.integration.core.MessageHandler; import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.jdbc.storedproc.PrimeMapper; import org.springframework.integration.jdbc.storedproc.ProcedureParameter; +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.test.util.TestUtils; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.SqlInOutParameter; import org.springframework.jdbc.core.SqlOutParameter; @@ -41,6 +46,7 @@ import org.springframework.jdbc.core.SqlParameter; /** * @author Gunnar Hillert + * @author Gary Russell * @since 2.1 * */ @@ -50,6 +56,8 @@ public class StoredProcOutboundGatewayParserTests { private EventDrivenConsumer outboundGateway; + private static volatile int adviceCalled; + @Test public void testProcedureNameIsSet() throws Exception { setUp("storedProcOutboundGatewayParserTest.xml", getClass()); @@ -206,6 +214,15 @@ public class StoredProcOutboundGatewayParserTests { } + @Test + public void advised() throws Exception { + setUp("advisedStoredProcOutboundGatewayParserTest.xml", getClass()); + + MessageHandler handler = TestUtils.getPropertyValue(this.outboundGateway, "handler", MessageHandler.class); + handler.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } + @After public void tearDown(){ if(context != null){ @@ -218,4 +235,13 @@ public class StoredProcOutboundGatewayParserTests { this.outboundGateway = this.context.getBean("storedProcedureOutboundGateway", EventDrivenConsumer.class); } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/advisedStoredProcOutboundChannelAdapterTest.xml b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/advisedStoredProcOutboundChannelAdapterTest.xml new file mode 100644 index 0000000000..bf0dc8f6e6 --- /dev/null +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/advisedStoredProcOutboundChannelAdapterTest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/advisedStoredProcOutboundGatewayParserTest.xml b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/advisedStoredProcOutboundGatewayParserTest.xml new file mode 100644 index 0000000000..c9477ef1d1 --- /dev/null +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/advisedStoredProcOutboundGatewayParserTest.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/handlingMapPayloadJdbcOutboundGatewayTest.xml b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/handlingMapPayloadJdbcOutboundGatewayTest.xml index d081186d5e..e23878060a 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/handlingMapPayloadJdbcOutboundGatewayTest.xml +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/config/handlingMapPayloadJdbcOutboundGatewayTest.xml @@ -17,7 +17,11 @@ + request-channel="target" reply-channel="output" data-source="dataSource" order="23"> + + + + + channel="target" jdbc-operations="jdbcTemplate" order="23"> + + + + diff --git a/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java b/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java index e97363b404..91de46dbe3 100644 --- a/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java +++ b/spring-integration-jms/src/main/java/org/springframework/integration/jms/JmsOutboundGateway.java @@ -50,7 +50,7 @@ import org.springframework.util.Assert; /** * An outbound Messaging Gateway for request/reply JMS. - * + * * @author Mark Fisher * @author Arjen Poutsma * @author Juergen Hoeller @@ -182,7 +182,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler { * Specify whether the request destination is a Topic. This value is * necessary when providing a destination name for a Topic rather than * a destination reference. - * + * * @param requestPubSubDomain true if the request destination is a Topic */ public void setRequestPubSubDomain(boolean requestPubSubDomain) { @@ -193,7 +193,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler { * Specify whether the reply destination is a Topic. This value is * necessary when providing a destination name for a Topic rather than * a destination reference. - * + * * @param replyPubSubDomain true if the reply destination is a Topic */ public void setReplyPubSubDomain(boolean replyPubSubDomain) { @@ -275,8 +275,8 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler { } /** - * This property describes how a JMS Message should be generated from the - * Spring Integration Message. If set to 'true', the body of the JMS Message will be + * This property describes how a JMS Message should be generated from the + * Spring Integration Message. If set to 'true', the body of the JMS Message will be * created from the Spring Integration Message's payload (via the MessageConverter). * If set to 'false', then the entire Spring Integration Message will serve as * the base for JMS Message creation. Since the JMS Message is created by the @@ -284,7 +284,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler { * the entire Spring Integration Message or only its payload. *
* Default is 'true' - * + * * @param extractRequestPayload */ public void setExtractRequestPayload(boolean extractRequestPayload) { @@ -297,7 +297,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler { * created from the JMS Reply Message's body (via MessageConverter). * Otherwise, the entire JMS Message will become the payload of the * Spring Integration Message. - * + * * @param extractReplyPayload */ public void setExtractReplyPayload(boolean extractReplyPayload) { @@ -312,6 +312,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler { this.setOutputChannel(replyChannel); } + @Override public String getComponentType() { return "jms:outbound-gateway"; } @@ -369,6 +370,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler { ^ this.requestDestinationName != null ^ this.requestDestinationExpressionProcessor != null, "Exactly one of 'requestDestination', 'requestDestinationName', or 'requestDestinationExpression' is required."); + super.onInit(); if (this.requestDestinationExpressionProcessor != null) { this.requestDestinationExpressionProcessor.setBeanFactory(getBeanFactory()); this.requestDestinationExpressionProcessor.setConversionService(getConversionService()); @@ -516,7 +518,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler { "because that ID can only be provided to a MessageSelector after the reuqest Message has been sent thereby " + "creating a race condition where a fast response might be sent before the MessageConsumer has been created. " + "Consider providing a value to the 'correlationKey' property of this gateway instead. Then the MessageConsumer " + - "will be created before the request Message is sent."); + "will be created before the request Message is sent."); } MessageProducer messageProducer = null; MessageConsumer messageConsumer = null; @@ -554,7 +556,7 @@ public class JmsOutboundGateway extends AbstractReplyProducingMessageHandler { */ private void deleteDestinationIfTemporary(Destination destination) { try { - if (destination instanceof TemporaryQueue) { + if (destination instanceof TemporaryQueue) { ((TemporaryQueue) destination).delete(); } else if (destination instanceof TemporaryTopic) { diff --git a/spring-integration-jms/src/main/resources/org/springframework/integration/jms/config/spring-integration-jms-2.2.xsd b/spring-integration-jms/src/main/resources/org/springframework/integration/jms/config/spring-integration-jms-2.2.xsd index 62ae297da3..5c53c83dfe 100644 --- a/spring-integration-jms/src/main/resources/org/springframework/integration/jms/config/spring-integration-jms-2.2.xsd +++ b/spring-integration-jms/src/main/resources/org/springframework/integration/jms/config/spring-integration-jms-2.2.xsd @@ -731,9 +731,10 @@
- + - + + @@ -967,9 +968,10 @@ - + - + + diff --git a/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/JmsOutboundChannelAdapterParserTests.java b/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/JmsOutboundChannelAdapterParserTests.java index 370e302313..ac97178ecf 100644 --- a/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/JmsOutboundChannelAdapterParserTests.java +++ b/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/JmsOutboundChannelAdapterParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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. @@ -23,20 +23,27 @@ import static org.junit.Assert.assertTrue; import javax.jms.DeliveryMode; import org.junit.Test; - import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; +import org.springframework.integration.core.MessageHandler; import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.jms.JmsHeaderMapper; +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.test.util.TestUtils; import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.support.converter.MessageConverter; /** * @author Mark Fisher + * @author Gary Russell */ public class JmsOutboundChannelAdapterParserTests { + private static volatile int adviceCalled; + @Test public void adapterWithConnectionFactoryAndDestination() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( @@ -47,6 +54,16 @@ public class JmsOutboundChannelAdapterParserTests { assertNotNull(accessor.getPropertyValue("jmsTemplate")); } + @Test + public void advisedAdapter() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( + "jmsOutboundWithConnectionFactoryAndDestination.xml", this.getClass()); + EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("advised"); + MessageHandler handler = TestUtils.getPropertyValue(endpoint, "handler", MessageHandler.class); + handler.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } + @Test public void adapterWithConnectionFactoryAndDestinationName() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( @@ -164,4 +181,13 @@ public class JmsOutboundChannelAdapterParserTests { assertEquals(false, accessor.getPropertyValue("explicitQosEnabled")); } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/JmsOutboundGatewayParserTests.java b/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/JmsOutboundGatewayParserTests.java index 3702c1af56..4d4ca31b1c 100644 --- a/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/JmsOutboundGatewayParserTests.java +++ b/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/JmsOutboundGatewayParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -30,7 +30,6 @@ import javax.jms.DeliveryMode; import org.junit.Test; import org.mockito.Mockito; - import org.springframework.beans.DirectFieldAccessor; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.integration.Message; @@ -41,9 +40,11 @@ import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.core.SubscribableChannel; import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.endpoint.PollingConsumer; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.history.MessageHistory; import org.springframework.integration.jms.JmsOutboundGateway; import org.springframework.integration.jms.StubMessageConverter; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.test.util.TestUtils; import org.springframework.jms.support.converter.MessageConverter; @@ -51,9 +52,12 @@ import org.springframework.jms.support.converter.MessageConverter; * @author Jonas Partner * @author Oleg Zhurakousky * @author Mark Fisher + * @author Gary Russell */ public class JmsOutboundGatewayParserTests { - + + private static volatile int adviceCalled; + @Test public void testWithDeliveryPersistentAttribute(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( @@ -66,6 +70,16 @@ public class JmsOutboundGatewayParserTests { assertEquals(DeliveryMode.PERSISTENT, deliveryMode); } + @Test + public void testAdvised(){ + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( + "jmsOutboundGatewayWithDeliveryPersistent.xml", this.getClass()); + EventDrivenConsumer endpoint = (EventDrivenConsumer) context.getBean("advised"); + JmsOutboundGateway gateway = TestUtils.getPropertyValue(endpoint, "handler", JmsOutboundGateway.class); + gateway.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } + @Test public void testDefault(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( @@ -88,7 +102,7 @@ public class JmsOutboundGatewayParserTests { Object order = accessor.getPropertyValue("order"); assertEquals(99, order); } - + @Test public void gatewayMaintainsReplyChannelAndInboundHistory() { ActiveMqTestUtils.prepare(); @@ -96,7 +110,7 @@ public class JmsOutboundGatewayParserTests { "gatewayMaintainsReplyChannel.xml", this.getClass()); SampleGateway gateway = context.getBean("gateway", SampleGateway.class); SubscribableChannel jmsInput = context.getBean("jmsInput", SubscribableChannel.class); - MessageHandler handler = new MessageHandler() { + MessageHandler handler = new MessageHandler() { public void handleMessage(Message message) throws MessagingException { MessageHistory history = MessageHistory.read(message); assertNotNull(history); @@ -139,11 +153,20 @@ public class JmsOutboundGatewayParserTests { public static interface SampleGateway{ public String echo(String value); } - + public static class SampleService{ public String echo(String value){ return value.toUpperCase(); } } - + + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/jmsOutboundGatewayWithDeliveryPersistent.xml b/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/jmsOutboundGatewayWithDeliveryPersistent.xml index 9b9b65c1b4..b6f8e6074e 100644 --- a/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/jmsOutboundGatewayWithDeliveryPersistent.xml +++ b/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/jmsOutboundGatewayWithDeliveryPersistent.xml @@ -24,5 +24,14 @@ - + + + + + + + diff --git a/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/jmsOutboundWithConnectionFactoryAndDestination.xml b/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/jmsOutboundWithConnectionFactoryAndDestination.xml index 1daa93aac5..7681480200 100644 --- a/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/jmsOutboundWithConnectionFactoryAndDestination.xml +++ b/spring-integration-jms/src/test/java/org/springframework/integration/jms/config/jmsOutboundWithConnectionFactoryAndDestination.xml @@ -27,4 +27,13 @@ + + + + + + diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java index 363f304c80..5a47131102 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/OperationInvokingMessageHandler.java @@ -103,6 +103,7 @@ public class OperationInvokingMessageHandler extends AbstractReplyProducingMessa @Override public final void onInit() { Assert.notNull(this.server, "MBeanServer is required."); + super.onInit(); } @Override diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParser.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParser.java index 501fbb80d0..113e3c69f3 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParser.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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,16 +16,17 @@ package org.springframework.integration.jmx.config; -import org.w3c.dom.Element; - import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.jmx.NotificationPublishingMessageHandler; +import org.w3c.dom.Element; /** * @author Mark Fisher + * @author Gary Russell * @since 2.0 */ public class NotificationPublishingChannelAdapterParser extends AbstractOutboundChannelAdapterParser { @@ -37,8 +38,8 @@ public class NotificationPublishingChannelAdapterParser extends AbstractOutbound @Override protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition( - "org.springframework.integration.jmx.NotificationPublishingMessageHandler"); + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .rootBeanDefinition(NotificationPublishingMessageHandler.class); builder.addConstructorArgValue(element.getAttribute("object-name")); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "default-notification-type"); return builder.getBeanDefinition(); diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/OperationInvokingChannelAdapterParser.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/OperationInvokingChannelAdapterParser.java index 55c22940e3..2319aca529 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/OperationInvokingChannelAdapterParser.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/OperationInvokingChannelAdapterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2012 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,16 +16,17 @@ package org.springframework.integration.jmx.config; -import org.w3c.dom.Element; - import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.jmx.OperationInvokingMessageHandler; +import org.w3c.dom.Element; /** * @author Mark Fisher + * @author Gary Russell * @since 2.0 */ public class OperationInvokingChannelAdapterParser extends AbstractOutboundChannelAdapterParser { @@ -37,8 +38,7 @@ public class OperationInvokingChannelAdapterParser extends AbstractOutboundChann @Override protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition( - "org.springframework.integration.jmx.OperationInvokingMessageHandler"); + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(OperationInvokingMessageHandler.class); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "server"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "object-name"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "operation-name"); diff --git a/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-2.2.xsd b/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-2.2.xsd index 7f0a939f52..05cd858d68 100644 --- a/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-2.2.xsd +++ b/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-2.2.xsd @@ -42,6 +42,9 @@ + + + @@ -58,6 +61,9 @@ + + + @@ -90,6 +96,9 @@ + + + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParserTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParserTests-context.xml index d8b1f8c8db..58be1e19da 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParserTests-context.xml +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParserTests-context.xml @@ -22,7 +22,11 @@ + default-notification-type="default.type"> + + + + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParserTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParserTests.java index b3dc8b303d..015c7025fa 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParserTests.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/NotificationPublishingChannelAdapterParserTests.java @@ -28,6 +28,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.jmx.JmxHeaders; import org.springframework.integration.support.MessageBuilder; import org.springframework.test.context.ContextConfiguration; @@ -36,6 +37,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @author Mark Fisher * @author Artem Bilan + * @author Gary Russell * @since 2.0 */ @ContextConfiguration @@ -51,6 +53,8 @@ public class NotificationPublishingChannelAdapterParserTests { @Autowired private MessageChannel publishingWithinChainChannel; + private static volatile int adviceCalled; + @After public void clearListener() { listener.lastNotification = null; @@ -59,6 +63,7 @@ public class NotificationPublishingChannelAdapterParserTests { @Test public void publishStringMessage() throws Exception { + adviceCalled = 0; assertNull(listener.lastNotification); Message message = MessageBuilder.withPayload("XYZ") .setHeader(JmxHeaders.NOTIFICATION_TYPE, "test.type").build(); @@ -68,6 +73,7 @@ public class NotificationPublishingChannelAdapterParserTests { assertEquals("XYZ", notification.getMessage()); assertEquals("test.type", notification.getType()); assertNull(notification.getUserData()); + assertEquals(1, adviceCalled); } @Test @@ -110,4 +116,15 @@ public class NotificationPublishingChannelAdapterParserTests { private static class TestData { } + public static class FooADvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + System.out.println("foo"); + new RuntimeException("foo").printStackTrace(); + return callback.execute(); + } + + } } diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingChannelAdapterParserTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingChannelAdapterParserTests-context.xml index 642f15d064..4c6ec94578 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingChannelAdapterParserTests-context.xml +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingChannelAdapterParserTests-context.xml @@ -18,7 +18,11 @@ + operation-name="test"> + + + + ("test2")); input.send(new GenericMessage("test3")); assertEquals(3, testBean.messages.size()); + assertEquals(3, adviceCalled); } @Test @@ -118,4 +122,14 @@ public class OperationInvokingChannelAdapterParserTests { .setHeader(JmxHeaders.OBJECT_NAME, "org.springframework.integration.jmx.config:type=TestBean,name=foo") .setHeader(JmxHeaders.OPERATION_NAME, "blah").build(); } + + public static class FooADvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return callback.execute(); + } + + } } diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayTests-context.xml index f9742c7275..64482bbe5d 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayTests-context.xml +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayTests-context.xml @@ -27,7 +27,11 @@ + operation-name="testWithReturn"> + + + + ) withReplyChannelOutput.receive().getPayload()).size()); withReplyChannel.send(new GenericMessage("3")); assertEquals(3, ((List) withReplyChannelOutput.receive().getPayload()).size()); + assertEquals(3, adviceCalled); } @Test @@ -94,4 +99,13 @@ public class OperationInvokingOutboundGatewayTests { assertEquals(3, ((List) withReplyChannelOutput.receive().getPayload()).size()); } + public static class FooADvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return callback.execute(); + } + + } } diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaOutboundChannelAdapterParser.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaOutboundChannelAdapterParser.java index bc6b40d662..22e7b698ae 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaOutboundChannelAdapterParser.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaOutboundChannelAdapterParser.java @@ -73,11 +73,16 @@ public class JpaOutboundChannelAdapterParser extends AbstractOutboundChannelAdap BeanDefinition txAdviceDefinition = IntegrationNamespaceUtils.configureTransactionAttributes(transactionalElement); ManagedList adviceChain = new ManagedList(); adviceChain.add(txAdviceDefinition); - jpaOutboundChannelAdapterBuilder.addPropertyValue("adviceChain", adviceChain); + jpaOutboundChannelAdapterBuilder.addPropertyValue("txAdviceChain", adviceChain); } return jpaOutboundChannelAdapterBuilder.getBeanDefinition(); } + @Override + protected boolean isUsingReplyProducer() { + return true; + } + } diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayFactoryBean.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayFactoryBean.java index 439027b6c3..3914d23c83 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayFactoryBean.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayFactoryBean.java @@ -39,19 +39,34 @@ import org.springframework.util.CollectionUtils; * * @author Amol Nayak * @author Gunnar Hillert + * @author Gary Russell * @since 2.2 * */ public class JpaOutboundGatewayFactoryBean extends AbstractFactoryBean { - private final JpaExecutor jpaExecutor; + private final JpaExecutor jpaExecutor; + private OutboundGatewayType gatewayType = OutboundGatewayType.UPDATING; + /** + * <transactional /> element applies to entire flow from this point + */ + private volatile List txAdviceChain; + + /** + * <request-handler-advice-chain /> only applies to the handleRequestMessage. + */ private volatile List adviceChain; + private volatile ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + private boolean producesReply = true; + private MessageChannel outputChannel; + private int order; + private long replyTimeout; /** @@ -69,34 +84,8 @@ public class JpaOutboundGatewayFactoryBean extends AbstractFactoryBean getObjectType() { - return MessageHandler.class; - } - - @Override - protected MessageHandler createInstance() { - - JpaOutboundGateway jpaOutboundGateway = new JpaOutboundGateway(jpaExecutor); - jpaOutboundGateway.setGatewayType(this.gatewayType); - jpaOutboundGateway.setProducesReply(this.producesReply); - jpaOutboundGateway.setOutputChannel(this.outputChannel); - jpaOutboundGateway.setOrder(this.order); - jpaOutboundGateway.setSendTimeout(replyTimeout); - - if (!CollectionUtils.isEmpty(this.adviceChain)) { - - ProxyFactory proxyFactory = new ProxyFactory(jpaOutboundGateway); - if (!CollectionUtils.isEmpty(adviceChain)) { - for (Advice advice : adviceChain) { - proxyFactory.addAdvice(advice); - } - } - - return (MessageHandler) proxyFactory.getProxy(this.beanClassLoader); - } - - return jpaOutboundGateway; + public void setTxAdviceChain(List txAdviceChain) { + this.txAdviceChain = txAdviceChain; } public void setAdviceChain(List adviceChain) { @@ -125,7 +114,38 @@ public class JpaOutboundGatewayFactoryBean extends AbstractFactoryBean getObjectType() { + return MessageHandler.class; + } + @Override + protected MessageHandler createInstance() { + JpaOutboundGateway jpaOutboundGateway = new JpaOutboundGateway(jpaExecutor); + jpaOutboundGateway.setGatewayType(this.gatewayType); + jpaOutboundGateway.setProducesReply(this.producesReply); + jpaOutboundGateway.setOutputChannel(this.outputChannel); + jpaOutboundGateway.setOrder(this.order); + jpaOutboundGateway.setSendTimeout(replyTimeout); + if (this.adviceChain != null) { + jpaOutboundGateway.setAdviceChain(this.adviceChain); + } + jpaOutboundGateway.afterPropertiesSet(); + + if (!CollectionUtils.isEmpty(this.txAdviceChain)) { + + ProxyFactory proxyFactory = new ProxyFactory(jpaOutboundGateway); + if (!CollectionUtils.isEmpty(txAdviceChain)) { + for (Advice advice : txAdviceChain) { + proxyFactory.addAdvice(advice); + } + } + + return (MessageHandler) proxyFactory.getProxy(this.beanClassLoader); + } + + return jpaOutboundGateway; + } } diff --git a/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-2.2.xsd b/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-2.2.xsd index a3b3fcb6b3..33a0068d92 100644 --- a/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-2.2.xsd +++ b/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-2.2.xsd @@ -9,7 +9,7 @@ + schemaLocation="http://www.springframework.org/schema/integration/spring-integration-2.2.xsd" /> + @@ -187,6 +188,7 @@ + @@ -218,6 +220,7 @@
+ diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.java index 39f3421cd5..78e7df421f 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.java @@ -24,12 +24,16 @@ import org.junit.After; import org.junit.Test; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.core.MessageHandler; import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.jpa.core.JpaExecutor; import org.springframework.integration.jpa.core.JpaOperations; import org.springframework.integration.jpa.support.JpaParameter; import org.springframework.integration.jpa.support.PersistMode; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.test.util.TestUtils; /** @@ -44,6 +48,8 @@ public class JpaMessageHandlerParserTests { private EventDrivenConsumer consumer; + private static volatile int adviceCalled; + @Test public void testJpaMessageHandlerParser() throws Exception { setUp("JpaMessageHandlerParserTests.xml", getClass()); @@ -77,6 +83,47 @@ public class JpaMessageHandlerParserTests { } + @Test + public void advised() throws Exception { + setUp("JpaMessageHandlerParserTests.xml", getClass()); + + EventDrivenConsumer consumer = this.context.getBean("advised", EventDrivenConsumer.class); + + final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(consumer, "inputChannel", AbstractMessageChannel.class); + + assertEquals("target", inputChannel.getComponentName()); + + final MessageHandler handler = TestUtils.getPropertyValue(consumer, "handler", MessageHandler.class); + + adviceCalled = 0; + + handler.handleMessage(new GenericMessage("foo")); + + assertEquals(1, adviceCalled); + } + + /* + * Tests that an already advised handler (tx) gets the request handler advice added to its chain. + */ + @Test + public void advisedAndTransactional() throws Exception { + setUp("JpaMessageHandlerParserTests.xml", getClass()); + + EventDrivenConsumer consumer = this.context.getBean("advisedAndTransactional", EventDrivenConsumer.class); + + final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(consumer, "inputChannel", AbstractMessageChannel.class); + + assertEquals("target", inputChannel.getComponentName()); + + final MessageHandler handler = TestUtils.getPropertyValue(consumer, "handler", MessageHandler.class); + + adviceCalled = 0; + + handler.handleMessage(new GenericMessage("foo")); + + assertEquals(1, adviceCalled); + } + @Test public void testJpaMessageHandlerParserWithEntityManagerFactory() throws Exception { setUp("JpaMessageHandlerParserTestsWithEmFactory.xml", getClass()); @@ -156,7 +203,7 @@ public class JpaMessageHandlerParserTests { assertNotNull(context.getBean("jpaOutboundChannelAdapter.jpaExecutor", JpaExecutor.class)); } - + @After public void tearDown(){ if(context != null){ @@ -169,4 +216,13 @@ public class JpaMessageHandlerParserTests { consumer = this.context.getBean("jpaOutboundChannelAdapter", EventDrivenConsumer.class); } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.xml index 93e455e3fa..588dbad2b1 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.xml @@ -26,4 +26,37 @@ + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.java index 22413e1140..6bd1758a64 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.java @@ -20,18 +20,22 @@ import org.junit.After; import org.junit.Test; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; import org.springframework.integration.channel.AbstractMessageChannel; import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.jpa.core.JpaExecutor; import org.springframework.integration.jpa.core.JpaOperations; import org.springframework.integration.jpa.outbound.JpaOutboundGateway; import org.springframework.integration.jpa.support.OutboundGatewayType; import org.springframework.integration.jpa.support.PersistMode; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.test.util.TestUtils; /** * @author Gunnar Hillert * @author Amol Nayak + * @author Gary Russell * @since 2.2 * */ @@ -41,6 +45,8 @@ public class JpaOutboundGatewayParserTests { private EventDrivenConsumer consumer; + private static volatile int adviceCalled; + @Test public void testRetrievingJpaOutboundGatewayParser() throws Exception { setUp("JpaOutboundGatewayParserTests.xml", getClass(), "retrievingJpaOutboundGateway"); @@ -126,6 +132,16 @@ public class JpaOutboundGatewayParserTests { } + @Test + public void advised() throws Exception { + setUp("JpaOutboundGatewayParserTests.xml", getClass(), "updatingJpaOutboundGateway"); + + EventDrivenConsumer consumer = this.context.getBean("advised", EventDrivenConsumer.class); + final JpaOutboundGateway jpaOutboundGateway = TestUtils.getPropertyValue(consumer, "handler", JpaOutboundGateway.class); + jpaOutboundGateway.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } + @Test public void testJpaExecutorBeanIdNaming() throws Exception { @@ -148,4 +164,13 @@ public class JpaOutboundGatewayParserTests { consumer = this.context.getBean(gatewayId, EventDrivenConsumer.class); } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml index e081b85db5..9cffc8b0cc 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml @@ -36,4 +36,19 @@ reply-channel="out" reply-timeout="100"/> + + + + + + diff --git a/spring-integration-mail/src/main/resources/org/springframework/integration/mail/config/spring-integration-mail-2.2.xsd b/spring-integration-mail/src/main/resources/org/springframework/integration/mail/config/spring-integration-mail-2.2.xsd index 63006186a5..ea240f4b1e 100644 --- a/spring-integration-mail/src/main/resources/org/springframework/integration/mail/config/spring-integration-mail-2.2.xsd +++ b/spring-integration-mail/src/main/resources/org/springframework/integration/mail/config/spring-integration-mail-2.2.xsd @@ -26,9 +26,10 @@ Defines an outbound mail-sending Channel Adapter. - + - + + diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/MailOutboundChannelAdapterParserTests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/MailOutboundChannelAdapterParserTests.java index 7ae2580037..5c650d77ff 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/MailOutboundChannelAdapterParserTests.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/MailOutboundChannelAdapterParserTests.java @@ -16,27 +16,33 @@ package org.springframework.integration.mail.config; +import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + import java.util.Properties; import org.junit.Test; import org.springframework.beans.DirectFieldAccessor; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.core.MessageHandler; import org.springframework.integration.endpoint.PollingConsumer; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.mail.MailSendingMessageHandler; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.test.util.TestUtils; import org.springframework.mail.MailSender; -import static junit.framework.Assert.assertEquals; - -import static org.junit.Assert.assertNotNull; - /** * @author Mark Fisher + * @author Gary Russell */ public class MailOutboundChannelAdapterParserTests { + public static volatile int adviceCalled; + @Test public void adapterWithMailSenderReference() { ApplicationContext context = new ClassPathXmlApplicationContext( @@ -50,6 +56,17 @@ public class MailOutboundChannelAdapterParserTests { assertEquals(23, fieldAccessor.getPropertyValue("order")); } + @Test + public void advised() { + ApplicationContext context = new ClassPathXmlApplicationContext( + "mailOutboundChannelAdapterParserTests.xml", this.getClass()); + Object adapter = context.getBean("advised.adapter"); + MessageHandler handler = (MessageHandler) + new DirectFieldAccessor(adapter).getPropertyValue("handler"); + handler.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } + @Test public void adapterWithHostProperty() { ApplicationContext context = new ClassPathXmlApplicationContext( @@ -61,7 +78,7 @@ public class MailOutboundChannelAdapterParserTests { MailSender mailSender = (MailSender) fieldAccessor.getPropertyValue("mailSender"); assertNotNull(mailSender); } - + @Test public void adapterWithPollableChannel() { ApplicationContext context = new ClassPathXmlApplicationContext( @@ -70,7 +87,7 @@ public class MailOutboundChannelAdapterParserTests { QueueChannel pollableChannel = TestUtils.getPropertyValue(pc, "inputChannel", QueueChannel.class); assertEquals("pollableChannel", pollableChannel.getComponentName()); } - + @Test public void adapterWithJavaMailProperties() { ApplicationContext context = new ClassPathXmlApplicationContext( @@ -87,4 +104,13 @@ public class MailOutboundChannelAdapterParserTests { assertEquals("true", javaMailProperties.get("mail.smtps.auth")); } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/mailOutboundChannelAdapterParserTests.xml b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/mailOutboundChannelAdapterParserTests.xml index 55b32ce0c8..b8e242e922 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/mailOutboundChannelAdapterParserTests.xml +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/config/mailOutboundChannelAdapterParserTests.xml @@ -32,4 +32,11 @@ + + + + + + diff --git a/spring-integration-rmi/src/main/resources/org/springframework/integration/rmi/config/spring-integration-rmi-2.2.xsd b/spring-integration-rmi/src/main/resources/org/springframework/integration/rmi/config/spring-integration-rmi-2.2.xsd index 5ed498f799..ff7d8d0135 100644 --- a/spring-integration-rmi/src/main/resources/org/springframework/integration/rmi/config/spring-integration-rmi-2.2.xsd +++ b/spring-integration-rmi/src/main/resources/org/springframework/integration/rmi/config/spring-integration-rmi-2.2.xsd @@ -63,9 +63,10 @@ - + - + + diff --git a/spring-integration-rmi/src/test/java/org/springframework/integration/rmi/config/RmiOutboundGatewayParserTests.java b/spring-integration-rmi/src/test/java/org/springframework/integration/rmi/config/RmiOutboundGatewayParserTests.java index d219fcefea..d6043ce61b 100644 --- a/spring-integration-rmi/src/test/java/org/springframework/integration/rmi/config/RmiOutboundGatewayParserTests.java +++ b/spring-integration-rmi/src/test/java/org/springframework/integration/rmi/config/RmiOutboundGatewayParserTests.java @@ -27,6 +27,7 @@ import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.core.PollableChannel; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; import org.springframework.integration.message.GenericMessage; import org.springframework.integration.rmi.RmiInboundGateway; import org.springframework.integration.rmi.RmiOutboundGateway; @@ -42,6 +43,8 @@ public class RmiOutboundGatewayParserTests { private final QueueChannel testChannel = new QueueChannel(); + private static volatile int adviceCalled; + @Before public void setupTestInboundGateway() throws Exception { testChannel.setBeanName("testChannel"); @@ -55,7 +58,7 @@ public class RmiOutboundGatewayParserTests { public void testOrder() { ApplicationContext context = new ClassPathXmlApplicationContext( "rmiOutboundGatewayParserTests.xml", this.getClass()); - RmiOutboundGateway gateway = context.getBean(RmiOutboundGateway.class); + RmiOutboundGateway gateway = context.getBean("gateway.handler", RmiOutboundGateway.class); assertEquals(23, TestUtils.getPropertyValue(gateway, "order")); } @@ -63,11 +66,12 @@ public class RmiOutboundGatewayParserTests { public void directInvocation() { ApplicationContext context = new ClassPathXmlApplicationContext( "rmiOutboundGatewayParserTests.xml", this.getClass()); - MessageChannel localChannel = (MessageChannel) context.getBean("localChannel"); + MessageChannel localChannel = (MessageChannel) context.getBean("advisedChannel"); localChannel.send(new GenericMessage("test")); Message result = testChannel.receive(1000); assertNotNull(result); assertEquals("test", result.getPayload()); + assertEquals(1, adviceCalled); } @Test //INT-1029 @@ -93,4 +97,13 @@ public class RmiOutboundGatewayParserTests { assertEquals("TEST", result.getPayload()); } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return callback.execute(); + } + + } } diff --git a/spring-integration-rmi/src/test/java/org/springframework/integration/rmi/config/rmiOutboundGatewayParserTests.xml b/spring-integration-rmi/src/test/java/org/springframework/integration/rmi/config/rmiOutboundGatewayParserTests.xml index 1e3c4b80ed..8254ef1ccb 100644 --- a/spring-integration-rmi/src/test/java/org/springframework/integration/rmi/config/rmiOutboundGatewayParserTests.xml +++ b/spring-integration-rmi/src/test/java/org/springframework/integration/rmi/config/rmiOutboundGatewayParserTests.xml @@ -18,6 +18,17 @@ remote-channel="testChannel" host="localhost"/> + + + + + + + + diff --git a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.2.xsd b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.2.xsd index ebccdbca7a..1170d41246 100644 --- a/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.2.xsd +++ b/spring-integration-sftp/src/main/resources/org/springframework/integration/sftp/config/spring-integration-sftp-2.2.xsd @@ -21,7 +21,9 @@ - + + + @@ -235,7 +237,9 @@ - + + + diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context.xml index f0ce6448e3..04c984ca6f 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests-context.xml @@ -50,6 +50,17 @@ charset="UTF-8" use-temporary-file-name="false" remote-directory="foo/bar"/> + + + + + + diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests.java index 7d9931708d..c00137efef 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/OutboundChannelAdapterParserTests.java @@ -27,19 +27,21 @@ import java.util.Iterator; import java.util.Set; import org.junit.Test; - import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.expression.Expression; import org.springframework.expression.common.LiteralExpression; import org.springframework.expression.spel.standard.SpelExpression; +import org.springframework.integration.Message; import org.springframework.integration.channel.PublishSubscribeChannel; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.file.FileNameGenerator; import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler; import org.springframework.integration.file.remote.session.CachingSessionFactory; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.sftp.session.DefaultSftpSessionFactory; import org.springframework.integration.test.util.TestUtils; @@ -50,6 +52,8 @@ import org.springframework.integration.test.util.TestUtils; */ public class OutboundChannelAdapterParserTests { + private static volatile int adviceCalled; + @Test public void testOutboundChannelAdapterWithId(){ ApplicationContext context = @@ -117,6 +121,16 @@ public class OutboundChannelAdapterParserTests { assertFalse((Boolean)TestUtils.getPropertyValue(handler,"useTemporaryFileName")); } + @Test + public void advised(){ + ApplicationContext context = + new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context.xml", this.getClass()); + Object consumer = context.getBean("advised"); + MessageHandler handler = TestUtils.getPropertyValue(consumer, "handler", MessageHandler.class); + handler.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } + @Test(expected=BeanDefinitionStoreException.class) public void testFailWithRemoteDirAndExpression(){ new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context-fail.xml", this.getClass()); @@ -128,4 +142,14 @@ public class OutboundChannelAdapterParserTests { new ClassPathXmlApplicationContext("OutboundChannelAdapterParserTests-context-fail-fileFileGen.xml", this.getClass()); } + + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests-context.xml b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests-context.xml index 32baa477af..95484539ce 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests-context.xml +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests-context.xml @@ -43,6 +43,24 @@ order="2" /> + + + + + + diff --git a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests.java b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests.java index 60b246ae02..dd459a44c6 100644 --- a/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests.java +++ b/spring-integration-sftp/src/test/java/org/springframework/integration/sftp/config/SftpOutboundGatewayParserTests.java @@ -26,8 +26,11 @@ import java.util.Set; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.Message; import org.springframework.integration.endpoint.AbstractEndpoint; import org.springframework.integration.file.remote.session.CachingSessionFactory; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.sftp.gateway.SftpOutboundGateway; import org.springframework.integration.test.util.TestUtils; import org.springframework.test.context.ContextConfiguration; @@ -50,6 +53,11 @@ public class SftpOutboundGatewayParserTests { @Autowired AbstractEndpoint gateway2; + @Autowired + AbstractEndpoint advised; + + private static volatile int adviceCalled; + @Test public void testGateway1() { SftpOutboundGateway gateway = TestUtils.getPropertyValue(gateway1, @@ -85,4 +93,22 @@ public class SftpOutboundGatewayParserTests { Set options = TestUtils.getPropertyValue(gateway, "options", Set.class); assertTrue(options.contains("-P")); } + + @Test + public void advised() { + SftpOutboundGateway gateway = TestUtils.getPropertyValue(advised, + "handler", SftpOutboundGateway.class); + gateway.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } + + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-twitter/src/main/resources/org/springframework/integration/twitter/config/spring-integration-twitter-2.2.xsd b/spring-integration-twitter/src/main/resources/org/springframework/integration/twitter/config/spring-integration-twitter-2.2.xsd index 4d969ce032..1f3269d07d 100644 --- a/spring-integration-twitter/src/main/resources/org/springframework/integration/twitter/config/spring-integration-twitter-2.2.xsd +++ b/spring-integration-twitter/src/main/resources/org/springframework/integration/twitter/config/spring-integration-twitter-2.2.xsd @@ -159,6 +159,9 @@ + + + diff --git a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/config/TestSendingMessageHandlerParser-context.xml b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/config/TestSendingMessageHandlerParser-context.xml index 2a14b52895..e3c6e36120 100644 --- a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/config/TestSendingMessageHandlerParser-context.xml +++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/config/TestSendingMessageHandlerParser-context.xml @@ -29,6 +29,20 @@ + + + + + + + + + + + + diff --git a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/config/TestSendingMessageHandlerParserTests.java b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/config/TestSendingMessageHandlerParserTests.java index 9a56a8280f..d8a50ea46d 100644 --- a/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/config/TestSendingMessageHandlerParserTests.java +++ b/spring-integration-twitter/src/test/java/org/springframework/integration/twitter/config/TestSendingMessageHandlerParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -17,12 +17,16 @@ package org.springframework.integration.twitter.config; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; import org.junit.Test; - import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; +import org.springframework.integration.core.MessageHandler; import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.test.util.TestUtils; import org.springframework.integration.twitter.outbound.DirectMessageSendingMessageHandler; @@ -33,13 +37,32 @@ import org.springframework.integration.twitter.outbound.DirectMessageSendingMess */ public class TestSendingMessageHandlerParserTests { + private static volatile int adviceCalled; + @Test public void testSendingMessageHandlerSuccessfulBootstrap(){ ApplicationContext ac = new ClassPathXmlApplicationContext("TestSendingMessageHandlerParser-context.xml", this.getClass()); EventDrivenConsumer dmAdapter = ac.getBean("dmAdapter", EventDrivenConsumer.class); - Object handler = TestUtils.getPropertyValue(dmAdapter, "handler"); + MessageHandler handler = TestUtils.getPropertyValue(dmAdapter, "handler", MessageHandler.class); assertEquals(DirectMessageSendingMessageHandler.class, handler.getClass()); assertEquals(23, TestUtils.getPropertyValue(handler, "order")); + dmAdapter = ac.getBean("dmAdvised", EventDrivenConsumer.class); + handler = TestUtils.getPropertyValue(dmAdapter, "handler", MessageHandler.class); + handler.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + MessageHandler handler2 = TestUtils.getPropertyValue(ac.getBean("advised"), "handler", MessageHandler.class); + assertNotSame(handler, handler2); + handler2.handleMessage(new GenericMessage("foo")); + assertEquals(2, adviceCalled); } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-2.2.xsd b/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-2.2.xsd index 1e20f1bf66..ce308ea07e 100644 --- a/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-2.2.xsd +++ b/spring-integration-ws/src/main/resources/org/springframework/integration/ws/config/spring-integration-ws-2.2.xsd @@ -29,6 +29,7 @@ + diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java index 472a98075e..849f9f9db8 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/WebServiceOutboundGatewayParserTests.java @@ -16,17 +16,24 @@ package org.springframework.integration.ws.config; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertNull; + import java.util.List; import org.junit.Test; - import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.Message; +import org.springframework.integration.core.MessageHandler; import org.springframework.integration.endpoint.AbstractEndpoint; import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.endpoint.PollingConsumer; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.test.util.TestUtils; import org.springframework.integration.ws.MarshallingWebServiceOutboundGateway; import org.springframework.integration.ws.SimpleWebServiceOutboundGateway; @@ -40,18 +47,16 @@ import org.springframework.ws.client.core.WebServiceMessageCallback; import org.springframework.ws.client.support.interceptor.ClientInterceptor; import org.springframework.ws.transport.WebServiceMessageSender; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertTrue; - -import static org.junit.Assert.assertNull; - /** * @author Mark Fisher * @author Oleg Zhurakousky * @author Gunnar Hillert + * @author Gary Russell */ public class WebServiceOutboundGatewayParserTests { + private static volatile int adviceCalled; + @Test public void simpleGatewayWithReplyChannel() { ApplicationContext context = new ClassPathXmlApplicationContext( @@ -360,6 +365,17 @@ public class WebServiceOutboundGatewayParserTests { assertEquals("Wrong DestinationProvider", stubProvider,destinationProviderObject); } + @Test + public void advised() { + ApplicationContext context = new ClassPathXmlApplicationContext( + "simpleWebServiceOutboundGatewayParserTests.xml", this.getClass()); + AbstractEndpoint endpoint = (AbstractEndpoint) context.getBean("gatewayWithAdvice"); + assertEquals(EventDrivenConsumer.class, endpoint.getClass()); + MessageHandler handler = TestUtils.getPropertyValue(endpoint, "handler", MessageHandler.class); + handler.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } + @Test(expected = BeanDefinitionParsingException.class) public void invalidGatewayWithBothUriAndDestinationProvider() { new ClassPathXmlApplicationContext("invalidGatewayWithBothUriAndDestinationProvider.xml", this.getClass()); @@ -370,4 +386,13 @@ public class WebServiceOutboundGatewayParserTests { new ClassPathXmlApplicationContext("invalidGatewayWithNeitherUriNorDestinationProvider.xml", this.getClass()); } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/simpleWebServiceOutboundGatewayParserTests.xml b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/simpleWebServiceOutboundGatewayParserTests.xml index 3d49c505ec..d991f24cf9 100644 --- a/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/simpleWebServiceOutboundGatewayParserTests.xml +++ b/spring-integration-ws/src/test/java/org/springframework/integration/ws/config/simpleWebServiceOutboundGatewayParserTests.xml @@ -102,6 +102,14 @@ request-channel="inputChannel" destination-provider="destinationProvider" /> + + + + + + diff --git a/spring-integration-xmpp/src/main/resources/org/springframework/integration/xmpp/config/spring-integration-xmpp-2.2.xsd b/spring-integration-xmpp/src/main/resources/org/springframework/integration/xmpp/config/spring-integration-xmpp-2.2.xsd index 763a44aa6d..483316d8d4 100644 --- a/spring-integration-xmpp/src/main/resources/org/springframework/integration/xmpp/config/spring-integration-xmpp-2.2.xsd +++ b/spring-integration-xmpp/src/main/resources/org/springframework/integration/xmpp/config/spring-integration-xmpp-2.2.xsd @@ -214,9 +214,10 @@ - + - + + diff --git a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests-context.xml b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests-context.xml index beffa34209..6c05125532 100644 --- a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests-context.xml @@ -43,6 +43,13 @@ xmpp-connection="testConnection"> + + + + + + diff --git a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests.java b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests.java index 73fc340b86..02356947f9 100644 --- a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests.java +++ b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/ChatMessageOutboundChannelAdapterParserTests.java @@ -16,6 +16,11 @@ package org.springframework.integration.xmpp.config; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + import java.util.List; import org.jivesoftware.smack.XMPPConnection; @@ -24,15 +29,17 @@ import org.junit.runner.RunWith; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.core.MessageHandler; import org.springframework.integration.core.SubscribableChannel; import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.endpoint.PollingConsumer; +import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice; +import org.springframework.integration.message.GenericMessage; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; import org.springframework.integration.xmpp.XmppHeaders; @@ -41,11 +48,6 @@ import org.springframework.integration.xmpp.support.XmppHeaderMapper; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertTrue; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - /** * @author Oleg Zhurakousky * @author Mark Fisher @@ -57,10 +59,12 @@ public class ChatMessageOutboundChannelAdapterParserTests { @Autowired private ApplicationContext context; - + @Autowired private XmppHeaderMapper headerMapper; + private static volatile int adviceCalled; + @Test public void testPollingConsumer() { Object pollingConsumer = context.getBean("withHeaderMapper"); @@ -75,11 +79,19 @@ public class ChatMessageOutboundChannelAdapterParserTests { assertTrue(eventConsumer instanceof SubscribableChannel); } + @Test + public void advised() { + MessageHandler handler = TestUtils.getPropertyValue(context.getBean("advised"), + "handler", MessageHandler.class); + handler.handleMessage(new GenericMessage("foo")); + assertEquals(1, adviceCalled); + } + @SuppressWarnings("unchecked") @Test public void testEventConsumer() { Object eventConsumer = context.getBean("outboundEventAdapter"); - DefaultXmppHeaderMapper headerMapper = + DefaultXmppHeaderMapper headerMapper = TestUtils.getPropertyValue(eventConsumer, "handler.headerMapper", DefaultXmppHeaderMapper.class); List requestHeaderNames = TestUtils.getPropertyValue(headerMapper, "requestHeaderNames", List.class); assertEquals(2, requestHeaderNames.size()); @@ -98,7 +110,7 @@ public class ChatMessageOutboundChannelAdapterParserTests { Message message = MessageBuilder.withPayload("hello").setHeader(XmppHeaders.TO, "oleg"). setHeader("foobar", "foobar").build(); XMPPConnection connection = context.getBean("testConnection", XMPPConnection.class); - + Mockito.doAnswer(new Answer() { public Object answer(InvocationOnMock invocation) { Object[] args = invocation.getArguments(); @@ -108,9 +120,9 @@ public class ChatMessageOutboundChannelAdapterParserTests { return null; }}) .when(connection).sendPacket(Mockito.any(org.jivesoftware.smack.packet.Message.class)); - + channel.send(message); - + verify(connection, times(1)).sendPacket(Mockito.any(org.jivesoftware.smack.packet.Message.class)); Mockito.reset(connection); } @@ -137,4 +149,13 @@ public class ChatMessageOutboundChannelAdapterParserTests { Mockito.reset(connection); } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } } diff --git a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/PresenceOutboundChannelAdapterParserTests-context.xml b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/PresenceOutboundChannelAdapterParserTests-context.xml index b9529edfae..63be35a8fa 100644 --- a/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/PresenceOutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-xmpp/src/test/java/org/springframework/integration/xmpp/config/PresenceOutboundChannelAdapterParserTests-context.xml @@ -29,6 +29,16 @@ xmpp-connection="testConnection" channel="eventChannel" order="34"/> + + + + + + + ("foo")); + assertEquals(1, adviceCalled); + } + @Test public void testRosterEventOutboundChannel(){ Object channel = context.getBean("eventOutboundRosterChannel"); @@ -78,4 +91,13 @@ public class PresenceOutboundChannelAdapterParserTests { assertEquals(45, TestUtils.getPropertyValue(handlers.toArray()[0], "order")); } + public static class FooAdvice extends AbstractRequestHandlerAdvice { + + @Override + protected Object doInvoke(ExecutionCallback callback, Object target, Message message) throws Exception { + adviceCalled++; + return null; + } + + } }