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

@@ -22,5 +22,10 @@
mapped-request-headers="foo*"/>
<int:channel id="requestChannel"/>
<int:chain id="chainWithRabbitOutbound" input-channel="amqpOutboundChannelAdapterWithinChain">
<amqp:outbound-channel-adapter exchange-name="outboundchanneladapter.test.1"/>
</int:chain>
</beans>

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.
@@ -17,6 +17,7 @@
package org.springframework.integration.amqp.config;
import java.lang.reflect.Field;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,6 +34,7 @@ import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.handler.MessageHandlerChain;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.test.context.ContextConfiguration;
@@ -47,6 +49,7 @@ import static junit.framework.Assert.assertEquals;
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 2.1
*/
@ContextConfiguration
@@ -99,4 +102,35 @@ public class AmqpOutboundChannelAdapterParserTests {
Mockito.verify(amqpTemplate, Mockito.times(1)).send(Mockito.any(String.class), Mockito.any(String.class), Mockito.any(org.springframework.amqp.core.Message.class));
}
@SuppressWarnings("rawtypes")
@Test
public void amqpOutboundChannelAdapterWithinChain() {
Object eventDrivernConsumer = context.getBean("chainWithRabbitOutbound");
List chainHandlers = TestUtils.getPropertyValue(eventDrivernConsumer, "handler.handlers", List.class);
AmqpOutboundEndpoint endpoint = (AmqpOutboundEndpoint) chainHandlers.get(0);
Field amqpTemplateField = ReflectionUtils.findField(AmqpOutboundEndpoint.class, "amqpTemplate");
amqpTemplateField.setAccessible(true);
RabbitTemplate amqpTemplate = TestUtils.getPropertyValue(endpoint, "amqpTemplate", RabbitTemplate.class);
amqpTemplate = Mockito.spy(amqpTemplate);
Mockito.doAnswer(new Answer() {
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
org.springframework.amqp.core.Message amqpReplyMessage = (org.springframework.amqp.core.Message) args[2];
assertEquals("hello", new String(amqpReplyMessage.getBody()));
return null;
}})
.when(amqpTemplate).send(Mockito.any(String.class), Mockito.any(String.class), Mockito.any(org.springframework.amqp.core.Message.class));
ReflectionUtils.setField(amqpTemplateField, endpoint, amqpTemplate);
MessageChannel requestChannel = context.getBean("amqpOutboundChannelAdapterWithinChain", MessageChannel.class);
Message<?> message = MessageBuilder.withPayload("hello").build();
requestChannel.send(message);
Mockito.verify(amqpTemplate, Mockito.times(1)).send(Mockito.any(String.class), Mockito.any(String.class), Mockito.any(org.springframework.amqp.core.Message.class));
}
}

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 {
}
}
}

View File

