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 <request-handler-advice-chain/> 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.
This commit is contained in:
Gary Russell
2012-07-14 13:00:38 -04:00
committed by Oleg Zhurakousky
parent 56e3c22970
commit 08cbab08c2
114 changed files with 2661 additions and 296 deletions

View File

@@ -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',

View File

@@ -368,6 +368,9 @@
Base type for the 'outbound-channel-adapter' and 'outbound-gateway' elements.
</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="exchange-name" type="xsd:string">
<xsd:annotation>
<xsd:documentation>

View File

@@ -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">
<amqp:outbound-channel-adapter id="rabbitOutbound" exchange-name="outboundchanneladapter.test.1"/>
<amqp:outbound-channel-adapter id="rabbitOutbound" exchange-name="outboundchanneladapter.test.1">
<amqp:request-handler-advice-chain>
<bean class="org.springframework.integration.amqp.config.AmqpOutboundChannelAdapterParserTests$FooAdvice" />
</amqp:request-handler-advice-chain>
</amqp:outbound-channel-adapter>
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>

View File

@@ -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<String>("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;
}
}
}

View File

@@ -55,7 +55,11 @@
amqp-template="amqpTemplate"
order="5"
mapped-request-headers=""
mapped-reply-headers=""/>
mapped-reply-headers="">
<amqp:request-handler-advice-chain>
<bean class="org.springframework.integration.amqp.config.AmqpOutboundGatewayParserTests$FooAdvice" />
</amqp:request-handler-advice-chain>
</amqp:outbound-gateway>
<int:channel id="returnChannel">
<int:queue/>

View File

@@ -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();
}
}
}

View File

@@ -91,6 +91,8 @@ public final class MessageHeaders implements Map<String, Object>, Serializable {
public static final String DISPOSITION_RESULT = "dispositionResult";
public static final String POSTPROCESS_RESULT = "postProcessResult";
private final Map<String, Object> headers;
@@ -164,12 +166,12 @@ public final class MessageHeaders implements Map<String, Object>, 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;

View File

@@ -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<H extends MessageHandler> implements FactoryBean<MessageHandler>, BeanFactoryAware {
@@ -43,6 +49,8 @@ public abstract class AbstractSimpleMessageHandlerFactoryBean<H extends MessageH
private final Object initializationMonitor = new Object();
private volatile List<Advice> adviceChain;
public AbstractSimpleMessageHandlerFactoryBean() {
super();
@@ -64,6 +72,10 @@ public abstract class AbstractSimpleMessageHandlerFactoryBean<H extends MessageH
return this.beanFactory;
}
public void setAdviceChain(List<Advice> 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<H extends MessageH
if (this.handler instanceof MessageProducer && this.outputChannel != null) {
((MessageProducer) this.handler).setOutputChannel(this.outputChannel);
}
if (this.handler instanceof BeanFactoryAware) {
((BeanFactoryAware) this.handler).setBeanFactory(beanFactory);
}
if (this.handler instanceof Orderable && this.order != null) {
((Orderable) this.handler).setOrder(this.order.intValue());
}
@@ -91,6 +100,10 @@ public abstract class AbstractSimpleMessageHandlerFactoryBean<H extends MessageH
if (handler instanceof BeanFactoryAware) {
((BeanFactoryAware) handler).setBeanFactory(getBeanFactory());
}
if (!CollectionUtils.isEmpty(this.adviceChain) &&
this.handler instanceof AbstractReplyProducingMessageHandler) {
((AbstractReplyProducingMessageHandler) this.handler).setAdviceChain(this.adviceChain);
}
this.initialized = true;
}
if (handler instanceof InitializingBean) {

View File

@@ -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.
@@ -15,10 +15,15 @@
*/
package org.springframework.integration.config;
import java.util.List;
import org.aopalliance.aop.Advice;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
@@ -36,8 +41,10 @@ import org.springframework.integration.core.SubscribableChannel;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
@@ -56,13 +63,13 @@ public class ConsumerEndpointFactoryBean
private volatile String inputChannelName;
private volatile PollerMetadata pollerMetadata;
private volatile boolean autoStartup = true;
private volatile MessageChannel inputChannel;
private volatile ConfigurableBeanFactory beanFactory;
private volatile ClassLoader beanClassLoader;
private volatile AbstractEndpoint endpoint;
@@ -75,6 +82,8 @@ public class ConsumerEndpointFactoryBean
private final Log logger = LogFactory.getLog(this.getClass());
private volatile List<Advice> 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<Advice> 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);

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 &lt;poller&gt; 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) {

View File

@@ -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<Advice> 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<Advice> 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 <code>null</code>. In that case, the header value must not also be
* <code>null</code>, 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();
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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<Object> onSuccessMessageProcessor;
private final MessageChannel successChannel;
private final ExpressionEvaluatingMessageProcessor<Object> 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<Object>(onSuccessExpression);
this.onSuccessMessageProcessor.setBeanFactory(this.beanFactory);
}
else {
this.onSuccessMessageProcessor = null;
}
this.successChannel = successChannel;
if (onFailureExpression != null) {
this.onFailureMessageProcessor = new ExpressionEvaluatingMessageProcessor<Object>(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<Object> 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;
}
}

View File

@@ -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<Object, AdvisedMetadata> metadataMap = new ConcurrentHashMap<Object, AdvisedMetadata>();
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;
}
}
}

View File

@@ -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<Object> 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<Object> 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<Object>(){
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);
}
}

View File

@@ -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;

View File

