INT-2275: any outbound-channel-adapter in <chain>

Add re-init logic for nested chains
Add logic about nested element for AbstractChannelAdapterParser
Refactor of DefaultOutboundChannelAdapterParser
Test for non-last nested chain with some outbound-channel-adapter
Improve XSD for chain-type
Manual outbound-channel-adapter ability for chain
Integration tests for all outbound-channel-adapter within <chain>
Remove redundant 'return-value-required' attribute from <stored-proc-outbound-channel-adapter>
Add support 'expectReply' for FileWritingMessageHandler

INT-2275 polishing & refactor FileOutbound*Parser

HttpRequestExecutingMessageHandlerTests polishing

INT-2275: polishing JavaDoc
This commit is contained in:
Artem Bilan
2012-01-03 23:26:16 +02:00
committed by Oleg Zhurakousky
parent 4d5b8d5be1
commit 45c429ee2b
58 changed files with 1302 additions and 318 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 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.
@@ -25,18 +25,28 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.util.StringUtils;
/**
* Base parser for Channel Adapters.
*
*
* Includes logic to determine {@link org.springframework.integration.MessageChannel}:
* if 'channel' attribute is defined - uses its value as 'channelName';
* if 'id' attribute is defined - creates {@link DirectChannel} at runtime and uses id's value as 'channelName';
* if current component is defined as nested element inside any other components e.g. &lt;chain&gt;
* 'id' and 'channel' attributes will be ignored and this component will not be parsed as
* {@link org.springframework.integration.endpoint.AbstractEndpoint}.
*
* @author Mark Fisher
* @author Artem Bilan
*/
public abstract class AbstractChannelAdapterParser extends AbstractBeanDefinitionParser {
@Override
protected final String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException {
String id = element.getAttribute("id");
protected final String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext)
throws BeanDefinitionStoreException {
String id = element.getAttribute(ID_ATTRIBUTE);
if (!element.hasAttribute("channel")) {
// the created channel will get the 'id', so the adapter's bean name includes a suffix
id = id + ".adapter";
@@ -57,13 +67,15 @@ public abstract class AbstractChannelAdapterParser extends AbstractBeanDefinitio
}
private String createDirectChannel(Element element, ParserContext parserContext) {
String channelId = element.getAttribute("id");
if (parserContext.isNested()) {
return null;
}
String channelId = element.getAttribute(ID_ATTRIBUTE);
if (!StringUtils.hasText(channelId)) {
parserContext.getReaderContext().error("The channel-adapter's 'id' attribute is required when no 'channel' "
+ "reference has been provided, because that 'id' would be used for the created channel.", element);
}
BeanDefinitionBuilder channelBuilder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".channel.DirectChannel");
BeanDefinitionBuilder channelBuilder = BeanDefinitionBuilder.genericBeanDefinition(DirectChannel.class);
BeanDefinitionHolder holder = new BeanDefinitionHolder(channelBuilder.getBeanDefinition(), channelId);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry());
return channelId;
@@ -72,7 +84,7 @@ public abstract class AbstractChannelAdapterParser extends AbstractBeanDefinitio
/**
* Subclasses must implement this method to parse the adapter element.
* The name of the MessageChannel bean is provided.
*/
*/
protected abstract AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName);
}

View File