@@ -8,7 +8,12 @@
http://www.springframework.org/schema/integration/event http://www.springframework.org/schema/integration/event/spring-integration-event.xsd">
<int:channel id="input"/>
<int-event:outbound-channel-adapter id="eventAdapter" channel="input"/>
<int:chain input-channel="inputChain">
<int:transformer expression="payload + 'bar'"/>
<int-event:outbound-channel-adapter/>
</int:chain>
</beans>

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,14 +16,9 @@
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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
@@ -40,8 +35,12 @@ 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
* @since 2.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@@ -83,6 +82,25 @@ public class EventOutboundChannelAdapterParserTests {
Assert.assertTrue(receivedEvent);
}
@Test //INT-2275
public void testInsideChain() {
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("foobar")) {
receivedEvent = true;
}
}
}
};
context.addApplicationListener(listener);
DirectChannel channel = context.getBean("inputChain", DirectChannel.class);
channel.send(new GenericMessage<String>("foo"));
Assert.assertTrue(receivedEvent);
}
@Test(timeout=2000)
public void validateUsageWithPollableChannel() throws Exception {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("EventOutboundChannelAdapterParserTestsWithPollable-context.xml", EventOutboundChannelAdapterParserTests.class);

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.
@@ -54,6 +54,7 @@ import java.nio.charset.Charset;
* @author Iwein Fuld
* @author Alex Peters
* @author Oleg Zhurakousky
* @author Artem Bilan
*/
public class FileWritingMessageHandler extends AbstractReplyProducingMessageHandler {
@@ -71,6 +72,7 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
private volatile Charset charset = Charset.defaultCharset();
private volatile boolean expectReply = true;
public FileWritingMessageHandler(File destinationDirectory) {
Assert.notNull(destinationDirectory, "Destination directory must not be null.");
@@ -92,7 +94,15 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
public void setTemporaryFileSuffix(String temporaryFileSuffix) {
this.temporaryFileSuffix = temporaryFileSuffix;
}
/**
* Specify whether a reply Message is expected. If not, this handler will simply return null for a
* successful response or throw an Exception for a non-successful response. The default is true.
*/
public void setExpectReply(boolean expectReply) {
this.expectReply = expectReply;
}
protected String getTemporaryFileSuffix() {
return temporaryFileSuffix;
}
@@ -169,6 +179,11 @@ public class FileWritingMessageHandler extends AbstractReplyProducingMessageHand
catch (Exception e) {
throw new MessageHandlingException(requestMessage, "failed to write Message payload to file", e);
}
if (!this.expectReply) {
return null;
}
if (resultFile != null) {
if (originalFileFromHeader == null && payload instanceof File) {
return MessageBuilder.withPayload(resultFile)

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.
@@ -22,8 +22,6 @@ 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.context.IntegrationContextUtils;
import org.springframework.util.StringUtils;
/**
* Parser for the &lt;outbound-channel-adapter/&gt; element of the 'file'
@@ -32,36 +30,14 @@ import org.springframework.util.StringUtils;
* @author Mark Fisher
* @author Iwein Fuld
* @author Oleg Zhurakousky
* @author Artem Bilan
*/
public class FileOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser {
@Override
protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) {
BeanDefinitionBuilder handlerBuilder = FileWritingMessageHandlerBeanDefinitionBuilder.configure(
element, IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME, parserContext);
if (handlerBuilder != null){
String remoteFileNameGenerator = element.getAttribute("filename-generator");
String remoteFileNameGeneratorExpression = element.getAttribute("filename-generator-expression");
boolean hasRemoteFileNameGenerator = StringUtils.hasText(remoteFileNameGenerator);
boolean hasRemoteFileNameGeneratorExpression = StringUtils.hasText(remoteFileNameGeneratorExpression);
if (hasRemoteFileNameGenerator || hasRemoteFileNameGeneratorExpression) {
if (hasRemoteFileNameGenerator && hasRemoteFileNameGeneratorExpression) {
parserContext.getReaderContext().error("at most one of 'filename-generator-expression' or 'filename-generator' " +
"is allowed on file outbound adapter/gateway", element);
}
if (hasRemoteFileNameGenerator) {
handlerBuilder.addPropertyReference("fileNameGenerator", remoteFileNameGenerator);
}
else {
BeanDefinitionBuilder fileNameGeneratorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.file.DefaultFileNameGenerator");
fileNameGeneratorBuilder.addPropertyValue("expression", remoteFileNameGeneratorExpression);
handlerBuilder.addPropertyValue("fileNameGenerator", fileNameGeneratorBuilder.getBeanDefinition());
}
}
}
return (handlerBuilder != null ? handlerBuilder.getBeanDefinition() : null);
BeanDefinitionBuilder handlerBuilder = FileWritingMessageHandlerBeanDefinitionBuilder.configure(element, false, parserContext);
return handlerBuilder.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,18 +16,19 @@
package org.springframework.integration.file.config;
import org.springframework.integration.config.xml.IntegrationNamespaceUtils;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.xml.AbstractConsumerEndpointParser;
import org.springframework.util.StringUtils;
/**
* Parser for the 'outbound-gateway' element of the file namespace.
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
* @since 1.0.3
*/
public class FileOutboundGatewayParser extends AbstractConsumerEndpointParser {
@@ -39,31 +40,8 @@ public class FileOutboundGatewayParser extends AbstractConsumerEndpointParser {
@Override
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
String replyChannel = element.getAttribute("reply-channel");
BeanDefinitionBuilder handlerBuilder =
FileWritingMessageHandlerBeanDefinitionBuilder.configure(element, replyChannel, parserContext);
String remoteFileNameGenerator = element.getAttribute("filename-generator");
String remoteFileNameGeneratorExpression = element.getAttribute("filename-generator-expression");
boolean hasRemoteFileNameGenerator = StringUtils.hasText(remoteFileNameGenerator);
boolean hasRemoteFileNameGeneratorExpression = StringUtils.hasText(remoteFileNameGeneratorExpression);
if (hasRemoteFileNameGenerator || hasRemoteFileNameGeneratorExpression) {
if (hasRemoteFileNameGenerator && hasRemoteFileNameGeneratorExpression) {
parserContext.getReaderContext().error("at most one of 'filename-generator-expression' or 'filename-generator' " +
"is allowed on file outbound adapter/gateway", element) ;
}
if (hasRemoteFileNameGenerator) {
handlerBuilder.addPropertyReference("fileNameGenerator", remoteFileNameGenerator);
}
else {
BeanDefinitionBuilder fileNameGeneratorBuilder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.file.DefaultFileNameGenerator");
fileNameGeneratorBuilder.addPropertyValue("expression", remoteFileNameGeneratorExpression);
handlerBuilder.addPropertyValue("fileNameGenerator", fileNameGeneratorBuilder.getBeanDefinition());
}
}
BeanDefinitionBuilder handlerBuilder = FileWritingMessageHandlerBeanDefinitionBuilder.configure(element, true, parserContext);
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(handlerBuilder, element, "reply-channel", "outputChannel");
return handlerBuilder;
}

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,6 +16,7 @@
package org.springframework.integration.file.config;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -29,32 +30,42 @@ import org.springframework.util.StringUtils;
* {@link org.springframework.integration.file.FileWritingMessageHandler}.
*
* @author Mark Fisher
* @author Artem Bilan
* @since 1.0.3
*/
abstract class FileWritingMessageHandlerBeanDefinitionBuilder {
static BeanDefinitionBuilder configure(Element element, String outputChannelBeanName, ParserContext parserContext) {
if (outputChannelBeanName == null) {
parserContext.getReaderContext().error("outputChannelBeanName must not be null", element);
return null;
}
static BeanDefinitionBuilder configure(Element element, boolean expectReply, ParserContext parserContext) {
String directory = element.getAttribute("directory");
if (!StringUtils.hasText(directory)) {
parserContext.getReaderContext().error("directory is required", element);
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition("org.springframework.integration.file.config.FileWritingMessageHandlerFactoryBean");
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FileWritingMessageHandlerFactoryBean.class);
builder.addPropertyValue("directory", directory);
if (StringUtils.hasText(outputChannelBeanName)) {
builder.addPropertyReference("outputChannel", outputChannelBeanName);
}
builder.addPropertyValue("expectReply", expectReply);
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "auto-create-directory");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "delete-source-files");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "temporary-file-suffix");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "charset");
String fileNameGenerator = element.getAttribute("filename-generator");
if (StringUtils.hasText(fileNameGenerator)) {
builder.addPropertyReference("fileNameGenerator", fileNameGenerator);
String remoteFileNameGenerator = element.getAttribute("filename-generator");
String remoteFileNameGeneratorExpression = element.getAttribute("filename-generator-expression");
boolean hasRemoteFileNameGenerator = StringUtils.hasText(remoteFileNameGenerator);
boolean hasRemoteFileNameGeneratorExpression = StringUtils.hasText(remoteFileNameGeneratorExpression);
if (hasRemoteFileNameGenerator || hasRemoteFileNameGeneratorExpression) {
if (hasRemoteFileNameGenerator && hasRemoteFileNameGeneratorExpression) {
parserContext.getReaderContext().error("at most one of 'filename-generator-expression' or 'filename-generator' " +
"is allowed on file outbound adapter/gateway", element);
}
if (hasRemoteFileNameGenerator) {
builder.addPropertyReference("fileNameGenerator", remoteFileNameGenerator);
}
else {
BeanDefinitionBuilder fileNameGeneratorBuilder = BeanDefinitionBuilder
.genericBeanDefinition(DefaultFileNameGenerator.class);
fileNameGeneratorBuilder.addPropertyValue("expression", remoteFileNameGeneratorExpression);
builder.addPropertyValue("fileNameGenerator", fileNameGeneratorBuilder.getBeanDefinition());
}
}
return builder;
}

View File

@@ -29,6 +29,7 @@ import org.springframework.integration.file.FileWritingMessageHandler;
* @author Iwein Fuld
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 1.0.3
*/
public class FileWritingMessageHandlerFactoryBean extends AbstractSimpleMessageHandlerFactoryBean<FileWritingMessageHandler>{
@@ -48,7 +49,9 @@ public class FileWritingMessageHandlerFactoryBean extends AbstractSimpleMessageH
private volatile Long sendTimeout;
private volatile String temporaryFileSuffix;
private volatile boolean expectReply = true;
public void setDirectory(File directory) {
this.directory = directory;
}
@@ -80,7 +83,11 @@ public class FileWritingMessageHandlerFactoryBean extends AbstractSimpleMessageH
public void setTemporaryFileSuffix(String temporaryFileSuffix) {
this.temporaryFileSuffix = temporaryFileSuffix;
}
public void setExpectReply(boolean expectReply) {
this.expectReply = expectReply;
}
@Override
protected FileWritingMessageHandler createHandler() {
FileWritingMessageHandler handler = new FileWritingMessageHandler(this.directory);
@@ -105,6 +112,7 @@ public class FileWritingMessageHandlerFactoryBean extends AbstractSimpleMessageH
if (this.temporaryFileSuffix != null) {
handler.setTemporaryFileSuffix(this.temporaryFileSuffix);
}
handler.setExpectReply(this.expectReply);
return handler;
}
}

View File

@@ -0,0 +1,30 @@
<?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:context="http://www.springframework.org/schema/context"
xmlns:si="http://www.springframework.org/schema/integration"
xmlns:file="http://www.springframework.org/schema/integration/file"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/file
http://www.springframework.org/schema/integration/file/spring-integration-file.xsd">
<si:chain input-channel="outboundChainChannel">
<si:header-enricher>
<si:header name="#{T(org.springframework.integration.file.FileHeaders).FILENAME}" value="${test.file}"/>
</si:header-enricher>
<file:outbound-channel-adapter directory="${work.dir}"/>
</si:chain>
<bean id="placeholderProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="properties" value="#{T(org.springframework.integration.file.FileOutboundChannelAdapterInsideChainTests).placeholderProperties}"/>
</bean>
<context:property-placeholder properties-ref="placeholderProperties"/>
</beans>

View File

@@ -0,0 +1,92 @@
/*
* 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.file;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
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.MessageChannel;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.FileCopyUtils;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import static org.junit.Assert.*;
/**
* //INT-2275
*
* @author Artem Bilan
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class FileOutboundChannelAdapterInsideChainTests {
public static final String TEST_FILE_NAME = FileOutboundChannelAdapterInsideChainTests.class.getSimpleName();
public static final String WORK_DIR_NAME = System.getProperty("java.io.tmpdir") + "/" + FileOutboundChannelAdapterInsideChainTests.class.getSimpleName() + "Dir";
public static final String SAMPLE_CONTENT = "test";
public static Properties placeholderProperties = new Properties();
static {
placeholderProperties.put("test.file", TEST_FILE_NAME);
placeholderProperties.put("work.dir", WORK_DIR_NAME);
}
@Autowired
private MessageChannel outboundChainChannel;
private static File workDir;
@BeforeClass
public static void setupClass() {
workDir = new File(WORK_DIR_NAME);
workDir.mkdir();
workDir.deleteOnExit();
}
@AfterClass
public static void cleanUp() {
if (workDir != null && workDir.exists()) {
for (File file : workDir.listFiles()) {
file.delete();
}
}
workDir.delete();
}
@Test //INT-2275
public void testFileOutboundChannelAdapterWithinChain() throws IOException {
Message<String> message = MessageBuilder.withPayload(SAMPLE_CONTENT).build();
outboundChainChannel.send(message);
File testFile = new File(workDir, TEST_FILE_NAME);
assertTrue(testFile.exists());
byte[] testFileContent = FileCopyUtils.copyToByteArray(testFile);
assertEquals(new String(testFileContent), SAMPLE_CONTENT);
}
}

View File

@@ -0,0 +1,30 @@
<?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:context="http://www.springframework.org/schema/context"
xmlns:si="http://www.springframework.org/schema/integration"
xmlns:ftp="http://www.springframework.org/schema/integration/ftp"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/ftp
http://www.springframework.org/schema/integration/ftp/spring-integration-ftp.xsd">
<si:chain input-channel="outboundChainChannel">
<ftp:outbound-channel-adapter
auto-create-directory="true"
session-factory="ftpSessionFactory"
remote-directory="remote-target-dir"/>
</si:chain>
<bean id="ftpSessionFactory"
class="org.springframework.integration.ftp.outbound.FtpSendingMessageHandlerTests$TestFtpSessionFactory">
<property name="username" value="kermit"/>
<property name="password" value="frog"/>
<property name="host" value="foo.com"/>
</bean>
</beans>

View File

@@ -32,8 +32,11 @@ import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
import org.springframework.integration.ftp.session.AbstractFtpSessionFactory;
@@ -42,6 +45,7 @@ import org.springframework.util.FileCopyUtils;
/**
* @author Oleg Zhurakousky
* @author Artem Bilan
*/
public class FtpSendingMessageHandlerTests {
@@ -120,6 +124,25 @@ public class FtpSendingMessageHandlerTests {
assertTrue("destination file was not created", destFile.exists());
}
@Test //INT-2275
public void testFtpOutboundChannelAdapterInsideChain() throws Exception {
File targetDir = new File("remote-target-dir");
assertTrue("target directory does not exist: " + targetDir.getName(), targetDir.exists());
File srcFile = File.createTempFile("testHandleFileMessage", ".tmp");
srcFile.deleteOnExit();
File destFile = new File(targetDir, srcFile.getName());
destFile.deleteOnExit();
ApplicationContext context = new ClassPathXmlApplicationContext("FtpOutboundChannelAdapterInsideChainTests-context.xml", getClass());
MessageChannel channel = context.getBean("outboundChainChannel", MessageChannel.class);
channel.send(new GenericMessage<File>(srcFile));
assertTrue("destination file was not created", destFile.exists());
}
public static class TestFtpSessionFactory extends AbstractFtpSessionFactory<FTPClient> {

View File

@@ -22,5 +22,9 @@
<entry key="'foo'" value="'bar'"/>
</int-gfe:cache-entries>
</int-gfe:outbound-channel-adapter>
<int:chain input-channel="cacheChainChannel">
<int-gfe:outbound-channel-adapter region="region1"/>
</int:chain>
</beans>

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. You may obtain a copy of the License at
@@ -31,6 +31,7 @@ import com.gemstone.gemfire.internal.cache.DistributedRegion;
/**
* @author David Turanski
* @author Artem Bilan
* @since 2.1
*/
@@ -40,7 +41,7 @@ public class GemfireOutboundChannelAdapterTests {
@Autowired
MessageChannel cacheChannel1;
@Autowired
DistributedRegion region1;
@@ -49,7 +50,11 @@ public class GemfireOutboundChannelAdapterTests {
@Autowired
DistributedRegion region2;
@Autowired
MessageChannel cacheChainChannel;
@Before
public void setUp() {
region1.clear();
@@ -75,4 +80,16 @@ public class GemfireOutboundChannelAdapterTests {
assertEquals("hello",region2.get("HELLO"));
assertEquals("bar",region2.get("foo"));
}
@Test //INT-2275
public void testWriteWithinChain() {
Map<String,String> map = new HashMap<String,String>();
map.put("foo","bar");
Message<?> message = MessageBuilder.withPayload(map).build();
cacheChainChannel.send(message);
assertEquals(1,region1.size());
assertEquals("bar",region1.get("foo"));
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
xmlns="http://www.springframework.org/schema/integration/http"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:si="http://www.springframework.org/schema/integration"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http.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
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<si:chain input-channel="httpOutboundChannelAdapterWithinChain">
<outbound-channel-adapter url="http://localhost/test1" rest-template="restTemplate"/>
</si:chain>
<beans:bean id="restTemplate" class="org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandlerTests$MockRestTemplate2"/>
</beans:beans>

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.
@@ -32,13 +32,16 @@ import javax.xml.transform.Source;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
@@ -47,6 +50,7 @@ import org.springframework.web.client.RestTemplate;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
*/
public class HttpRequestExecutingMessageHandlerTests {
@@ -639,7 +643,15 @@ public class HttpRequestExecutingMessageHandlerTests {
assertNull(request.getHeaders().getContentType());
*/
}
@Test //INT-2275
public void testOutboundChannelAdapterWithinChain() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("HttpOutboundChannelAdapterWithinChainTests-context.xml", this.getClass());
MessageChannel channel = ctx.getBean("httpOutboundChannelAdapterWithinChain", MessageChannel.class);
channel.send(MessageBuilder.withPayload("test").build());
// It's just enough if it was sent successfully from chain without any failures
}
public static class City{
private String name;
public City(String name){
@@ -661,4 +673,15 @@ public class HttpRequestExecutingMessageHandlerTests {
throw new RuntimeException("intentional");
}
}
private static class MockRestTemplate2 extends RestTemplate {
@Override
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity,
Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
return new ResponseEntity<T>(HttpStatus.OK);
}
}
}

View File

@@ -0,0 +1,40 @@
<?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-ip="http://www.springframework.org/schema/integration/ip"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/integration/ip http://www.springframework.org/schema/integration/ip/spring-integration-ip.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">
<bean id="tcpIpUtils" class="org.springframework.integration.ip.util.SocketTestUtils" />
<int-ip:tcp-connection-factory
id="scf"
type="server"
so-timeout="60000"
deserializer=""
port="#{tcpIpUtils.findAvailableServerSocket(7000)}"/>
<int-ip:tcp-inbound-channel-adapter
connection-factory="scf"
channel="inbound"/>
<int:channel id="inbound">
<int:queue/>
</int:channel>
<int-ip:tcp-connection-factory
id="ccf"
type="client"
host="localhost"
port="#{scf.port}"
single-use="true"
so-timeout="60000"/>
<int:chain input-channel="tcpOutboundChannelAdapterWithinChain">
<int-ip:tcp-outbound-channel-adapter connection-factory="ccf"/>
</int:chain>
</beans>

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.
@@ -43,10 +43,15 @@ import javax.net.ServerSocketFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.serializer.DefaultDeserializer;
import org.springframework.core.serializer.DefaultSerializer;
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.ip.IpHeaders;
import org.springframework.integration.ip.tcp.connection.AbstractConnectionFactory;
import org.springframework.integration.ip.tcp.connection.HelloWorldInterceptorFactory;
import org.springframework.integration.ip.tcp.connection.TcpConnectionInterceptorFactory;
@@ -57,10 +62,12 @@ import org.springframework.integration.ip.tcp.serializer.ByteArrayCrLfSerializer
import org.springframework.integration.ip.tcp.serializer.ByteArrayLengthHeaderSerializer;
import org.springframework.integration.ip.tcp.serializer.ByteArrayStxEtxSerializer;
import org.springframework.integration.ip.util.SocketTestUtils;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
/**
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class TcpSendingMessageHandlerTests {
@@ -1055,4 +1062,20 @@ public class TcpSendingMessageHandlerTests {
done.set(true);
}
@Test
public void testOutboundChannelAdapterWithinChain() throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"TcpOutboundChannelAdapterWithinTests-context.xml", this.getClass());
AbstractConnectionFactory ccf = ctx.getBean("ccf", AbstractConnectionFactory.class);
// TODO Lifecycle#start() isn't invoked within chain...
ccf.start();
MessageChannel channelAdapterWithinChain = ctx.getBean("tcpOutboundChannelAdapterWithinChain", MessageChannel.class);
PollableChannel inbound = ctx.getBean("inbound", PollableChannel.class);
String testPayload = "Hello, world!";
channelAdapterWithinChain.send(new GenericMessage<String>(testPayload));
Message<?> m = inbound.receive(1000);
assertNotNull(m);
assertEquals(testPayload, new String((byte[]) m.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.
@@ -26,6 +26,7 @@ import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
@@ -49,6 +50,7 @@ import org.springframework.integration.test.util.TestUtils;
* received in the other context (and written back to the console).
*
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class UdpUnicastEndToEndTests implements Runnable {
@@ -67,7 +69,18 @@ public class UdpUnicastEndToEndTests implements Runnable {
private CountDownLatch readyToReceive = new CountDownLatch(1);
private static long hangAroundFor = 0;
private static long hangAroundFor = 10;
@Before
public void setup() {
this.testingIpText = null;
this.finalMessage = null;
this.sentFirst = new CountDownLatch(1);
this.firstReceived = new CountDownLatch(1);
this.doneProcessing = new CountDownLatch(1);
this.okToRun = true;
this.readyToReceive = new CountDownLatch(1);
}
@Test
public void runIt() throws Exception {
@@ -80,6 +93,17 @@ public class UdpUnicastEndToEndTests implements Runnable {
applicationContext.stop();
}
@Test
public void tesUudpOutboundChannelAdapterWithinChain() throws Exception {
UdpUnicastEndToEndTests launcher = new UdpUnicastEndToEndTests();
Thread t = new Thread(launcher);
t.start(); // launch the receiver
AbstractApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"testIp-out-within-chain-context.xml", UdpUnicastEndToEndTests.class);
launcher.launchSender(applicationContext);
applicationContext.stop();
}
public void launchSender(ApplicationContext applicationContext) throws Exception {
ChannelResolver channelResolver = new BeanFactoryChannelResolver(applicationContext);
@@ -159,6 +183,7 @@ public class UdpUnicastEndToEndTests implements Runnable {
public static void main(String[] args) throws Exception {
hangAroundFor = 120000;
new UdpUnicastEndToEndTests().runIt();
new UdpUnicastEndToEndTests().tesUudpOutboundChannelAdapterWithinChain();
}
}

View File

@@ -0,0 +1,42 @@
<?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"
xmlns:stream="http://www.springframework.org/schema/integration/stream"
xmlns:ip="http://www.springframework.org/schema/integration/ip"
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
http://www.springframework.org/schema/integration/stream
http://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd
http://www.springframework.org/schema/integration/ip
http://www.springframework.org/schema/integration/ip/spring-integration-ip.xsd">
<stream:stdin-channel-adapter id="stdin" channel="outputChannel" >
<poller fixed-delay="100"/>
</stream:stdin-channel-adapter>
<channel id="inputChannel"/>
<channel id="outputChannel" />
<service-activator input-channel="inputChannel"
output-channel="outputChannel"
ref="testIp"
method="testIp"/>
<chain input-channel="outputChannel">
<ip:udp-outbound-channel-adapter host="localhost"
port="11111"
check-length="true"
acknowledge="true"
ack-host="localhost"
ack-port="22223"
ack-timeout="10000"/>
</chain>
<beans:import resource="testIp-common-context.xml" />
</beans:beans>

View File

@@ -705,17 +705,6 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="return-value-required" default="false">
<xsd:annotation>
<xsd:documentation>
Indicates whether this procedure's return value
should be included.
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:union memberTypes="xsd:boolean xsd:string" />
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
@@ -1313,4 +1302,4 @@
<xsd:enumeration value="INOUT"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
</xsd:schema>

View File

@@ -0,0 +1,29 @@
<?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="http://www.springframework.org/schema/integration"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:int-jdbc="http://www.springframework.org/schema/integration/jdbc"
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">
<jdbc:embedded-database id="dataSource" type="DERBY">
<jdbc:script location="classpath:derby-stored-procedures.sql"/>
</jdbc:embedded-database>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource" />
</bean>
<int:chain input-channel="jdbcStoredProcOutboundChannelAdapterWithinChain">
<int-jdbc:stored-proc-outbound-channel-adapter stored-procedure-name="CREATE_USER" data-source="dataSource">
<int-jdbc:parameter name="username" expression="payload.username"/>
<int-jdbc:parameter name="password" expression="payload.password"/>
<int-jdbc:parameter name="email" expression="payload.email"/>
</int-jdbc:stored-proc-outbound-channel-adapter>
</int:chain>
</beans>

View File

@@ -0,0 +1,68 @@
/*
* 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.jdbc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.jdbc.storedproc.User;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Map;
import static org.junit.Assert.assertEquals;
/**
* @author Artem Bilan
* @since 2.2
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class StoredProcOutboundChannelAdapterWithinChainTests {
@Autowired
private AbstractApplicationContext context;
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private MessageChannel jdbcStoredProcOutboundChannelAdapterWithinChain;
@Test
public void test() {
Message<User> message = MessageBuilder.withPayload(new User("username", "password", "email")).build();
this.jdbcStoredProcOutboundChannelAdapterWithinChain.send(message);
Map<String, Object> map = this.jdbcTemplate.queryForMap("SELECT * FROM USERS WHERE USERNAME=?", "username");
assertEquals("Wrong username", "username", map.get("USERNAME"));
assertEquals("Wrong password", "password", map.get("PASSWORD"));
assertEquals("Wrong email", "email", map.get("EMAIL"));
// embeddedDatabase can be in working state. So other tests with the same embeddedDatabase beanId, type and init scripts
// may be failed with Exception like: object in the DB already exists
this.context.destroy();
}
}

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. You may obtain a copy of the License at
@@ -38,6 +38,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*
*/
@@ -115,6 +116,16 @@ public class JdbcMessageHandlerParserTests {
assertEquals("Wrong id", "foo", map.get("name"));
}
@Test
public void testOutboundChannelAdapterWithinChain(){
setUp("handlingJdbcOutboundChannelAdapterWithinChainTest.xml", getClass());
Message<?> message = MessageBuilder.withPayload("foo").setHeader("business.key", "FOO").build();
channel.send(message);
Map<String, Object> map = this.jdbcTemplate.queryForMap("SELECT * from FOOS");
assertEquals("Wrong id", "FOO", map.get("ID"));
assertEquals("Wrong id", "foo", map.get("name"));
}
@After
public void tearDown(){
if(context != null){

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration/jdbc"
xmlns:si="http://www.springframework.org/schema/integration"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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
http://www.springframework.org/schema/integration/jdbc
http://www.springframework.org/schema/integration/jdbc/spring-integration-jdbc.xsd">
<si:chain input-channel="target">
<outbound-channel-adapter
query="insert into foos (id, status, name) values (:headers[business.key], 0, :payload)"
jdbc-operations="jdbcTemplate"/>
</si:chain>
<beans:import resource="jdbcOutboundChannelAdapterCommonConfig.xml" />
</beans:beans>

View File

@@ -0,0 +1,31 @@
<?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="http://www.springframework.org/schema/integration"
xmlns:jms="http://www.springframework.org/schema/integration/jms"
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
http://www.springframework.org/schema/integration/jms
http://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd">
<int:channel id="receiveChannel">
<int:queue/>
</int:channel>
<int:chain input-channel="outboundChainChannel">
<jms:outbound-channel-adapter destination="testQueue"/>
</int:chain>
<jms:message-driven-channel-adapter destination="testQueue" channel="receiveChannel"/>
<bean id="testQueue" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="test.queue"/>
</bean>
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="vm://localhost?broker.persistent=false"/>
</bean>
</beans>

View File

@@ -0,0 +1,48 @@
/*
* 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.jms;
import org.junit.Test;
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.support.MessageBuilder;
import static org.junit.Assert.*;
/**
* //INT-2275
*
* @author Artem Bilan
*/
public class JmsOutboundInsideChainTests {
@Test
public void testJmsOutboundChannelInsideChain(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("JmsOutboundInsideChainTests-context.xml", getClass());
PollableChannel receiveChannel = context.getBean("receiveChannel", PollableChannel.class);
MessageChannel outboundChainChannel = context.getBean("outboundChainChannel", MessageChannel.class);
String testString = "test";
Message<String> shippedMessage = MessageBuilder.withPayload(testString).build();
outboundChainChannel.send(shippedMessage);
Message<?> receivedMessage = receiveChannel.receive();
assertEquals(testString, receivedMessage.getPayload());
context.close();
}
}

View File

@@ -24,6 +24,13 @@
object-name="test.publisher:name=publisher"
default-notification-type="default.type"/>
<si:chain input-channel="publishingWithinChainChannel">
<jmx:notification-publishing-channel-adapter
object-name="test.publisher:name=publisher-chain"
default-notification-type="default.type"/>
</si:chain>
<bean id="testListener" class="org.springframework.integration.jmx.config.TestListener"/>
</beans>

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.
@@ -35,6 +35,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Mark Fisher
* @author Artem Bilan
* @since 2.0
*/
@ContextConfiguration
@@ -47,6 +48,9 @@ public class NotificationPublishingChannelAdapterParserTests {
@Autowired
private TestListener listener;
@Autowired
private MessageChannel publishingWithinChainChannel;
@After
public void clearListener() {
listener.lastNotification = null;
@@ -54,7 +58,7 @@ public class NotificationPublishingChannelAdapterParserTests {
@Test
public void publishStringMessag() throws Exception {
public void publishStringMessage() throws Exception {
assertNull(listener.lastNotification);
Message<?> message = MessageBuilder.withPayload("XYZ")
.setHeader(JmxHeaders.NOTIFICATION_TYPE, "test.type").build();
@@ -90,7 +94,18 @@ public class NotificationPublishingChannelAdapterParserTests {
assertEquals("default.type", notification.getType());
}
@Test //INT-2275
public void publishStringMessageWithinChain() throws Exception {
assertNull(listener.lastNotification);
Message<?> message = MessageBuilder.withPayload("XYZ")
.setHeader(JmxHeaders.NOTIFICATION_TYPE, "test.type").build();
publishingWithinChainChannel.send(message);
assertNotNull(listener.lastNotification);
Notification notification = listener.lastNotification;
assertEquals("XYZ", notification.getMessage());
assertEquals("test.type", notification.getType());
assertNull(notification.getUserData());
}
private static class TestData {
}

View File

@@ -20,6 +20,22 @@
object-name="org.springframework.integration.jmx.config:type=TestBean,name=testBeanAdapter"
operation-name="test"/>
<jmx:operation-invoking-channel-adapter id="operationWithNonNullReturn"
object-name="org.springframework.integration.jmx.config:type=TestBean,name=testBeanAdapter"
operation-name="testWithReturn"/>
<bean id="testBeanAdapter" class="org.springframework.integration.jmx.config.TestBean"/>
<si:chain input-channel="operationInvokingWithinChain">
<jmx:operation-invoking-channel-adapter
object-name="org.springframework.integration.jmx.config:type=TestBean,name=testBeanAdapter"
operation-name="test"/>
</si:chain>
<si:chain input-channel="operationWithinChainWithNonNullReturn">
<jmx:operation-invoking-channel-adapter
object-name="org.springframework.integration.jmx.config:type=TestBean,name=testBeanAdapter"
operation-name="testWithReturn"/>
</si:chain>
</beans>

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.
@@ -17,6 +17,8 @@
package org.springframework.integration.jmx.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.After;
import org.junit.Test;
@@ -24,6 +26,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.MessagingException;
import org.springframework.integration.jmx.JmxHeaders;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
@@ -33,6 +36,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
* @since 2.0
*/
@ContextConfiguration
@@ -42,6 +46,15 @@ public class OperationInvokingChannelAdapterParserTests {
@Autowired
private MessageChannel input;
@Autowired
private MessageChannel operationWithNonNullReturn;
@Autowired
private MessageChannel operationInvokingWithinChain;
@Autowired
private MessageChannel operationWithinChainWithNonNullReturn;
@Autowired
private TestBean testBean;
@@ -59,7 +72,19 @@ public class OperationInvokingChannelAdapterParserTests {
input.send(new GenericMessage<String>("test3"));
assertEquals(3, testBean.messages.size());
}
@Test
public void testOutboundAdapterWithNonNullReturn() throws Exception {
try {
operationWithNonNullReturn.send(new GenericMessage<String>("test1"));
fail("Expect MessagingException about non-null return");
}
catch (Exception e) {
assertTrue(e instanceof MessagingException);
// TODO Add check exception's message about 'must have a void return' after <jmx:operation-invoking-channel-adapter/> refactoring
}
}
@Test
// Headers should be ignored
public void adapterWitJmxHeaders() throws Exception {
@@ -69,7 +94,25 @@ public class OperationInvokingChannelAdapterParserTests {
input.send(this.createMessage("3"));
assertEquals(3, testBean.messages.size());
}
@Test //INT-2275
public void testInvokeOperationWithinChain() throws Exception {
operationInvokingWithinChain.send(new GenericMessage<String>("test1"));
assertEquals(1, testBean.messages.size());
}
@Test //INT-2275
public void testOperationWithinChainWithNonNullReturn() throws Exception {
try {
operationWithinChainWithNonNullReturn.send(new GenericMessage<String>("test1"));
fail("Expect MessagingException about non-null return");
}
catch (Exception e) {
assertTrue(e instanceof MessagingException);
// TODO Add check exception's message about 'must have a void return' after <jmx:operation-invoking-channel-adapter/> refactoring
}
}
private Message<?> createMessage(String payload){
return MessageBuilder.withPayload(payload)
.setHeader(JmxHeaders.OBJECT_NAME, "org.springframework.integration.jmx.config:type=TestBean,name=foo")

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.
@@ -26,6 +26,7 @@ import org.springframework.jmx.support.ObjectNameManager;
/**
* @author Mark Fisher
* @author Artem Bilan
* @since 2.0
*/
public class TestListener implements NotificationListener, BeanFactoryAware {
@@ -37,6 +38,8 @@ public class TestListener implements NotificationListener, BeanFactoryAware {
try {
server.addNotificationListener(
ObjectNameManager.getInstance("test.publisher:name=publisher"), this, null, null);
server.addNotificationListener(
ObjectNameManager.getInstance("test.publisher:name=publisher-chain"), this, null, null);
}
catch (Exception e) {
throw new IllegalArgumentException(e);

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.
@@ -31,6 +31,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.mapping.MessageMappingException;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.support.MessageBuilder;
@@ -40,6 +41,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Marius Bogoevici
* @author Artem Bilan
*/
@RunWith(value = SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:/org/springframework/integration/mail/mailSendingMessageHandlerContextTests.xml"})
@@ -51,6 +53,9 @@ public class MailSendingMessageHandlerContextTests {
@Autowired
private StubJavaMailSender mailSender;
@Autowired
private MessageChannel sendMailOutboundChainChannel;
@Before
public void reset() {
@@ -99,4 +104,13 @@ public class MailSendingMessageHandlerContextTests {
this.handler.handleMessage(new GenericMessage<byte[]>(payload));
}
@Test //INT-2275
public void mailOutboundChannelAdapterWithinChain() {
this.sendMailOutboundChainChannel.send(MailTestsHelper.createIntegrationMessage());
SimpleMailMessage mailMessage = MailTestsHelper.createSimpleMailMessage();
assertEquals("no mime message should have been sent", 0, this.mailSender.getSentMimeMessages().size());
assertEquals("only one simple message must be sent", 1, this.mailSender.getSentSimpleMailMessages().size());
assertEquals("message content different from expected", mailMessage, this.mailSender.getSentSimpleMailMessages().get(0));
}
}

View File

@@ -1,16 +1,18 @@
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-mail="http://www.springframework.org/schema/integration/mail"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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
http://www.springframework.org/schema/integration/mail http://www.springframework.org/schema/integration/mail/spring-integration-mail.xsd">
<bean id="javaMailSender" class="org.springframework.integration.mail.StubJavaMailSender">
<constructor-arg>
<bean class="javax.mail.internet.MimeMessage">
<constructor-arg type="javax.mail.Session"><null/></constructor-arg>
<constructor-arg type="javax.mail.Session">
<null/>
</constructor-arg>
</bean>
</constructor-arg>
</bean>
@@ -19,4 +21,8 @@
<constructor-arg ref="javaMailSender"/>
</bean>
</beans>
<int:chain input-channel="sendMailOutboundChainChannel">
<int-mail:outbound-channel-adapter mail-sender="javaMailSender"/>
</int:chain>
</beans>

View File

@@ -26,4 +26,8 @@
<bean id="testConverter" class="org.springframework.integration.redis.config.RedisOutboundChannelAdapterParserTests$TestMessageConverter"/>
<int:chain input-channel="redisOutboudChain">
<int-redis:outbound-channel-adapter topic="foo"/>
</int:chain>
</beans>

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.
@@ -38,6 +38,7 @@ import static junit.framework.Assert.assertEquals;
/**
* @author Oleg Zhurakousky
* @author Mark Fisher
* @author Artem Bilan
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@@ -69,6 +70,16 @@ public class RedisOutboundChannelAdapterParserTests extends RedisAvailableTests{
assertEquals("Hello Redis", receiveChannel.receive(1000).getPayload());
}
@Test //INT-2275
@RedisAvailable
public void testOutboundChannelAdapterWithinChain() throws Exception{
MessageChannel sendChannel = context.getBean("redisOutboudChain", MessageChannel.class);
sendChannel.send(new GenericMessage<String>("Hello Redis from chain"));
Thread.sleep(1000);
QueueChannel receiveChannel = context.getBean("receiveChannel", QueueChannel.class);
assertEquals("Hello Redis from chain", receiveChannel.receive(1000).getPayload());
}
@SuppressWarnings("unused")
private static class TestMessageConverter extends SimpleMessageConverter {

View File

@@ -0,0 +1,24 @@
<?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:si="http://www.springframework.org/schema/integration"
xmlns:sftp="http://www.springframework.org/schema/integration/sftp"
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
http://www.springframework.org/schema/integration/sftp
http://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd">
<si:chain input-channel="outboundChainChannel">
<sftp:outbound-channel-adapter
auto-create-directory="true"
session-factory="sftpSessionFactory"
remote-directory="remote-target-dir"/>
</si:chain>
<bean id="sftpSessionFactory"
class="org.springframework.integration.sftp.outbound.SftpSendingMessageHandlerTests$TestSftpSessionFactory"/>
</beans>

View File

@@ -30,7 +30,10 @@ import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
import org.springframework.integration.file.remote.session.Session;
@@ -106,6 +109,24 @@ public class SftpSendingMessageHandlerTests {
assertTrue(new File("remote-target-dir", "foo.txt").exists());
}
@Test //INT-2275
public void testSftpOutboundChannelAdapterInsideChain() throws Exception {
File targetDir = new File("remote-target-dir");
assertTrue("target directory does not exist: " + targetDir.getName(), targetDir.exists());
File srcFile = File.createTempFile("testHandleFileMessage", ".tmp");
srcFile.deleteOnExit();
File destFile = new File(targetDir, srcFile.getName());
destFile.deleteOnExit();
ApplicationContext context = new ClassPathXmlApplicationContext("SftpOutboundChannelAdapterInsideChainTests-context.xml", getClass());
MessageChannel channel = context.getBean("outboundChainChannel", MessageChannel.class);
channel.send(new GenericMessage<File>(srcFile));
assertTrue("destination file was not created", destFile.exists());
}
public static class TestSftpSessionFactory extends DefaultSftpSessionFactory {

View File

@@ -24,20 +24,22 @@ import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.nio.charset.Charset;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.stream.CharacterStreamWritingMessageHandler;
/**
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
*/
public class ConsoleOutboundChannelAdapterParserTests {
@@ -156,4 +158,13 @@ public class ConsoleOutboundChannelAdapterParserTests {
assertEquals("foo" + System.getProperty("line.separator"), out.toString());
}
@Test //INT-2275
public void stdoutInsideNestedChain() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"consoleOutboundChannelAdapterParserTests.xml", ConsoleOutboundChannelAdapterParserTests.class);
DirectChannel channel = context.getBean("stdoutInsideNestedChain", DirectChannel.class);
this.resetStreams();
channel.send(new GenericMessage<String>("foo"));
assertEquals("foobar", out.toString());
}
}

View File

@@ -20,4 +20,11 @@
<stdout-channel-adapter id="newlineAdapter" append-newline="true" channel="testChannel"/>
</beans:beans>
<integration:chain input-channel="stdoutInsideNestedChain">
<integration:transformer expression="payload + 'bar'"/>
<integration:chain>
<stdout-channel-adapter/>
</integration:chain>
</integration:chain>
</beans:beans>

View File

@@ -33,6 +33,10 @@
<twitter:dm-outbound-channel-adapter twitter-template="twitterTemplate" channel="inputChannel"/>
<chain input-channel="dmOutboundWithinChain">
<twitter:dm-outbound-channel-adapter twitter-template="twitterTemplate"/>
</chain>
</beans:beans>

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.
@@ -31,14 +31,19 @@ import org.springframework.util.StringUtils;
/**
* @author Josh Long
* @author Oleg Zhurakouksy
* @author Artem Bilan
*/
@ContextConfiguration
public class TestSendingDMsUsingNamespace extends AbstractJUnit4SpringContextTests {
@Autowired
@Qualifier("inputChannel")
private MessageChannel inputChannel;
@Autowired
@Qualifier("dmOutboundWithinChain")
private MessageChannel dmOutboundWithinChain;
@Test
@Ignore
public void testSendigRealDirectMessage() throws Throwable {
@@ -52,4 +57,16 @@ public class TestSendingDMsUsingNamespace extends AbstractJUnit4SpringContextTes
inputChannel.send(mb.build());
}
@Test
@Ignore
public void testSendigDirectMessageFromChain() throws Throwable {
String dmUsr = "z_oleg";
MessageBuilder<String> mb = MessageBuilder.withPayload("Hello world!");
if (StringUtils.hasText(dmUsr)) {
mb.setHeader(TwitterHeaders.DM_TARGET_USER_ID, dmUsr);
}
dmOutboundWithinChain.send(mb.build());
}
}

View File

@@ -32,7 +32,11 @@
<channel id="out"/>
<twitter:outbound-channel-adapter twitter-template="twitterTemplate" channel="out"/>
<chain input-channel="outFromChain">
<twitter:outbound-channel-adapter twitter-template="twitterTemplate"/>
</chain>
</beans:beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 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.
@@ -21,6 +21,8 @@ import java.util.Date;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
@@ -31,6 +33,7 @@ import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
/**
* @author Josh Long
* @author Artem Bilan
*/
@ContextConfiguration
public class TestSendingUpdatesUsingNamespace extends AbstractJUnit4SpringContextTests {
@@ -39,6 +42,10 @@ public class TestSendingUpdatesUsingNamespace extends AbstractJUnit4SpringContex
@Value("#{out}") private MessageChannel channel;
@Autowired
@Qualifier("outFromChain")
private MessageChannel outFromChain;
@Test
@Ignore
public void testSendingATweet() throws Throwable {
@@ -48,4 +55,11 @@ public class TestSendingUpdatesUsingNamespace extends AbstractJUnit4SpringContex
this.messagingTemplate.send(this.channel, m);
}
@Test
@Ignore
public void testSendingATweetFromChain() throws Throwable {
Message<String> m = MessageBuilder.withPayload("Early start today" + new Date(System.currentTimeMillis())).build();
this.outFromChain.send(m);
}
}

View File

@@ -43,4 +43,8 @@
xmpp-connection="testConnection">
</int-xmpp:outbound-channel-adapter>
<int:chain input-channel="outboundChainChannel">
<int-xmpp:outbound-channel-adapter xmpp-connection="testConnection"/>
</int:chain>
</beans>

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.
@@ -49,6 +49,7 @@ import static org.mockito.Mockito.verify;
/**
* @author Oleg Zhurakousky
* @author Mark Fisher
* @author Artem Bilan
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@@ -104,7 +105,6 @@ public class ChatMessageOutboundChannelAdapterParserTests {
org.jivesoftware.smack.packet.Message xmppMessage = (org.jivesoftware.smack.packet.Message) args[0];
assertEquals("oleg", xmppMessage.getTo());
assertEquals("foobar", xmppMessage.getProperty("foobar"));
assertEquals("oleg", xmppMessage.getTo());
return null;
}})
.when(connection).sendPacket(Mockito.any(org.jivesoftware.smack.packet.Message.class));
@@ -112,6 +112,29 @@ public class ChatMessageOutboundChannelAdapterParserTests {
channel.send(message);
verify(connection, times(1)).sendPacket(Mockito.any(org.jivesoftware.smack.packet.Message.class));
Mockito.reset(connection);
}
@SuppressWarnings("rawtypes")
@Test //INT-2275
public void testOutboundChannelAdapterInsideChain() throws Exception{
MessageChannel channel = context.getBean("outboundChainChannel", MessageChannel.class);
Message<?> message = MessageBuilder.withPayload("hello").setHeader(XmppHeaders.TO, "artem").build();
XMPPConnection connection = context.getBean("testConnection", XMPPConnection.class);
Mockito.doAnswer(new Answer() {
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
org.jivesoftware.smack.packet.Message xmppMessage = (org.jivesoftware.smack.packet.Message) args[0];
assertEquals("artem", xmppMessage.getTo());
assertEquals("hello", xmppMessage.getBody());
return null;
}})
.when(connection).sendPacket(Mockito.any(org.jivesoftware.smack.packet.Message.class));
channel.send(message);
verify(connection, times(1)).sendPacket(Mockito.any(org.jivesoftware.smack.packet.Message.class));
Mockito.reset(connection);
}
}

View File

@@ -57,7 +57,7 @@
<para>
The &lt;chain&gt; element provides an <code>input-channel</code> attribute, and if the last element in the chain is capable
of producing reply messages (optional), it also supports an <code>output-channel</code> attribute. The sub-elements are then
filters, transformers, splitters, and service-activators. The last element may also be a router.
filters, transformers, splitters, and service-activators. The last element may also be a router or an outbound-channel-adapter.
<programlisting language="xml"><![CDATA[ <int:chain input-channel="input" output-channel="output">
<int:filter ref="someSelector" throw-exception-on-rejection="true"/>
<int:header-enricher>
@@ -72,7 +72,18 @@
that touches only header values. You could obtain the same result by implementing a MessageHandler that did the
header modifications and wiring that as a bean, but the header-enricher is obviously a simpler option.
</para>
<para>
The &lt;chain&gt; can be configured as the last 'black-box' consumer of the message flow. For this solution it is
enough to put at the end of the &lt;chain&gt; some &lt;outbound-channel-adapter&gt;:
<programlisting language="xml"><![CDATA[ <int:chain input-channel="input">
<si-xml:marshalling-transformer marshaller="marshaller" result-type="StringResult" />
<int:service-activator ref="someService" method="someMethod"/>
<int:header-enricher>
<int:header name="foo" value="bar"/>
</int:header-enricher>
<int:logging-channel-adapter level="INFO" log-full-message="true"/>
</int:chain>]]></programlisting>
</para>
<para>
Sometimes you need to make a nested call to another chain from within a chain and then come
back and continue execution within the original chain.