@@ -1124,6 +1124,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="request-handler-advice-chain" type="adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="request-channel" type="xsd:string" use="optional">
<xsd:annotation>
@@ -1481,28 +1482,7 @@
<xsd:sequence>
<xsd:choice>
<xsd:element name="transactional" type="transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="advice-chain" minOccurs="0" maxOccurs="1">
<xsd:complexType>
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="ref" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:attribute name="bean" type="xsd:string" use="required">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="java.lang.Object" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:any namespace="##other" processContents="strict" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="advice-chain" type="adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element name="psuedo-transactional" type="pseudoTransactionalType" minOccurs="0" maxOccurs="1">
@@ -3422,12 +3402,34 @@ is provided, the return value is expected to match a channel name exactly.
<xsd:attributeGroup ref="transactionSyncAttributeGroup" />
</xsd:complexType>
<xsd:complexType name="adviceChainType">
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="ref" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:attribute name="bean" type="xsd:string" use="required">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="java.lang.Object" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:any namespace="##other" processContents="strict" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="expressionOrInnerEndpointDefinitionAware">
<xsd:complexContent>
<xsd:extension base="handlerEndpointType">
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:choice minOccurs="0" maxOccurs="3">
<xsd:element name="poller" type="basePollerType" minOccurs="0" maxOccurs="1" />
<xsd:element name="expression" type="innerExpressionType" minOccurs="0" maxOccurs="1" />
<xsd:element name="request-handler-advice-chain" type="adviceChainType" minOccurs="0" maxOccurs="1" />
<xsd:any namespace="##other" processContents="strict" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="expression" type="xsd:string">

View File

@@ -23,7 +23,11 @@
<queue capacity="1"/>
</channel>
<filter ref="selectorBean" method="hasText" input-channel="adapterInput" output-channel="adapterOutput"/>
<filter ref="selectorBean" method="hasText" input-channel="adapterInput" output-channel="adapterOutput">
<request-handler-advice-chain>
<beans:bean class="org.springframework.integration.config.FilterParserTests$FooFilter" />
</request-handler-advice-chain>
</filter>
<beans:bean id="selectorBean"
class="org.springframework.integration.config.FilterParserTests$TestSelectorBean"/>

View File

@@ -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<String>("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();
}
}
}

View File

@@ -22,6 +22,10 @@
<property name="name" expression="payload.sourceName"/>
<property name="age" value="42"/>
<property name="gender" expression="@testBean"/>
<request-handler-advice-chain>
<beans:bean class="org.springframework.integration.config.xml.EnricherParserTests$FooAdvice" />
</request-handler-advice-chain>
</enricher>
<beans:bean id="testBean" class="java.lang.String">

View File

@@ -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();
}
}
}

View File

@@ -21,4 +21,9 @@
<beans:bean id="testBean" class="org.springframework.integration.config.xml.ServiceActivatorParserTests$TestBean"/>
<service-activator id="withAdvice" input-channel="advisedInput" expression="'foo'">
<request-handler-advice-chain>
<beans:bean class="org.springframework.integration.config.xml.ServiceActivatorParserTests$BarAdvice" />
</request-handler-advice-chain>
</service-activator>
</beans:beans>

View File

@@ -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";
}
}
}

View File

@@ -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<String> message = new GenericMessage<String>("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<Advice> adviceChain = new ArrayList<Advice>();
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<String> message = new GenericMessage<String>("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<Advice> adviceChain = new ArrayList<Advice>();
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<String> message = new GenericMessage<String>("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<Advice> adviceChain = new ArrayList<Advice>();
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<Advice> adviceChain = new ArrayList<Advice>();
adviceChain.add(advice);
handler.setAdviceChain(adviceChain);
handler.afterPropertiesSet();
doFail.set(true);
Message<String> message = new GenericMessage<String>("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<Advice> adviceChain = new ArrayList<Advice>();
adviceChain.add(advice);
handler.setAdviceChain(adviceChain);
handler.afterPropertiesSet();
Message<String> message = new GenericMessage<String>("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<Advice> adviceChain = new ArrayList<Advice>();
adviceChain.add(advice);
handler.setAdviceChain(adviceChain);
handler.afterPropertiesSet();
Message<String> message = new GenericMessage<String>("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<Object>() {
public Object recover(RetryContext context) throws Exception {
return "baz";
}
});
List<Advice> adviceChain = new ArrayList<Advice>();
adviceChain.add(advice);
handler.setAdviceChain(adviceChain);
handler.afterPropertiesSet();
Message<String> message = new GenericMessage<String>("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());
}
}

View File

@@ -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<String>("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();
}
}
}

View File

@@ -13,7 +13,11 @@
<queue capacity="50"/>
</channel>
<transformer input-channel="input" ref="testBean" method="upperCase" output-channel="output"/>
<transformer input-channel="input" ref="testBean" method="upperCase" output-channel="output">
<request-handler-advice-chain>
<beans:bean class="org.springframework.integration.transformer.TransformerContextTests$FooAdvice" />
</request-handler-advice-chain>
</transformer>
<beans:bean id="testBean" class="org.springframework.integration.transformer.TestBean"/>

View File

@@ -81,9 +81,10 @@
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:all>
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1" />
</xsd:all>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="channel" type="xsd:string">
<xsd:annotation>

View File

@@ -11,6 +11,14 @@
<int-event:outbound-channel-adapter id="eventAdapter" channel="input"/>
<int:channel id="inputAdvice"/>
<int-event:outbound-channel-adapter id="withAdvice" channel="inputAdvice">
<int-event:request-handler-advice-chain>
<bean class="org.springframework.integration.event.config.EventOutboundChannelAdapterParserTests$FooAdvice" />
</int-event:request-handler-advice-chain>
</int-event:outbound-channel-adapter>
<int:chain input-channel="inputChain">
<int:transformer expression="payload + 'bar'"/>
<int-event:outbound-channel-adapter/>

View File

@@ -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<ApplicationEvent>() {
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<String>("hello"));
Assert.assertTrue(receivedEvent);
Assert.assertEquals(1, adviceCalled);
}
@Test //INT-2275
public void testInsideChain() {
receivedEvent = false;
ApplicationListener<?> listener = new ApplicationListener<ApplicationEvent>() {
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<ApplicationEvent>() {
@@ -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();
}
}
}