@@ -23,22 +23,31 @@ 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.xml.ParserContext;
import org.springframework.integration.config.ConsumerEndpointFactoryBean;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
* Base class for outbound Channel Adapter parsers.
*
* If this component is defined as the top-level element in the Spring application context it will produce
* 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
*/
public abstract class AbstractOutboundChannelAdapterParser extends AbstractChannelAdapterParser {
@Override
protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) {
if (parserContext.isNested()) {
return this.parseConsumer(element, parserContext);
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ConsumerEndpointFactoryBean.class);
Element pollerElement = DomUtils.getChildElementByTagName(element, "poller");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
IntegrationNamespaceUtils.BASE_PACKAGE + ".config.ConsumerEndpointFactoryBean");
builder.addPropertyReference("handler", this.parseAndRegisterConsumer(element, parserContext));
if (pollerElement != null) {
if (!StringUtils.hasText(channelName)) {
@@ -60,15 +69,13 @@ public abstract class AbstractOutboundChannelAdapterParser extends AbstractChann
protected String parseAndRegisterConsumer(Element element, ParserContext parserContext) {
AbstractBeanDefinition definition = this.parseConsumer(element, parserContext);
if (definition == null) {
parserContext.getReaderContext().error(
"Consumer parsing must return a BeanComponentDefinition.", element);
parserContext.getReaderContext().error("Consumer parsing must return an AbstractBeanDefinition.", element);
}
String order = element.getAttribute("order");
String order = element.getAttribute(IntegrationNamespaceUtils.ORDER);
if (StringUtils.hasText(order)) {
definition.getPropertyValues().addPropertyValue("order", order);
definition.getPropertyValues().addPropertyValue(IntegrationNamespaceUtils.ORDER, order);
}
String beanName = BeanDefinitionReaderUtils.generateBeanName(
definition, parserContext.getRegistry());
String beanName = BeanDefinitionReaderUtils.generateBeanName(definition, parserContext.getRegistry());
parserContext.registerBeanComponent(new BeanComponentDefinition(definition, beanName));
return beanName;
}

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.
@@ -18,16 +18,14 @@ package org.springframework.integration.config.xml;
import org.w3c.dom.Element;
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.RootBeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.ExpressionFactoryBean;
import org.springframework.integration.handler.ExpressionEvaluatingMessageHandler;
import org.springframework.integration.handler.MethodInvokingMessageHandler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -39,7 +37,8 @@ import org.springframework.util.StringUtils;
*/
public class DefaultOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser {
protected String parseAndRegisterConsumer(Element element, ParserContext parserContext) {
@Override
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
BeanComponentDefinition innerConsumerDefinition = IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext);
String consumerRef = element.getAttribute(IntegrationNamespaceUtils.REF_ATTRIBUTE);
@@ -61,42 +60,27 @@ public class DefaultOutboundChannelAdapterParser extends AbstractOutboundChannel
"The 'method' attribute cannot be used with the 'expression' attribute.", element);
}
if (hasMethod | isExpression) {
BeanDefinitionBuilder consumerBuilder = null;
if (hasMethod) {
consumerBuilder = BeanDefinitionBuilder.genericBeanDefinition(MethodInvokingMessageHandler.class);
if (isRef) {
consumerBuilder.addConstructorArgReference(consumerRef);
}
else {
consumerBuilder.addConstructorArgValue(innerConsumerDefinition);
}
consumerBuilder.addConstructorArgValue(methodName);
BeanDefinitionBuilder consumerBuilder = null;
if (isExpression) {
consumerBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExpressionEvaluatingMessageHandler.class);
RootBeanDefinition expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class);
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(consumerExpressionString);
consumerBuilder.addConstructorArgValue(expressionDef);
}
else {
consumerBuilder = BeanDefinitionBuilder.genericBeanDefinition(MethodInvokingMessageHandler.class);
if (isRef) {
consumerBuilder.addConstructorArgReference(consumerRef);
}
else {
consumerBuilder = BeanDefinitionBuilder.genericBeanDefinition(ExpressionEvaluatingMessageHandler.class);
RootBeanDefinition expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class);
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(consumerExpressionString);
consumerBuilder.addConstructorArgValue(expressionDef);
consumerBuilder.addConstructorArgValue(innerConsumerDefinition);
}
consumerBuilder.addPropertyValue("componentType", "outbound-channel-adapter");
String order = element.getAttribute(IntegrationNamespaceUtils.ORDER);
if (StringUtils.hasText(order)) {
consumerBuilder.addPropertyValue(IntegrationNamespaceUtils.ORDER, order);
}
consumerRef = BeanDefinitionReaderUtils.registerWithGeneratedName(consumerBuilder.getBeanDefinition(), parserContext.getRegistry());
consumerBuilder.addConstructorArgValue(hasMethod ? methodName : "handleMessage");
}
else if (isInnerConsumer) {
consumerRef = innerConsumerDefinition.getBeanName();
}
Assert.hasText(consumerRef, "cannot determine consumer for 'outbound-channel-adapter'");
return consumerRef;
}
@Override
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
throw new UnsupportedOperationException();
consumerBuilder.addPropertyValue("componentType", "outbound-channel-adapter");
return consumerBuilder.getBeanDefinition();
}
}

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,11 +16,9 @@
package org.springframework.integration.handler;
import java.util.HashSet;
import java.util.List;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.Ordered;
import org.springframework.integration.Message;
@@ -34,6 +32,9 @@ import org.springframework.integration.support.channel.BeanFactoryChannelResolve
import org.springframework.integration.support.channel.ChannelResolver;
import org.springframework.util.Assert;
import java.util.HashSet;
import java.util.List;
/**
* A composite {@link MessageHandler} implementation that invokes a chain of
* MessageHandler instances in order.
@@ -65,8 +66,9 @@ import org.springframework.util.Assert;
* @author Mark Fisher
* @author Iwein Fuld
* @author Gary Russell
* @author Artem Bilan
*/
public class MessageHandlerChain extends AbstractMessageHandler implements MessageProducer, Ordered {
public class MessageHandlerChain extends AbstractMessageHandler implements MessageProducer {
private volatile List<MessageHandler> handlers;
@@ -79,8 +81,6 @@ public class MessageHandlerChain extends AbstractMessageHandler implements Messa
*/
private volatile Long sendTimeout = null;
private volatile int order = Ordered.LOWEST_PRECEDENCE;
private volatile ChannelResolver channelResolver;
private volatile boolean initialized;
@@ -100,15 +100,6 @@ public class MessageHandlerChain extends AbstractMessageHandler implements Messa
this.sendTimeout = sendTimeout;
}
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return this.order;
}
@Override
public String getComponentType() {
return "chain";
@@ -156,6 +147,13 @@ public class MessageHandlerChain extends AbstractMessageHandler implements Messa
}
};
((MessageProducer) handler).setOutputChannel(nextChannel);
// If this 'handler' is a nested non-last &lt;chain&gt;, it is necessary
// to 'force' re-init it for check its configuration in conjunction with current MessageHandlerChain.
if (handler instanceof MessageHandlerChain) {
new DirectFieldAccessor(handler).setPropertyValue("initialized", false);
((MessageHandlerChain) handler).afterPropertiesSet();
}
}
else if (handler instanceof MessageProducer) {
MessageChannel replyChannel = new ReplyForwardingMessageChannel();

View File

@@ -789,7 +789,7 @@
</xsd:complexType>
</xsd:element>
<xsd:element name="outbound-channel-adapter">
<xsd:element name="outbound-channel-adapter" type="methodInvokingChannelAdapterType">
<xsd:annotation>
<xsd:documentation>
Defines a Channel Adapter that receives from a MessageChannel and passes to
@@ -797,89 +797,75 @@
MessageHandler.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="methodInvokingChannelAdapterType">
<xsd:attribute name="order">
<xsd:annotation>
<xsd:documentation>
Specifies the order for invocation when this endpoint is connected as a
subscriber to a channel. This is particularly relevant when that channel
is using a "failover" dispatching strategy. It has no effect when this
endpoint itself is a Polling Consumer for a channel with a queue.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="logging-channel-adapter">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="channelAdapterType">
<xsd:attribute name="level" default="INFO">
<xsd:annotation>
<xsd:documentation><![CDATA[
<xsd:extension base="outboundChannelAdapterType">
<xsd:attributeGroup ref="loggingChannelAdapterAttributes"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="loggingChannelAdapterTypeChain">
<xsd:complexContent>
<xsd:extension base="outboundChannelAdapterTypeChain">
<xsd:attributeGroup ref="loggingChannelAdapterAttributes"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:attributeGroup name="loggingChannelAdapterAttributes">
<xsd:attribute name="level" default="INFO">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specify the log level.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="FATAL" />
<xsd:enumeration value="ERROR" />
<xsd:enumeration value="WARN" />
<xsd:enumeration value="INFO" />
<xsd:enumeration value="DEBUG" />
<xsd:enumeration value="TRACE" />
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="logger-name" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Provide a name for the logger. This is useful when there are multiple logging Channel Adapters configured,
and you would like to differentiate them within the actual log. By default the logger name will be the
fully qualified class name of the LoggingHandler implementation.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expression">
<xsd:annotation>
<xsd:documentation><![CDATA[
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="FATAL" />
<xsd:enumeration value="ERROR" />
<xsd:enumeration value="WARN" />
<xsd:enumeration value="INFO" />
<xsd:enumeration value="DEBUG" />
<xsd:enumeration value="TRACE" />
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="logger-name" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Provide a name for the logger. This is useful when there are multiple logging Channel Adapters configured,
and you would like to differentiate them within the actual log. By default the logger name will be the
fully qualified class name of the LoggingHandler implementation.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="expression">
<xsd:annotation>
<xsd:documentation><![CDATA[
Provide a SpEL expression to be evaluated against the Message as the root object. For example,
the default behavior is equivalent to an expression of "payload", or an expression may evaluate
against the payload itself ("payload.address.city") or headers ("headers.foo"). This attribute and
the 'log-full-message' attribute are mutually exclusive. See the documentation on the
'log-full-message' attribute for more information.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="log-full-message">
<xsd:annotation>
<xsd:documentation><![CDATA[
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="log-full-message">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specify whether to log the full message. This attribute and the 'expression' attribute are
mutually exclusive. Setting this to true is equivalent to setting an expression value of "#root"
since the Message is the root object against which the expression will be evaluated. If no
'expression' is provided, and this value is false (the default), only the payload will be logged.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order">
<xsd:annotation>
<xsd:documentation>
Specifies the order for invocation when this endpoint is connected as a
subscriber to a channel. This is particularly relevant when that channel
is using a "failover" dispatching strategy. It has no effect when this
endpoint itself is a Polling Consumer for a channel with a queue.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:attributeGroup name="methodInvokingOrExpressionEvaluatingAttributes">
<xsd:attribute name="ref" type="xsd:string">
@@ -945,18 +931,42 @@ endpoint itself is a Polling Consumer for a channel with a queue.
<xsd:complexType name="methodInvokingChannelAdapterType">
<xsd:complexContent>
<xsd:extension base="channelAdapterType">
<xsd:extension base="outboundChannelAdapterType">
<xsd:attributeGroup ref="methodInvokingOrExpressionEvaluatingAttributes" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="channelAdapterType">
<xsd:complexType name="methodInvokingChannelAdapterTypeChain">
<xsd:complexContent>
<xsd:extension base="outboundChannelAdapterTypeChain">
<xsd:attributeGroup ref="methodInvokingOrExpressionEvaluatingAttributes" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="outboundChannelAdapterType">
<xsd:all>
<xsd:element name="poller" type="basePollerType" minOccurs="0" maxOccurs="1" />
<xsd:element ref="beans:bean" minOccurs="0" maxOccurs="1" />
</xsd:all>
<xsd:attributeGroup ref="channelAdapterAttributes" />
<xsd:attribute name="order">
<xsd:annotation>
<xsd:documentation>
Specifies the order for invocation when this endpoint is connected as a
subscriber to a channel. This is particularly relevant when that channel
is using a "failover" dispatching strategy. It has no effect when this
endpoint itself is a Polling Consumer for a channel with a queue.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="outboundChannelAdapterTypeChain">
<xsd:all>
<xsd:element ref="beans:bean" minOccurs="0" maxOccurs="1" />
</xsd:all>
</xsd:complexType>
<xsd:element name="service-activator">
@@ -1369,38 +1379,44 @@ endpoint itself is a Polling Consumer for a channel with a queue.
</xsd:element>
<xsd:complexType name="chain-type">
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:any processContents="strict" namespace="##other" minOccurs="0" maxOccurs="unbounded" />
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:any processContents="strict" namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="service-activator" type="expressionOrInnerEndpointDefinitionAware" />
<xsd:element name="splitter" type="splitter-type" />
<xsd:element name="transformer" type="expressionOrInnerEndpointDefinitionAware" />
<xsd:element name="header-enricher" type="header-enricher-type" />
<xsd:element name="header-filter" type="header-filter-type" />
<xsd:element name="enricher" type="enricher-type" />
<xsd:element name="filter" type="filter-type" />
<xsd:element name="aggregator" type="aggregator-type" />
<xsd:element name="resequencer" type="resequencer-type" />
<xsd:element name="router" type="routerTypeChain" />
<xsd:element name="payload-type-router" type="payloadTypeRouterTypeChain" />
<xsd:element name="recipient-list-router" type="recipientListRouterTypeChain" />
<xsd:element name="exception-type-router" type="exceptionTypeRouterTypeChain" />
<xsd:element name="header-value-router" type="headerValueRouterTypeChain" />
<xsd:element name="delayer" type="delayer-type" />
<xsd:element name="gateway" type="innerGatewayType" />
<xsd:element name="poller" type="basePollerType" />
<xsd:element name="payload-serializing-transformer" type="payload-serializing-transformer-type" />
<xsd:element name="payload-deserializing-transformer" type="payload-deserializing-transformer-type" />
<xsd:element name="object-to-string-transformer" type="specialized-transformer-type" />
<xsd:element name="object-to-map-transformer" type="specialized-transformer-type" />
<xsd:element name="map-to-object-transformer" type="map-to-object-transformer-type" />
<xsd:element name="object-to-json-transformer" type="object-to-json-transformer-type" />
<xsd:element name="json-to-object-transformer" type="json-to-object-transformer-type" />
<xsd:element name="claim-check-in" type="claimCheckInTypeChain" />
<xsd:element name="claim-check-out" type="claimCheckOutTypeChain" />
<xsd:element name="control-bus" type="control-bus-type" />
<xsd:element name="chain" type="chain-type" />
</xsd:choice>
<xsd:element name="service-activator" type="expressionOrInnerEndpointDefinitionAware"/>
<xsd:element name="splitter" type="splitter-type"/>
<xsd:element name="transformer" type="expressionOrInnerEndpointDefinitionAware"/>
<xsd:element name="header-enricher" type="header-enricher-type"/>
<xsd:element name="header-filter" type="header-filter-type"/>
<xsd:element name="enricher" type="enricher-type"/>
<xsd:element name="filter" type="filter-type"/>
<xsd:element name="aggregator" type="aggregator-type"/>
<xsd:element name="resequencer" type="resequencer-type"/>
<xsd:element name="router" type="routerTypeChain"/>
<xsd:element name="payload-type-router" type="payloadTypeRouterTypeChain"/>
<xsd:element name="recipient-list-router" type="recipientListRouterTypeChain"/>
<xsd:element name="exception-type-router" type="exceptionTypeRouterTypeChain"/>
<xsd:element name="header-value-router" type="headerValueRouterTypeChain"/>
<xsd:element name="delayer" type="delayer-type"/>
<xsd:element name="gateway" type="innerGatewayType"/>
<xsd:element name="poller" type="basePollerType"/>
<xsd:element name="payload-serializing-transformer" type="payload-serializing-transformer-type"/>
<xsd:element name="payload-deserializing-transformer" type="payload-deserializing-transformer-type"/>
<xsd:element name="object-to-string-transformer" type="specialized-transformer-type"/>
<xsd:element name="object-to-map-transformer" type="specialized-transformer-type"/>
<xsd:element name="map-to-object-transformer" type="map-to-object-transformer-type"/>
<xsd:element name="object-to-json-transformer" type="object-to-json-transformer-type"/>
<xsd:element name="json-to-object-transformer" type="json-to-object-transformer-type"/>
<xsd:element name="claim-check-in" type="claimCheckInTypeChain"/>
<xsd:element name="claim-check-out" type="claimCheckOutTypeChain"/>
<xsd:element name="control-bus" type="control-bus-type"/>
<xsd:element name="chain" type="chain-type"/>
</xsd:choice>
<xsd:choice minOccurs="0">
<xsd:element name="outbound-channel-adapter" type="methodInvokingChannelAdapterTypeChain"/>
<xsd:element name="logging-channel-adapter" type="loggingChannelAdapterTypeChain"/>
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="poller">

View File

@@ -96,6 +96,17 @@
<service-activator ref="testHandler" />
</chain>
<chain input-channel="outboundChannelAdapterChannel">
<outbound-channel-adapter ref="testConsumer"/>
</chain>
<chain input-channel="loggingChannelAdapterChannel">
<transformer expression="payload.toUpperCase()"/>
<logging-channel-adapter level="WARN"/>
</chain>
<beans:bean id="testConsumer" class="org.springframework.integration.config.TestConsumer" />
<channel id="strings">
<queue/>
</channel>

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,22 +16,17 @@
package org.springframework.integration.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import java.util.List;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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;
@@ -43,6 +38,15 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.List;
import static org.junit.Assert.*;
import static org.junit.matchers.JUnitMatchers.both;
import static org.junit.matchers.JUnitMatchers.containsString;
/**
* @author Mark Fisher
* @author Iwein Fuld
@@ -94,9 +98,19 @@ public class ChainParserTests {
@Autowired
private MessageChannel headerValueRouterWithMappingInput;
@Autowired
private MessageChannel loggingChannelAdapterChannel;
@Autowired
private MessageChannel outboundChannelAdapterChannel;
@Autowired
private TestConsumer testConsumer;
@Autowired
@Qualifier("claimCheckInput")
private MessageChannel claimCheckInput;
@Autowired
@Qualifier("claimCheckOutput")
private PollableChannel claimCheckOutput;
@@ -241,6 +255,40 @@ public class ChainParserTests {
assertEquals(message.getPayload(), reply.getPayload());
}
@Test //INT-2275
public void chainWithOutboundChannelAdapter() {
this.outboundChannelAdapterChannel.send(successMessage);
assertSame(successMessage, testConsumer.getLastMessage());
}
@Test //INT-2275
public void chainWithLoggingChannelAdapter() {
Message<?> message = MessageBuilder.withPayload("test").build();
PrintStream realOut = System.out;
try {
OutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
this.loggingChannelAdapterChannel.send(message);
assertEquals("LoggingHandler: TEST", out.toString().trim());
}
finally {
System.setOut(realOut);
}
}
@Test(expected = BeanCreationException.class) //INT-2275
public void invalidNestedChainWithLoggingChannelAdapter() {
try {
new ClassPathXmlApplicationContext("invalidNestedChainWithOutboundChannelAdapter-context.xml", this.getClass());
fail("BeanCreationException is expected!");
}
catch (BeansException e) {
assertEquals(IllegalArgumentException.class, e.getCause().getClass());
assertThat(e.getMessage(), both(containsString("output channel was provided")).and(containsString("does not implement the MessageProducer")));
throw e;
}
}
public static class StubHandler extends AbstractReplyProducingMessageHandler {
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
<chain input-channel="invalidNestedChain">
<chain>
<logging-channel-adapter/>
</chain>
<logging-channel-adapter/>
</chain>
</beans:beans>

View File

@@ -13,16 +13,22 @@
<queue capacity="2"/>
</channel>
<channel id="channelC"/>
<outbound-channel-adapter id="adapter" channel="channel" ref="bean" method="out" order="99" auto-startup="false"/>
<beans:bean id="bean"
class="org.springframework.integration.config.xml.MethodInvokingOutboundChannelAdapterParserTests$TestBean"/>
class="org.springframework.integration.config.xml.DefaultOutboundChannelAdapterParserTests$TestBean"/>
<outbound-channel-adapter id="adapterB" channel="channelB" method="out" order="99" auto-startup="false">
<beans:bean class="org.springframework.integration.config.xml.MethodInvokingOutboundChannelAdapterParserTests$TestBean"/>
<beans:bean class="org.springframework.integration.config.xml.DefaultOutboundChannelAdapterParserTests$TestBean"/>
<poller task-executor="executor" max-messages-per-poll="5" fixed-delay="20" />
</outbound-channel-adapter>
<task:executor id="executor" pool-size="5" />
<outbound-channel-adapter id="adapterC" channel="channelC" order="99">
<beans:bean class="org.springframework.integration.config.TestConsumer"/>
</outbound-channel-adapter>
</beans:beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 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.
@@ -25,6 +25,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.integration.config.TestConsumer;
import org.springframework.integration.handler.MethodInvokingMessageHandler;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.test.context.ContextConfiguration;
@@ -32,10 +33,11 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Mark Fisher
* @author Artem Bilan
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class MethodInvokingOutboundChannelAdapterParserTests {
public class DefaultOutboundChannelAdapterParserTests {
@Autowired
private ApplicationContext context;
@@ -44,21 +46,29 @@ public class MethodInvokingOutboundChannelAdapterParserTests {
@Test
public void checkConfig() {
Object adapter = context.getBean("adapter");
assertEquals(Boolean.FALSE, TestUtils.getPropertyValue(adapter, "autoStartup"));
Object handler = TestUtils.getPropertyValue(adapter, "handler");
assertEquals(MethodInvokingMessageHandler.class, handler.getClass());
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
assertEquals(99, handlerAccessor.getPropertyValue("order"));
assertEquals(Boolean.FALSE, TestUtils.getPropertyValue(adapter, "autoStartup"));
assertEquals(99, TestUtils.getPropertyValue(handler, "order"));
}
@Test
public void checkConfigWithInnerBeanAndPoller() {
Object adapter = context.getBean("adapterB");
assertEquals(Boolean.FALSE, TestUtils.getPropertyValue(adapter, "autoStartup"));
Object handler = TestUtils.getPropertyValue(adapter, "handler");
assertEquals(MethodInvokingMessageHandler.class, handler.getClass());
DirectFieldAccessor handlerAccessor = new DirectFieldAccessor(handler);
assertEquals(99, handlerAccessor.getPropertyValue("order"));
assertEquals(Boolean.FALSE, TestUtils.getPropertyValue(adapter, "autoStartup"));
assertEquals(99, TestUtils.getPropertyValue(handler, "order"));
}
@Test
public void checkConfigWithInnerMessageHandler() {
Object adapter = context.getBean("adapterC");
Object handler = TestUtils.getPropertyValue(adapter, "handler");
assertEquals(MethodInvokingMessageHandler.class, handler.getClass());
assertEquals(99, TestUtils.getPropertyValue(handler, "order"));
Object targetObject = TestUtils.getPropertyValue(handler, "processor.delegate.targetObject");
assertEquals(TestConsumer.class, targetObject.getClass());
}
@@ -68,4 +78,5 @@ public class MethodInvokingOutboundChannelAdapterParserTests {
}
}
}