View File

@@ -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();

View File

@@ -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 &lt;outbound-channel-adapter/&gt; 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;
}
}

View File

@@ -255,9 +255,10 @@ Only files matching this regular expression will be picked up by this adapter.
</xsd:element>
<xsd:complexType name="outboundFileBaseType">
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[Identifies the underlying Spring bean definition (EventDrivenConsumer)]]></xsd:documentation>

View File

@@ -47,7 +47,11 @@
<file:outbound-channel-adapter id="usageChannel"
filename-generator-expression="'fileToAppend.txt'"
mode="APPEND"
directory="test"/>
directory="test">
<file:request-handler-advice-chain>
<bean class="org.springframework.integration.file.config.FileOutboundChannelAdapterParserTests$FooAdvice" />
</file:request-handler-advice-chain>
</file:outbound-channel-adapter>
<file:outbound-channel-adapter id="usageChannelWithFailMode"
filename-generator-expression="'fileToAppend.txt'"

View File

@@ -29,15 +29,16 @@ import java.nio.charset.Charset;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.Expression;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.MessagingException;
import org.springframework.expression.Expression;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileWritingMessageHandler;
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;
@@ -88,6 +89,8 @@ public class FileOutboundChannelAdapterParserTests {
@Autowired
MessageChannel usageChannelConcurrent;
private volatile static int adviceCalled;
@Test
public void simpleAdapter() {
DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(simpleAdapter);
@@ -272,5 +275,14 @@ public class FileOutboundChannelAdapterParserTests {
for (char character : characters) {
assertEquals(c, character);
}
}
public static class FooAdvice extends AbstractRequestHandlerAdvice {
@Override
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception {
adviceCalled++;
return callback.execute();
}
}
}

View File

@@ -19,7 +19,11 @@
<int-file:outbound-gateway id="gatewayWithDirectoryExpression"
request-channel="someChannel" directory-expression="'build/foo'"
auto-startup="false" order="777" filename-generator-expression="'foo.txt'" />
auto-startup="false" order="777" filename-generator-expression="'foo.txt'">
<int-file:request-handler-advice-chain>
<beans:bean class="org.springframework.integration.file.config.FileOutboundGatewayParserTests$FooAdvice" />
</int-file:request-handler-advice-chain>
</int-file:outbound-gateway>
<int-file:outbound-gateway id="gatewayWithReplaceMode"
request-channel="gatewayWithReplaceModeChannel"

View File

@@ -35,6 +35,7 @@ import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileWritingMessageHandler;
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;
@@ -70,6 +71,8 @@ public class FileOutboundGatewayParserTests {
@Autowired
MessageChannel gatewayWithFailModeLowercaseChannel;
private volatile static int adviceCalled;
@Test
public void checkOrderedGateway() throws Exception {
@@ -94,6 +97,8 @@ public class FileOutboundGatewayParserTests {
public void testOutboundGatewayWithDirectoryExpression() throws Exception {
FileWritingMessageHandler handler = TestUtils.getPropertyValue(gatewayWithDirectoryExpression, "handler", FileWritingMessageHandler.class);
assertEquals("'build/foo'", TestUtils.getPropertyValue(handler, "destinationDirectoryExpression", Expression.class).getExpressionString());
handler.handleMessage(new GenericMessage<String>("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;
}
}
}

View File

@@ -20,6 +20,9 @@
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="base-ftp-adapter-type">
<xsd:all>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:all>
<xsd:attribute name="remote-directory-expression"
type="xsd:string">
<xsd:annotation>
@@ -232,6 +235,9 @@
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="base-adapter-type">
<xsd:all>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:all>
<xsd:attribute name="command" use="required" type="xsd:string">
<xsd:annotation>
<xsd:documentation>

View File

@@ -59,7 +59,16 @@
channel="ftpChannel"
session-factory="cachingSessionFactory"
remote-directory="foo/bar"/>
<int-ftp:outbound-channel-adapter id="advisedAdapter"
channel="ftpChannel"
session-factory="cachingSessionFactory"
remote-directory="foo/bar">
<int-ftp:request-handler-advice-chain>
<bean class="org.springframework.integration.ftp.config.FtpOutboundChannelAdapterParserTests$FooAdvice" />
</int-ftp:request-handler-advice-chain>
</int-ftp:outbound-channel-adapter>
<int:channel id="anotherFtpChannel"/>
<int:publish-subscribe-channel id="ftpChannel"/>

View File

@@ -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<String>("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;
}
}
}

View File

@@ -41,7 +41,11 @@
command-options="-P"
expression="payload"
order="2"
/>
>
<int-ftp:request-handler-advice-chain>
<bean class="org.springframework.integration.ftp.config.FtpOutboundGatewayParserTests$FooAdvice" />
</int-ftp:request-handler-advice-chain>
</int-ftp:outbound-gateway>
<int:channel id="outbound"/>

View File

@@ -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<String> options = TestUtils.getPropertyValue(gateway, "options", Set.class);
assertTrue(options.contains("-P"));
gateway.handleMessage(new GenericMessage<String>("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;
}
}
}

View File

@@ -132,7 +132,7 @@
writes Message to a Gemfire cache
</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element name="cache-entries" type="beans:mapType"
minOccurs="0" maxOccurs="1">
<xsd:annotation>
@@ -142,7 +142,8 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string" use="optional" />

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int-gfe="http://www.springframework.org/schema/integration/gemfire"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/integration/gemfire http://www.springframework.org/schema/integration/gemfire/spring-integration-gemfire-2.2.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<int:channel id="input" />
<int-gfe:outbound-channel-adapter region="region" channel="input">
<int-gfe:request-handler-advice-chain>
<bean class="org.springframework.integration.gemfire.config.xml.GemfireOutboundChannelAdapterParserTests$FooAdvice" />
</int-gfe:request-handler-advice-chain>
</int-gfe:outbound-channel-adapter>
<bean id="region" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.gemstone.gemfire.cache.Region" />
</bean>
</beans>

View File

@@ -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 = "<outbound-channel-adapter />";
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 = "<outbound-channel-adapter />";
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<String>("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;
}
}
}

View File

@@ -49,10 +49,11 @@
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:all minOccurs="0" maxOccurs="1">
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element ref="integration:poller" minOccurs="0"
maxOccurs="1" />
</xsd:all>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attributeGroup ref="integration:inputOutputChannelGroupWithId" />
<xsd:attribute name="customizer" type="xsd:string">
<xsd:annotation>

View File

@@ -21,7 +21,11 @@
</beans:bean>
<groovy:control-bus input-channel="input" output-channel="output" send-timeout="100" order="1" auto-startup="true"
customizer="groovyCustomizer"/>
customizer="groovyCustomizer">
<groovy:request-handler-advice-chain>
<beans:bean class="org.springframework.integration.groovy.config.GroovyControlBusTests$FooAdvice" />
</groovy:request-handler-advice-chain>
</groovy:control-bus>
<beans:bean id="service" class="org.springframework.integration.groovy.config.GroovyControlBusTests$Service" />

View File

@@ -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();
}
}
}

View File

@@ -1,11 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.springframework.org/schema/integration/http" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:integration="http://www.springframework.org/schema/integration"
targetNamespace="http://www.springframework.org/schema/integration/http" elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans" />
<xsd:import namespace="http://www.springframework.org/schema/tool" />
<xsd:import namespace="http://www.springframework.org/schema/integration" schemaLocation="http://www.springframework.org/schema/integration/spring-integration-2.2.xsd" />
<xsd:annotation>
<xsd:documentation><![CDATA[
@@ -347,9 +349,10 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re
Defines an outbound HTTP-based Channel Adapter.
</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element name="uri-variable" type="uriVariableType" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="url" type="xsd:string" use="optional">
<xsd:annotation>
@@ -509,9 +512,10 @@ The String "HTTP_REQUEST_HEADERS" will match against any of the standard HTTP Re
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="gatewayType">
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element name="uri-variable" type="uriVariableType" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="request-channel" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>

View File

@@ -40,6 +40,12 @@
<outbound-channel-adapter id="withUrlExpression" url-expression="'http://localhost/test1'" channel="requests"/>
<outbound-channel-adapter id="withAdvice" url-expression="'http://localhost/test1'" channel="requests">
<request-handler-advice-chain>
<beans:bean class="org.springframework.integration.http.config.HttpOutboundChannelAdapterParserTests$FooAdvice" />
</request-handler-advice-chain>
</outbound-channel-adapter>
<outbound-channel-adapter id="withUrlExpressionAndTemplate"
url-expression="'http://localhost/test1'" channel="requests"
rest-template="customRestTemplate"/>

View File

@@ -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<String>("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;
}
}
}

View File

@@ -42,6 +42,12 @@
<outbound-gateway id="withUrlExpression" url-expression="'http://localhost/test1'" request-channel="requests"/>
<outbound-gateway id="withAdvice" url-expression="'http://localhost/test1'" request-channel="requests">
<request-handler-advice-chain>
<beans:bean class="org.springframework.integration.http.config.HttpOutboundGatewayParserTests$FooAdvice" />
</request-handler-advice-chain>
</outbound-gateway>
<beans:bean id="testRequestFactory" class="org.springframework.http.client.SimpleClientHttpRequestFactory"/>
<beans:bean id="testErrorHandler" class="org.springframework.integration.http.config.HttpOutboundGatewayParserTests$StubErrorHandler"/>

View File

@@ -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<String>("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;
}
}
}

View File

@@ -84,6 +84,9 @@ message headers (ip_hostName). Default "true".
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="udpAdapterType">
<xsd:all>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:all>
<xsd:attribute name="host" type="xsd:string" />
<xsd:attribute name="acknowledge" type="xsd:string" />
<xsd:attribute name="ack-host" type="xsd:string" />
@@ -176,6 +179,9 @@ task executors such as a WorkManagerTaskExecutor.
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="smartLifeCycleType">
<xsd:all>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:all>
<xsd:attribute name="id" type="xsd:string"/>
<xsd:attribute name="connection-factory" type="xsd:string">
<xsd:annotation>
@@ -298,6 +304,9 @@ task executors such as a WorkManagerTaskExecutor.
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="smartLifeCycleType">
<xsd:all>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:all>
<xsd:attribute name="id" type="xsd:string"/>
<xsd:attribute name="connection-factory" type="xsd:string">
<xsd:annotation>

View File

@@ -138,6 +138,15 @@
order="12"
/>
<int:channel id="udpAdviceChannel" />
<ip:udp-outbound-channel-adapter channel="udpAdviceChannel" host="localhost" port="0">
<ip:request-handler-advice-chain>
<bean class="org.springframework.integration.ip.config.ParserUnitTests$FooAdvice" />
</ip:request-handler-advice-chain>
</ip:udp-outbound-channel-adapter>
<ip:tcp-connection-factory id="cfC1"
type="client"
port="#{tcpIpUtils.findAvailableServerSocket(5700)}"
@@ -154,6 +163,21 @@
phase="125"
/>
<bean id="mockClientCf" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.ip.tcp.connection.AbstractClientConnectionFactory" />
</bean>
<int:channel id="tcpAdviceChannel" />
<ip:tcp-outbound-channel-adapter id="tcpOutAdvice"
channel="tcpAdviceChannel"
connection-factory="mockClientCf"
phase="125">
<ip:request-handler-advice-chain>
<bean class="org.springframework.integration.ip.config.ParserUnitTests$FooAdvice" />
</ip:request-handler-advice-chain>
</ip:tcp-outbound-channel-adapter>
<ip:tcp-connection-factory id="cfS2"
type="server"
port="#{tcpIpUtils.findAvailableServerSocket(5800)}"
@@ -199,6 +223,17 @@
phase="127"
/>
<int:channel id="tcpAdviceGateChannel" />
<ip:tcp-outbound-gateway id="outAdviceGateway"
request-channel="tcpAdviceGateChannel"
reply-channel="replyChannel"
connection-factory="mockClientCf">
<ip:request-handler-advice-chain>
<bean class="org.springframework.integration.ip.config.ParserUnitTests$FooAdvice" />
</ip:request-handler-advice-chain>
</ip:tcp-outbound-gateway>
<ip:tcp-connection-factory
id="client1"
type="client"

View File

@@ -36,11 +36,13 @@ import org.springframework.context.ApplicationContext;
import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.Serializer;
import org.springframework.core.task.TaskExecutor;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.channel.DirectChannel;
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.ip.tcp.TcpInboundGateway;
import org.springframework.integration.ip.tcp.TcpOutboundGateway;
import org.springframework.integration.ip.tcp.TcpReceivingChannelAdapter;
@@ -59,6 +61,7 @@ import org.springframework.integration.ip.udp.MulticastReceivingChannelAdapter;
import org.springframework.integration.ip.udp.MulticastSendingMessageHandler;
import org.springframework.integration.ip.udp.UnicastReceivingChannelAdapter;
import org.springframework.integration.ip.udp.UnicastSendingMessageHandler;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.test.context.ContextConfiguration;
@@ -115,6 +118,10 @@ public class ParserUnitTests {
@Qualifier(value="outGateway.handler")
TcpOutboundGateway tcpOutboundGateway;
@Autowired
@Qualifier(value="outAdviceGateway.handler")
TcpOutboundGateway outAdviceGateway;
// verify we can still inject by generated name
@Autowired
@Qualifier(value="org.springframework.integration.ip.tcp.TcpOutboundGateway#0")
@@ -195,6 +202,15 @@ public class ParserUnitTests {
@Autowired
private DirectChannel udpChannel;
@Autowired
private DirectChannel udpAdviceChannel;
@Autowired
private DirectChannel tcpAdviceChannel;
@Autowired
private DirectChannel tcpAdviceGateChannel;
@Autowired
private DirectChannel tcpChannel;
@@ -232,6 +248,8 @@ public class ParserUnitTests {
@Autowired
TcpSocketSupport socketSupport;
private static volatile int adviceCalled;
@Test
public void testInUdp() {
DirectFieldAccessor dfa = new DirectFieldAccessor(udpIn);
@@ -353,6 +371,27 @@ public class ParserUnitTests {
assertSame(this.udpOut, iterator.next());
}
@Test
public void udpAdvice() {
adviceCalled = 0;
this.udpAdviceChannel.send(new GenericMessage<String>("foo"));
assertEquals(1, adviceCalled);
}
@Test
public void tcpAdvice() {
adviceCalled = 0;
this.tcpAdviceChannel.send(new GenericMessage<String>("foo"));
assertEquals(1, adviceCalled);
}
@Test
public void tcpGatewayAdvice() {
adviceCalled = 0;
this.tcpAdviceGateChannel.send(new GenericMessage<String>("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;
}
}
}

View File

@@ -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'.");

View File

@@ -245,9 +245,10 @@
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="queryType">
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="sql-parameter-source-factory" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
@@ -334,7 +335,7 @@
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="selectType">
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="3">
<xsd:element name="update" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:appinfo>
@@ -357,7 +358,8 @@
</xsd:annotation>
</xsd:element>
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string" use="optional"/>
<xsd:attribute name="update" type="xsd:string">
<xsd:annotation>
@@ -652,6 +654,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attributeGroup ref="coreStoredProcComponentAttributes"/>
<xsd:attribute name="use-payload-as-parameter-source">
@@ -787,6 +790,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
</xsd:sequence>
<xsd:attributeGroup ref="coreStoredProcComponentAttributes"/>

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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<String>("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;
}
}
}

View File

@@ -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<String>("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;
}
}
}

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:int-jdbc="http://www.springframework.org/schema/integration/jdbc"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/jdbc http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<int:channel id="target"/>
<jdbc:embedded-database id="dataSource" type="HSQL"/>
<int-jdbc:message-store id="messageStore" data-source="dataSource"/>
<int-jdbc:stored-proc-outbound-channel-adapter id="storedProcedureOutboundChannelAdapter"
data-source="dataSource" channel="target"
stored-procedure-name="testProcedure1">
<int-jdbc:sql-parameter-definition name="username" direction="IN" type="VARCHAR"/>
<int-jdbc:sql-parameter-definition name="password" direction="OUT" />
<int-jdbc:sql-parameter-definition name="age" direction="INOUT" type="INTEGER" scale="5"/>
<int-jdbc:sql-parameter-definition name="description" />
<int-jdbc:parameter name="username" value="kenny" type="java.lang.String"/>
<int-jdbc:parameter name="description" value="Who killed Kenny?"/>
<int-jdbc:parameter name="password" expression="payload.username"/>
<int-jdbc:parameter name="age" value="30" type="java.lang.Integer"/>
<int-jdbc:request-handler-advice-chain>
<bean class="org.springframework.integration.jdbc.config.StoredProcMessageHandlerParserTests$FooAdvice" />
</int-jdbc:request-handler-advice-chain>
</int-jdbc:stored-proc-outbound-channel-adapter>
</beans>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:int-jdbc="http://www.springframework.org/schema/integration/jdbc"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/jdbc http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<int:channel id="requestChannel" />
<int:channel id="replyChannel" />
<jdbc:embedded-database id="datasource" type="HSQL" />
<int-jdbc:stored-proc-outbound-gateway
request-channel="requestChannel" stored-procedure-name="GET_PRIME_NUMBERS"
data-source="datasource" auto-startup="true" id="storedProcedureOutboundGateway"
ignore-column-meta-data="false" is-function="false"
skip-undeclared-results="false" order="2" reply-channel="replyChannel"
reply-timeout="555" return-value-required="false">
<int-jdbc:sql-parameter-definition name="username" direction="IN" type="VARCHAR" />
<int-jdbc:sql-parameter-definition name="password" direction="OUT" />
<int-jdbc:sql-parameter-definition name="age" direction="INOUT" type="INTEGER" scale="5" />
<int-jdbc:sql-parameter-definition name="description" />
<int-jdbc:parameter name="username" value="kenny" type="java.lang.String" />
<int-jdbc:parameter name="description" value="Who killed Kenny?" />
<int-jdbc:parameter name="password" expression="payload.username" />
<int-jdbc:parameter name="age" value="30" type="java.lang.Integer" />
<int-jdbc:returning-resultset name="out" row-mapper="org.springframework.integration.jdbc.storedproc.PrimeMapper"/>
<int-jdbc:request-handler-advice-chain>
<bean class="org.springframework.integration.jdbc.config.StoredProcOutboundGatewayParserTests$FooAdvice" />
</int-jdbc:request-handler-advice-chain>
</int-jdbc:stored-proc-outbound-gateway>
<int:poller default="true" fixed-rate="10000" />
</beans>

View File

@@ -17,7 +17,11 @@
</si:channel>
<outbound-gateway id="jdbcGateway" query="select * from foos where id=:headers[id]" update="insert into foos (id, status, name) values (:headers[id], 0, :payload[foo])"
request-channel="target" reply-channel="output" data-source="dataSource" order="23"/>
request-channel="target" reply-channel="output" data-source="dataSource" order="23">
<request-handler-advice-chain>
<beans:bean class="org.springframework.integration.jdbc.config.JdbcOutboundGatewayParserTests$FooAdvice" />
</request-handler-advice-chain>
</outbound-gateway>
<si:chain input-channel="jdbcOutboundGatewayInsideChain" output-channel="replyChannel">
<outbound-gateway query="select * from foos where id=:headers[id]"

View File

@@ -11,7 +11,11 @@
http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd">
<outbound-channel-adapter query="insert into foos (id, status, name) values (:headers[business.key], 0, :payload)"
channel="target" jdbc-operations="jdbcTemplate" order="23"/>
channel="target" jdbc-operations="jdbcTemplate" order="23">
<request-handler-advice-chain>
<beans:bean class="org.springframework.integration.jdbc.config.JdbcMessageHandlerParserTests$FooAdvice" />
</request-handler-advice-chain>
</outbound-channel-adapter>
<beans:import resource="jdbcOutboundChannelAdapterCommonConfig.xml" />

View File

@@ -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.
* <br>
* 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) {

View File

@@ -731,9 +731,10 @@
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string"/>
<xsd:attribute name="request-channel" type="xsd:string">
<xsd:annotation>
@@ -967,9 +968,10 @@
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="jmsAdapterType">
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="destination" type="xsd:string">
<xsd:annotation>
<xsd:documentation>

View File

@@ -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<String>("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;
}
}
}

View File

@@ -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<String>("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;
}
}
}

View File

@@ -24,5 +24,14 @@
</bean>
</constructor-arg>
</bean>
<jms:outbound-gateway id="advised"
request-destination-name="requestQueue"
request-channel="requestChannel"
delivery-persistent="true">
<jms:request-handler-advice-chain>
<bean class="org.springframework.integration.jms.config.JmsOutboundGatewayParserTests$FooAdvice" />
</jms:request-handler-advice-chain>
</jms:outbound-gateway>
</beans>

View File

@@ -27,4 +27,13 @@
<bean id="testDestination" class="org.springframework.integration.jms.StubDestination"/>
<jms:outbound-channel-adapter id="advised"
channel="input"
connection-factory="testConnectionFactory"
destination="testDestination">
<jms:request-handler-advice-chain>
<bean class="org.springframework.integration.jms.config.JmsOutboundChannelAdapterParserTests$FooAdvice" />
</jms:request-handler-advice-chain>
</jms:outbound-channel-adapter>
</beans>

View File

@@ -103,6 +103,7 @@ public class OperationInvokingMessageHandler extends AbstractReplyProducingMessa
@Override
public final void onInit() {
Assert.notNull(this.server, "MBeanServer is required.");
super.onInit();
}
@Override

View File

@@ -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();

View File

@@ -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");

View File

@@ -42,6 +42,9 @@
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="operationInvokingType">
<xsd:all>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:all>
<xsd:attribute name="request-channel" type="xsd:string" />
<xsd:attribute name="reply-channel" type="xsd:string" use="optional" />
</xsd:extension>
@@ -58,6 +61,9 @@
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="operationInvokingType">
<xsd:all>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:all>
<xsd:attribute name="channel" type="xsd:string" use="optional" />
</xsd:extension>
</xsd:complexContent>
@@ -90,6 +96,9 @@
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="adapterType">
<xsd:all>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:all>
<xsd:attribute name="default-notification-type" type="xsd:string" use="optional" />
</xsd:extension>
</xsd:complexContent>

View File

@@ -22,7 +22,11 @@
<jmx:notification-publishing-channel-adapter
id="adapter" channel="channel"
object-name="test.publisher:name=publisher"
default-notification-type="default.type"/>
default-notification-type="default.type">
<jmx:request-handler-advice-chain>
<bean class="org.springframework.integration.jmx.config.NotificationPublishingChannelAdapterParserTests$FooADvice" />
</jmx:request-handler-advice-chain>
</jmx:notification-publishing-channel-adapter>
<si:chain input-channel="publishingWithinChainChannel">

View File

@@ -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();
}
}
}

View File

@@ -18,7 +18,11 @@
<jmx:operation-invoking-channel-adapter id="input"
object-name="org.springframework.integration.jmx.config:type=TestBean,name=testBeanAdapter"
operation-name="test"/>
operation-name="test">
<jmx:request-handler-advice-chain>
<bean class="org.springframework.integration.jmx.config.OperationInvokingChannelAdapterParserTests$FooADvice" />
</jmx:request-handler-advice-chain>
</jmx:operation-invoking-channel-adapter>
<jmx:operation-invoking-channel-adapter id="operationWithNonNullReturn"
object-name="org.springframework.integration.jmx.config:type=TestBean,name=testBeanAdapter"

View File

@@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.MessagingException;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.integration.jmx.JmxHeaders;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
@@ -58,6 +59,8 @@ public class OperationInvokingChannelAdapterParserTests {
@Autowired
private TestBean testBean;
private static volatile int adviceCalled;
@After
public void resetLists() {
testBean.messages.clear();
@@ -71,6 +74,7 @@ public class OperationInvokingChannelAdapterParserTests {
input.send(new GenericMessage<String>("test2"));
input.send(new GenericMessage<String>("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();
}
}
}

View File

@@ -27,7 +27,11 @@
<jmx:operation-invoking-outbound-gateway request-channel="withReplyChannel"
reply-channel="withReplyChannelOutput"
object-name="org.springframework.integration.jmx.config:type=TestBean,name=testBeanGateway"
operation-name="testWithReturn"/>
operation-name="testWithReturn">
<jmx:request-handler-advice-chain>
<bean class="org.springframework.integration.jmx.config.OperationInvokingOutboundGatewayTests$FooADvice" />
</jmx:request-handler-advice-chain>
</jmx:operation-invoking-outbound-gateway>
<si:chain input-channel="jmxOutboundGatewayInsideChain" output-channel="withReplyChannelOutput">
<jmx:operation-invoking-outbound-gateway operation-name="testWithReturn"

View File

@@ -25,8 +25,10 @@ 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.PollableChannel;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.test.context.ContextConfiguration;
@@ -59,6 +61,8 @@ public class OperationInvokingOutboundGatewayTests {
@Autowired
private TestBean testBean;
private static volatile int adviceCalled;
@After
public void resetLists() {
testBean.messages.clear();
@@ -72,6 +76,7 @@ public class OperationInvokingOutboundGatewayTests {
assertEquals(2, ((List<?>) withReplyChannelOutput.receive().getPayload()).size());
withReplyChannel.send(new GenericMessage<String>("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();
}
}
}

View File

@@ -73,11 +73,16 @@ public class JpaOutboundChannelAdapterParser extends AbstractOutboundChannelAdap
BeanDefinition txAdviceDefinition = IntegrationNamespaceUtils.configureTransactionAttributes(transactionalElement);
ManagedList<BeanDefinition> adviceChain = new ManagedList<BeanDefinition>();
adviceChain.add(txAdviceDefinition);
jpaOutboundChannelAdapterBuilder.addPropertyValue("adviceChain", adviceChain);
jpaOutboundChannelAdapterBuilder.addPropertyValue("txAdviceChain", adviceChain);
}
return jpaOutboundChannelAdapterBuilder.getBeanDefinition();
}
@Override
protected boolean isUsingReplyProducer() {
return true;
}
}

View File

@@ -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<MessageHandler> {
private final JpaExecutor jpaExecutor;
private final JpaExecutor jpaExecutor;
private OutboundGatewayType gatewayType = OutboundGatewayType.UPDATING;
/**
* &lt;transactional /&gt; element applies to entire flow from this point
*/
private volatile List<Advice> txAdviceChain;
/**
* &lt;request-handler-advice-chain /&gt; only applies to the handleRequestMessage.
*/
private volatile List<Advice> 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<MessageHa
this.gatewayType = gatewayType;
}
@Override
public Class<?> 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<Advice> txAdviceChain) {
this.txAdviceChain = txAdviceChain;
}
public void setAdviceChain(List<Advice> adviceChain) {
@@ -125,7 +114,38 @@ public class JpaOutboundGatewayFactoryBean extends AbstractFactoryBean<MessageHa
public void setReplyTimeout(long replyTimeout) {
this.replyTimeout = replyTimeout;
}
@Override
public Class<?> 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;
}
}

View File

@@ -9,7 +9,7 @@
<xsd:import namespace="http://www.springframework.org/schema/beans" />
<xsd:import namespace="http://www.springframework.org/schema/tool" />
<xsd:import namespace="http://www.springframework.org/schema/integration"
schemaLocation="http://www.springframework.org/schema/integration/spring-integration-2.1.xsd" />
schemaLocation="http://www.springframework.org/schema/integration/spring-integration-2.2.xsd" />
<xsd:annotation>
<xsd:documentation><![CDATA[
@@ -96,6 +96,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attributeGroup ref="coreJpaComponentAttributes"/>
<xsd:attributeGroup ref="commonUpdatingJpaAttributes"/>
@@ -187,6 +188,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attributeGroup ref="coreJpaComponentAttributes" />
<xsd:attributeGroup ref="commonUpdatingJpaAttributes"/>
@@ -218,6 +220,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attributeGroup ref="coreJpaComponentAttributes" />
<xsd:attributeGroup ref="commonJpaOutboundGatewayAttributes"/>

View File

@@ -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<String>("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<String>("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;
}
}
}

View File

@@ -26,4 +26,37 @@
<int-jpa:parameter name="updatedDateTime" expression="new java.util.Date()"/>
</int-jpa:outbound-channel-adapter>
<int-jpa:outbound-channel-adapter id="advised"
entity-manager="entityManager"
auto-startup="true"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
jpa-query="from Student"
persist-mode="PERSIST"
order="1"
channel="target">
<int-jpa:parameter name="firstName" value="kenny" type="java.lang.String"/>
<int-jpa:parameter name="firstaName" value="cartman"/>
<int-jpa:parameter name="updatedDateTime" expression="new java.util.Date()"/>
<int-jpa:request-handler-advice-chain>
<bean class="org.springframework.integration.jpa.config.xml.JpaMessageHandlerParserTests$FooAdvice" />
</int-jpa:request-handler-advice-chain>
</int-jpa:outbound-channel-adapter>
<int-jpa:outbound-channel-adapter id="advisedAndTransactional"
entity-manager="entityManager"
auto-startup="true"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
jpa-query="from Student"
persist-mode="PERSIST"
order="1"
channel="target">
<int-jpa:transactional />
<int-jpa:parameter name="firstName" value="kenny" type="java.lang.String"/>
<int-jpa:parameter name="firstaName" value="cartman"/>
<int-jpa:parameter name="updatedDateTime" expression="new java.util.Date()"/>
<int-jpa:request-handler-advice-chain>
<bean class="org.springframework.integration.jpa.config.xml.JpaMessageHandlerParserTests$FooAdvice" />
</int-jpa:request-handler-advice-chain>
</int-jpa:outbound-channel-adapter>
</beans>

View File

@@ -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<String>("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;
}
}
}

View File

@@ -36,4 +36,19 @@
reply-channel="out"
reply-timeout="100"/>
<int-jpa:updating-outbound-gateway id="advised"
entity-manager-factory="entityManagerFactory"
auto-startup="false"
entity-class="org.springframework.integration.jpa.test.entity.StudentDomain"
persist-mode="PERSIST"
use-payload-as-parameter-source="true"
order="2"
request-channel="in"
reply-channel="out"
reply-timeout="100">
<int-jpa:request-handler-advice-chain>
<bean class="org.springframework.integration.jpa.config.xml.JpaOutboundGatewayParserTests$FooAdvice" />
</int-jpa:request-handler-advice-chain>
</int-jpa:updating-outbound-gateway>
</beans>

View File

@@ -26,9 +26,10 @@
Defines an outbound mail-sending Channel Adapter.
</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string"/>
<xsd:attribute name="channel" type="xsd:string">
<xsd:annotation>

View File

@@ -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<String>("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;
}
}
}

View File

@@ -32,4 +32,11 @@
</constructor-arg>
</bean>
<int-mail:outbound-channel-adapter id="advised"
mail-sender="mailSender">
<int-mail:request-handler-advice-chain>
<bean class="org.springframework.integration.mail.config.MailOutboundChannelAdapterParserTests$FooAdvice" />
</int-mail:request-handler-advice-chain>
</int-mail:outbound-channel-adapter>
</beans>

View File

@@ -63,9 +63,10 @@
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="gatewayType">
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="2">
<xsd:element ref="integration:poller" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="request-channel" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>

View File

@@ -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<String>("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();
}
}
}

View File

@@ -18,6 +18,17 @@
remote-channel="testChannel"
host="localhost"/>
<channel id="advisedChannel"/>
<rmi:outbound-gateway id="advised"
request-channel="advisedChannel"
remote-channel="testChannel"
host="localhost">
<rmi:request-handler-advice-chain>
<beans:bean class="org.springframework.integration.rmi.config.RmiOutboundGatewayParserTests$FooAdvice" />
</rmi:request-handler-advice-chain>
</rmi:outbound-gateway>
<chain input-channel="rmiOutboundGatewayInsideChain">
<rmi:outbound-gateway remote-channel="testChannel" host="localhost"/>
</chain>

View File

@@ -21,7 +21,9 @@
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="base-sftp-adapter-type">
<xsd:all>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:all>
<xsd:attribute name="remote-directory-expression"
type="xsd:string">
<xsd:annotation>
@@ -235,7 +237,9 @@
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="base-adapter-type">
<xsd:all>
<xsd:element name="request-handler-advice-chain" type="integration:adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:all>
<xsd:attribute name="command" use="required" type="xsd:string">
<xsd:annotation>
<xsd:documentation>

View File

@@ -50,6 +50,17 @@
charset="UTF-8"
use-temporary-file-name="false"
remote-directory="foo/bar"/>
<int-sftp:outbound-channel-adapter id="advised"
session-factory="sftpSessionFactory"
channel="inputChannel"
charset="UTF-8"
use-temporary-file-name="false"
remote-directory="foo/bar">
<int-sftp:request-handler-advice-chain>
<bean class="org.springframework.integration.sftp.config.OutboundChannelAdapterParserTests$FooAdvice" />
</int-sftp:request-handler-advice-chain>
</int-sftp:outbound-channel-adapter>
<bean id="fileNameGenerator" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.file.FileNameGenerator"/>

Some files were not shown because too many files have changed in this diff Show More