diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingCorrelationStrategy.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingCorrelationStrategy.java index 3b7f708117..6d312628fc 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingCorrelationStrategy.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingCorrelationStrategy.java @@ -16,8 +16,13 @@ package org.springframework.integration.aggregator; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.Message; import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; +import org.springframework.util.Assert; /** * {@link CorrelationStrategy} implementation that evaluates an expression. @@ -26,12 +31,23 @@ import org.springframework.integration.handler.ExpressionEvaluatingMessageProces */ public class ExpressionEvaluatingCorrelationStrategy implements CorrelationStrategy { + private static final ExpressionParser expressionParser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + + private final ExpressionEvaluatingMessageProcessor processor; - public ExpressionEvaluatingCorrelationStrategy(String expression) { + + public ExpressionEvaluatingCorrelationStrategy(String expressionString) { + Assert.hasText(expressionString, "expressionString must not be empty"); + Expression expression = expressionParser.parseExpression(expressionString); this.processor = new ExpressionEvaluatingMessageProcessor(expression, Object.class); } + public ExpressionEvaluatingCorrelationStrategy(Expression expression) { + this.processor = new ExpressionEvaluatingMessageProcessor(expression, Object.class); + } + + public Object getCorrelationKey(Message message) { return processor.processMessage(message); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageGroupProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageGroupProcessor.java index 240f43d196..bbfd121fe6 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageGroupProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/aggregator/ExpressionEvaluatingMessageGroupProcessor.java @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2010 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.aggregator; import java.util.Map; @@ -15,12 +31,16 @@ import org.springframework.integration.store.MessageGroup; * * @author Alex Peters * @author Dave Syer - * */ public class ExpressionEvaluatingMessageGroupProcessor extends AbstractAggregatingMessageGroupProcessor implements BeanFactoryAware { - + private final ExpressionEvaluatingMessageListProcessor processor; + + public ExpressionEvaluatingMessageGroupProcessor(String expression) { + processor = new ExpressionEvaluatingMessageListProcessor(expression); + } + public void setBeanFactory(BeanFactory beanFactory) { processor.setBeanFactory(beanFactory); } @@ -33,10 +53,6 @@ public class ExpressionEvaluatingMessageGroupProcessor extends AbstractAggregati processor.setExpectedType(expectedType); } - public ExpressionEvaluatingMessageGroupProcessor(String expression) { - processor = new ExpressionEvaluatingMessageListProcessor(expression); - } - /** * Evaluate the expression provided on the unmarked messages (a collection) in the group, and delegate to the * {@link MessagingTemplate} to send downstream. diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/MapBasedChannelResolver.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/MapBasedChannelResolver.java deleted file mode 100644 index 197352937f..0000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/MapBasedChannelResolver.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2002-2009 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.channel; - -import java.util.HashMap; -import java.util.Map; - -import org.springframework.integration.MessageChannel; -import org.springframework.integration.support.channel.ChannelResolver; -import org.springframework.util.Assert; - -/** - * {@link ChannelResolver} implementation that resolves {@link MessageChannel} - * instances by matching the channel name against keys within a Map. - * - * @author Mark Fisher - */ -public class MapBasedChannelResolver implements ChannelResolver { - - private volatile Map channelMap = new HashMap(); - - /** - * Empty constructor for use when providing the channel map via - * {@link #setChannelMap(Map)}. - */ - public MapBasedChannelResolver() { - } - - /** - * Create a {@link ChannelResolver} that uses the provided Map. - * Each String key will resolve to the associated channel value. - */ - public MapBasedChannelResolver(Map channelMap) { - this.setChannelMap(channelMap); - } - - /** - * Provide a map of channels to be used by this resolver. - * Each String key will resolve to the associated channel value. - */ - public void setChannelMap(Map channelMap) { - Assert.notNull(channelMap, "channelMap must not be null"); - this.channelMap = channelMap; - } - - public MessageChannel resolveChannelName(String channelName) { - return this.channelMap.get(channelName); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMessageHandlerFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMessageHandlerFactoryBean.java index 6e753eb129..d3ebf8132b 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMessageHandlerFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMessageHandlerFactoryBean.java @@ -22,6 +22,10 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.MessageChannel; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.handler.AbstractMessageHandler; @@ -38,13 +42,16 @@ import org.springframework.util.StringUtils; */ abstract class AbstractMessageHandlerFactoryBean implements FactoryBean, BeanFactoryAware { + private static final ExpressionParser expressionParser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + + private volatile MessageHandler handler; private volatile Object targetObject; private volatile String targetMethodName; - private volatile String expression; + private volatile Expression expression; private volatile MessageChannel outputChannel; @@ -65,7 +72,11 @@ abstract class AbstractMessageHandlerFactoryBean implements FactoryBean) { this.handler = this.createMessageProcessingHandler((MessageProcessor) this.targetObject); } else { @@ -158,7 +169,7 @@ abstract class AbstractMessageHandlerFactoryBean implements FactoryBean channelIdentifierMap; private volatile MessageChannel defaultOutputChannel; @@ -48,7 +52,6 @@ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean { private volatile Boolean ignoreSendFailures; - public void setChannelResolver(ChannelResolver channelResolver) { this.channelResolver = channelResolver; } @@ -76,36 +79,75 @@ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean { public void setIgnoreSendFailures(Boolean ignoreSendFailures) { this.ignoreSendFailures = ignoreSendFailures; } - - @Override - MessageHandler createMethodInvokingHandler(Object targetObject, String targetMethodName) { - Assert.notNull(targetObject, "target object must not be null"); - AbstractMessageRouter router = this.createRouter(targetObject, targetMethodName); - return this.configureRouter(router); + + public void setChannelIdentifierMap(Map channelIdentifierMap) { + this.channelIdentifierMap = channelIdentifierMap; } @Override - MessageHandler createExpressionEvaluatingHandler(String expression) { + MessageHandler createMethodInvokingHandler(Object targetObject, String targetMethodName) { + + Assert.notNull(targetObject, "target object must not be null"); + AbstractMessageRouter router = extractRouter(targetObject); + + if (router == null) { + router = this.createRouter(targetObject, targetMethodName); + this.configureRouter(router); + return router; + } + + Assert.isTrue(!StringUtils.hasText(targetMethodName), "target method should not be provided when the target " + + "object is an implementation of AbstractMessageRouter"); + this.configureRouter(router); + + if (targetObject instanceof MessageHandler) { + return (MessageHandler) targetObject; + } + return router; + + } + + private AbstractMessageRouter extractRouter(Object targetObject) { + if (targetObject instanceof AbstractMessageRouter) { + return (AbstractMessageRouter) targetObject; + } + if (targetObject instanceof Advised) { + return extractAopTarget((Advised) targetObject); + } + return null; + } + + private AbstractMessageRouter extractAopTarget(Advised advised) { + TargetSource targetSource = advised.getTargetSource(); + if (targetSource == null) { + return null; + } + Object target; + try { + target = targetSource.getTarget(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + return extractRouter(target); + } + + @Override + MessageHandler createExpressionEvaluatingHandler(Expression expression) { return this.configureRouter(new ExpressionEvaluatingRouter(expression)); } private AbstractMessageRouter createRouter(Object targetObject, String targetMethodName) { - if (targetObject instanceof AbstractMessageRouter) { - Assert.isTrue(!StringUtils.hasText(targetMethodName), - "target method should not be provided when the target " + - "object is an implementation of AbstractMessageRouter"); - return (AbstractMessageRouter) targetObject; - } - MethodInvokingRouter router = (StringUtils.hasText(targetMethodName)) - ? new MethodInvokingRouter(targetObject, targetMethodName) - : new MethodInvokingRouter(targetObject); + MethodInvokingRouter router = (StringUtils.hasText(targetMethodName)) ? new MethodInvokingRouter(targetObject, + targetMethodName) : new MethodInvokingRouter(targetObject); return router; } private AbstractMessageRouter configureRouter(AbstractMessageRouter router) { - if (this.channelResolver != null && - router instanceof AbstractChannelNameResolvingMessageRouter) { - ((AbstractChannelNameResolvingMessageRouter) router).setChannelResolver(this.channelResolver); + if (this.channelResolver != null && router instanceof AbstractMessageRouter) { + ((AbstractMessageRouter) router).setChannelResolver(this.channelResolver); + } + if (this.channelIdentifierMap != null && router instanceof AbstractMessageRouter) { + ((AbstractMessageRouter) router).setChannelIdentifierMap(this.channelIdentifierMap); } if (this.defaultOutputChannel != null) { router.setDefaultOutputChannel(this.defaultOutputChannel); @@ -114,10 +156,11 @@ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean { router.setTimeout(timeout.longValue()); } if (this.ignoreChannelNameResolutionFailures != null) { - Assert.isTrue(router instanceof AbstractChannelNameResolvingMessageRouter, + Assert.isTrue(router instanceof AbstractMessageRouter, "The 'ignoreChannelNameResolutionFailures' property can only be set on routers that extend " - + AbstractChannelNameResolvingMessageRouter.class.getName()); - ((AbstractChannelNameResolvingMessageRouter) router).setIgnoreChannelNameResolutionFailures(ignoreChannelNameResolutionFailures); + + AbstractMessageRouter.class.getName()); + ((AbstractMessageRouter) router) + .setIgnoreChannelNameResolutionFailures(ignoreChannelNameResolutionFailures); } if (this.applySequence != null) { router.setApplySequence(this.applySequence); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/ServiceActivatorFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/ServiceActivatorFactoryBean.java index 8c593bfd26..6651c3b748 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/ServiceActivatorFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/ServiceActivatorFactoryBean.java @@ -16,6 +16,7 @@ package org.springframework.integration.config; +import org.springframework.expression.Expression; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; import org.springframework.integration.handler.MessageProcessor; @@ -51,7 +52,7 @@ public class ServiceActivatorFactoryBean extends AbstractMessageHandlerFactoryBe } @Override - MessageHandler createExpressionEvaluatingHandler(String expression) { + MessageHandler createExpressionEvaluatingHandler(Expression expression) { ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression); processor.setBeanFactory(this.getBeanFactory()); return this.configureHandler(new ServiceActivatingHandler(processor)); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/SourcePollingChannelAdapterFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/SourcePollingChannelAdapterFactoryBean.java index 957fbee0a5..888b37532d 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/SourcePollingChannelAdapterFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/SourcePollingChannelAdapterFactoryBean.java @@ -125,6 +125,11 @@ public class SourcePollingChannelAdapterFactoryBean implements FactoryBean childElements = DomUtils.getChildElementsByTagName(element, "mapping"); - if (childElements != null && childElements.size() > 0) { - BeanDefinitionBuilder channelResolverBuilder = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationNamespaceUtils.BASE_PACKAGE + ".channel.MapBasedChannelResolver"); - ManagedMap channelMap = new ManagedMap(); - for (Element childElement : childElements) { - channelMap.put(childElement.getAttribute("value"), - new RuntimeBeanReference(childElement.getAttribute("channel"))); - } - channelResolverBuilder.addPropertyValue("channelMap", channelMap); - beanDefinition.getPropertyValues().add("channelResolver", channelResolverBuilder.getBeanDefinition()); - } - } - return beanDefinition; - } - - protected abstract BeanDefinition doParseRouter(Element element, ParserContext parserContext); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractDelegatingConsumerEndpointParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractDelegatingConsumerEndpointParser.java index 6a5d79ef62..0e32bc975a 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractDelegatingConsumerEndpointParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractDelegatingConsumerEndpointParser.java @@ -38,6 +38,7 @@ abstract class AbstractDelegatingConsumerEndpointParser extends AbstractConsumer @Override protected final BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { + Object source = parserContext.extractSource(element); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(this.getFactoryBeanClassName()); BeanComponentDefinition innerDefinition = IntegrationNamespaceUtils.parseInnerHandlerDefinition(element, parserContext); String ref = element.getAttribute(REF_ATTRIBUTE); @@ -45,23 +46,43 @@ abstract class AbstractDelegatingConsumerEndpointParser extends AbstractConsumer boolean hasRef = StringUtils.hasText(ref); boolean hasExpression = StringUtils.hasText(expression); Element scriptElement = DomUtils.getChildElementByTagName(element, "script"); + Element expressionElement = DomUtils.getChildElementByTagName(element, "expression"); if (innerDefinition != null) { - if (hasRef || hasExpression) { + if (hasRef || hasExpression || expressionElement != null) { parserContext.getReaderContext().error( - "Neither 'ref' nor 'expression' are permitted when an inner bean () is configured.", element); + "Neither 'ref' nor 'expression' are permitted when an inner bean () is configured.", source); return null; } builder.addPropertyValue("targetObject", innerDefinition); } + else if (scriptElement != null) { + if (hasRef || hasExpression || expressionElement != null) { + parserContext.getReaderContext().error( + "Neither 'ref' nor 'expression' are permitted when an inner script element is configured.", source); + return null; + } + BeanDefinition scriptBeanDefinition = parserContext.getDelegate().parseCustomElement(scriptElement, builder.getBeanDefinition()); + builder.addPropertyValue("targetObject", scriptBeanDefinition); + } + else if (expressionElement != null) { + if (hasRef || hasExpression) { + parserContext.getReaderContext().error( + "Neither 'ref' nor 'expression' are permitted when an inner 'expression' element is configured.", source); + return null; + } + BeanDefinitionBuilder dynamicExpressionBuilder = BeanDefinitionBuilder.genericBeanDefinition( + "org.springframework.integration.expression.DynamicExpression"); + String key = expressionElement.getAttribute("key"); + String expressionSourceReference = expressionElement.getAttribute("source"); + dynamicExpressionBuilder.addConstructorArgValue(key); + dynamicExpressionBuilder.addConstructorArgReference(expressionSourceReference); + builder.addPropertyValue("expression", dynamicExpressionBuilder.getBeanDefinition()); + } else if (hasRef) { builder.addPropertyReference("targetObject", ref); } else if (hasExpression) { - builder.addPropertyValue("expression", expression); - } - else if (scriptElement != null) { - BeanDefinition scriptBeanDefinition = parserContext.getDelegate().parseCustomElement(scriptElement, builder.getBeanDefinition()); - builder.addPropertyValue("targetObject", scriptBeanDefinition); + builder.addPropertyValue("expressionString", expression); } else if (!this.hasDefaultOption()) { parserContext.getReaderContext().error("Exactly one of the 'ref' attribute, 'expression' attribute, " + diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractRouterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractRouterParser.java index d2a3d0f241..002d1c8c10 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractRouterParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractRouterParser.java @@ -16,11 +16,17 @@ package org.springframework.integration.config.xml; +import java.util.List; + import org.w3c.dom.Element; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; /** * Base parser for routers. @@ -44,6 +50,34 @@ public abstract class AbstractRouterParser extends AbstractConsumerEndpointParse return builder; } - protected abstract BeanDefinition parseRouter(Element element, ParserContext parserContext); + protected final BeanDefinition parseRouter(Element element, ParserContext parserContext) { + BeanDefinition beanDefinition = this.doParseRouter(element, parserContext); + if (beanDefinition != null) { + String channelResolver = element.getAttribute("channel-resolver"); + if (StringUtils.hasText(channelResolver)){ + beanDefinition.getPropertyValues().add("channelResolver", new RuntimeBeanReference(channelResolver)); + } + // check if mapping is provided otherwise returned values will be treated as channel names + List childElements = DomUtils.getChildElementsByTagName(element, "mapping"); + if (childElements != null && childElements.size() > 0) { + ManagedMap channelMap = new ManagedMap(); + for (Element childElement : childElements) { + String beanClassName = beanDefinition.getBeanClassName(); + String key = null; + if (beanClassName.endsWith("PayloadTypeRouter")){ + key = childElement.getAttribute("type"); + } + else { + key = childElement.getAttribute("value"); + } + channelMap.put(key, childElement.getAttribute("channel")); + } + beanDefinition.getPropertyValues().add("channelIdentifierMap", channelMap); + } + } + return beanDefinition; + } + + protected abstract BeanDefinition doParseRouter(Element element, ParserContext parserContext); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/DefaultRouterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/DefaultRouterParser.java index 9be2d47513..49ec9c25fd 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/DefaultRouterParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/DefaultRouterParser.java @@ -18,9 +18,6 @@ package org.springframework.integration.config.xml; import java.util.List; -import org.w3c.dom.Element; - -import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.support.RootBeanDefinition; @@ -28,11 +25,13 @@ import org.springframework.beans.factory.xml.ParserContext; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; /** * Parser for the <router/> element. * * @author Mark Fisher + * @author Oleg Zhurakousky */ public class DefaultRouterParser extends AbstractDelegatingConsumerEndpointParser { @@ -60,13 +59,12 @@ public class DefaultRouterParser extends AbstractDelegatingConsumerEndpointParse parserContext.extractSource(element)); } BeanDefinitionBuilder channelResolverBuilder = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationNamespaceUtils.BASE_PACKAGE + ".channel.MapBasedChannelResolver"); - ManagedMap channelMap = new ManagedMap(); + IntegrationNamespaceUtils.BASE_PACKAGE + ".support.channel.BeanFactoryChannelResolver"); + ManagedMap channelMap = new ManagedMap(); for (Element mappingElement : mappingElements) { - channelMap.put(mappingElement.getAttribute("value"), - new RuntimeBeanReference(mappingElement.getAttribute("channel"))); + channelMap.put(mappingElement.getAttribute("value"), mappingElement.getAttribute("channel")); } - channelResolverBuilder.addPropertyValue("channelMap", channelMap); + builder.addPropertyValue("channelIdentifierMap", channelMap); builder.addPropertyValue(CHANNEL_RESOLVER_PROPERTY, channelResolverBuilder.getBeanDefinition()); } else if (StringUtils.hasText(resolverBeanName)) { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderEnricherParserSupport.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderEnricherParserSupport.java index db5c95bce3..9d3b72cf86 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderEnricherParserSupport.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderEnricherParserSupport.java @@ -29,6 +29,7 @@ import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; /** * Base support class for 'header-enricher' parsers. @@ -110,12 +111,17 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar if (headerName != null) { String value = headerElement.getAttribute("value"); String ref = headerElement.getAttribute("ref"); - String expression = headerElement.getAttribute("expression"); String method = headerElement.getAttribute("method"); + String expression = headerElement.getAttribute("expression"); + Element expressionElement = DomUtils.getChildElementByTagName(headerElement, "expression"); + if (StringUtils.hasText(expression) && expressionElement != null) { + parserContext.getReaderContext().error("The 'expression' attribute and sub-element are mutually exclusive", element); + return; + } boolean isValue = StringUtils.hasText(value); boolean isRef = StringUtils.hasText(ref); - boolean isExpression = StringUtils.hasText(expression); boolean hasMethod = StringUtils.hasText(method); + boolean isExpression = StringUtils.hasText(expression) || expressionElement != null; if (!(isValue ^ (isRef ^ isExpression))) { parserContext.getReaderContext().error( "Exactly one of the 'ref', 'value', or 'expression' attributes is required.", element); @@ -139,7 +145,16 @@ public abstract class HeaderEnricherParserSupport extends AbstractTransformerPar } valueProcessorBuilder = BeanDefinitionBuilder.genericBeanDefinition( IntegrationNamespaceUtils.BASE_PACKAGE + ".transformer.HeaderEnricher$ExpressionEvaluatingHeaderValueMessageProcessor"); - valueProcessorBuilder.addConstructorArgValue(expression); + if (expressionElement != null) { + BeanDefinitionBuilder dynamicExpressionBuilder = BeanDefinitionBuilder.genericBeanDefinition( + "org.springframework.integration.expression.DynamicExpression"); + dynamicExpressionBuilder.addConstructorArgValue(expressionElement.getAttribute("key")); + dynamicExpressionBuilder.addConstructorArgReference(expressionElement.getAttribute("source")); + valueProcessorBuilder.addConstructorArgValue(dynamicExpressionBuilder.getBeanDefinition()); + } + else { + valueProcessorBuilder.addConstructorArgValue(expression); + } valueProcessorBuilder.addConstructorArgValue(headerType); } else { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderValueRouterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderValueRouterParser.java index dee7eafcf2..704489c496 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderValueRouterParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/HeaderValueRouterParser.java @@ -29,7 +29,7 @@ import org.springframework.beans.factory.xml.ParserContext; * @author Mark Fisher * @since 1.0.3 */ -public class HeaderValueRouterParser extends AbstractChannelNameResolvingRouterParser { +public class HeaderValueRouterParser extends AbstractRouterParser { @Override protected BeanDefinition doParseRouter(Element element, ParserContext parserContext) { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PayloadTypeRouterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PayloadTypeRouterParser.java index 8bf6023e29..e831d633fd 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PayloadTypeRouterParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PayloadTypeRouterParser.java @@ -16,18 +16,10 @@ package org.springframework.integration.config.xml; -import java.util.List; - -import org.w3c.dom.Element; - import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; /** * Parser for the <payload-type-router/> element. @@ -37,27 +29,12 @@ import org.springframework.util.xml.DomUtils; * @since 1.0.3 */ public class PayloadTypeRouterParser extends AbstractRouterParser { - + @Override - @SuppressWarnings("unchecked") - protected BeanDefinition parseRouter(Element element, ParserContext parserContext) { + protected BeanDefinition doParseRouter(Element element, + ParserContext parserContext) { BeanDefinitionBuilder payloadTypeRouterBuilder = BeanDefinitionBuilder.genericBeanDefinition( IntegrationNamespaceUtils.BASE_PACKAGE + ".router.PayloadTypeRouter"); - List childElements = DomUtils.getChildElementsByTagName(element, "mapping"); - Assert.notEmpty(childElements, - "Type mapping must be provided (e.g., )"); - ManagedMap channelMap = new ManagedMap(); - for (Element childElement : childElements) { - String typeName = childElement.getAttribute("type"); - ClassLoader classLoader = parserContext.getReaderContext().getBeanClassLoader(); - if (classLoader == null) { - classLoader = ClassUtils.getDefaultClassLoader(); - } - Assert.isTrue(ClassUtils.isPresent(typeName, classLoader), typeName + " can not be loaded"); - channelMap.put(typeName, new RuntimeBeanReference(childElement.getAttribute("channel"))); - } - payloadTypeRouterBuilder.addPropertyValue("payloadTypeChannelMap", channelMap); return payloadTypeRouterBuilder.getBeanDefinition(); } - } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PublishingInterceptorParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PublishingInterceptorParser.java index a2638530f4..ad8bb45fd8 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PublishingInterceptorParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PublishingInterceptorParser.java @@ -53,10 +53,9 @@ public class PublishingInterceptorParser extends AbstractBeanDefinitionParser { spelSourceBuilder.addPropertyValue("headerExpressionMap", mappings.get("headers")); } BeanDefinitionBuilder chResolverBuilder = BeanDefinitionBuilder.genericBeanDefinition( - "org.springframework.integration.channel.MapBasedChannelResolver"); + "org.springframework.integration.support.channel.BeanFactoryChannelResolver"); if (mappings.get("channels") != null){ spelSourceBuilder.addPropertyValue("channelMap", mappings.get("channels")); - chResolverBuilder.addConstructorArgValue(mappings.get("resolvableChannels")); } String chResolverName = BeanDefinitionReaderUtils.registerWithGeneratedName(chResolverBuilder.getBeanDefinition(), parserContext.getRegistry()); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/endpoint/AbstractPollingEndpoint.java b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/AbstractPollingEndpoint.java index 6338a68bc8..c01b5fcb05 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/endpoint/AbstractPollingEndpoint.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/AbstractPollingEndpoint.java @@ -18,13 +18,13 @@ package org.springframework.integration.endpoint; import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.Executor; import java.util.concurrent.ScheduledFuture; import org.aopalliance.aop.Advice; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.core.task.SyncTaskExecutor; -import org.springframework.core.task.TaskExecutor; import org.springframework.integration.MessageHandlingException; import org.springframework.integration.MessagingException; import org.springframework.integration.channel.MessagePublishingErrorHandler; @@ -43,11 +43,11 @@ import org.springframework.util.ErrorHandler; */ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implements BeanClassLoaderAware { - private volatile TaskExecutor taskExecutor = new SyncTaskExecutor(); + private volatile Executor taskExecutor = new SyncTaskExecutor(); private volatile ErrorHandler errorHandler; - private volatile PollerMetadata pollerMetadata; + private volatile PollerMetadata pollerMetadata = new PollerMetadata(); private volatile ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); @@ -84,14 +84,14 @@ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implement return; } Assert.notNull(this.pollerMetadata.getTrigger(), "Trigger is required"); - Assert.notNull(this.getBeanFactory(), "BeanFactory is required"); - TaskExecutor providedExecutor = this.pollerMetadata.getTaskExecutor(); + Executor providedExecutor = this.pollerMetadata.getTaskExecutor(); if (providedExecutor != null) { this.taskExecutor = providedExecutor; } if (this.taskExecutor != null) { if (!(this.taskExecutor instanceof ErrorHandlingTaskExecutor)) { if (this.errorHandler == null) { + Assert.notNull(this.getBeanFactory(), "BeanFactory is required"); this.errorHandler = new MessagePublishingErrorHandler( new BeanFactoryChannelResolver(getBeanFactory())); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/expression/DynamicExpression.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/DynamicExpression.java new file mode 100644 index 0000000000..2ad9983bcb --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/DynamicExpression.java @@ -0,0 +1,149 @@ +/* + * Copyright 2002-2010 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.expression; + +import java.util.Locale; + +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.EvaluationException; +import org.springframework.expression.Expression; +import org.springframework.util.Assert; + +/** + * An implementation of {@link Expression} that delegates to an {@link ExpressionSource} + * for resolving the actual Expression instance per-invocation at runtime. + * + * @author Mark Fisher + * @since 2.0 + */ +public class DynamicExpression implements Expression { + + private final String key; + + private final ExpressionSource expressionSource; + + + public DynamicExpression(String key, ExpressionSource expressionSource) { + Assert.notNull(key, "key must not be null"); + Assert.notNull(expressionSource, "expressionSource must not be null"); + this.key = key; + this.expressionSource = expressionSource; + } + + + public Object getValue() throws EvaluationException { + return this.resolveExpression().getValue(); + } + + public Object getValue(Object rootObject) throws EvaluationException { + return this.resolveExpression().getValue(rootObject); + } + + public T getValue(Class desiredResultType) throws EvaluationException { + return this.resolveExpression().getValue(desiredResultType); + } + + public T getValue(Object rootObject, Class desiredResultType) throws EvaluationException { + return this.resolveExpression().getValue(rootObject, desiredResultType); + } + + public Object getValue(EvaluationContext context) throws EvaluationException { + return this.resolveExpression().getValue(context); + } + + public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException { + return this.resolveExpression().getValue(context, rootObject); + } + + public T getValue(EvaluationContext context, Class desiredResultType) throws EvaluationException { + return this.resolveExpression().getValue(context, desiredResultType); + } + + public T getValue(EvaluationContext context, Object rootObject, Class desiredResultType) throws EvaluationException { + return this.resolveExpression().getValue(context, rootObject, desiredResultType); + } + + public Class getValueType() throws EvaluationException { + return this.resolveExpression().getValueType(); + } + + public Class getValueType(Object rootObject) throws EvaluationException { + return this.resolveExpression().getValueType(rootObject); + } + + public Class getValueType(EvaluationContext context) throws EvaluationException { + return this.resolveExpression().getValueType(context); + } + + public Class getValueType(EvaluationContext context, Object rootObject) throws EvaluationException { + return this.resolveExpression().getValueType(context, rootObject); + } + + public TypeDescriptor getValueTypeDescriptor() throws EvaluationException { + return this.resolveExpression().getValueTypeDescriptor(); + } + + public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException { + return this.resolveExpression().getValueTypeDescriptor(rootObject); + } + + public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) throws EvaluationException { + return this.resolveExpression().getValueTypeDescriptor(context); + } + + public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject) throws EvaluationException { + return this.resolveExpression().getValueTypeDescriptor(context, rootObject); + } + + public boolean isWritable(EvaluationContext context) throws EvaluationException { + return this.resolveExpression().isWritable(context); + } + + public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException { + return this.resolveExpression().isWritable(context, rootObject); + } + + public boolean isWritable(Object rootObject) throws EvaluationException { + return this.resolveExpression().isWritable(rootObject); + } + + public void setValue(EvaluationContext context, Object value) throws EvaluationException { + this.resolveExpression().setValue(context, value); + } + + public void setValue(Object rootObject, Object value) throws EvaluationException { + this.resolveExpression().setValue(rootObject, value); + } + + public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException { + this.resolveExpression().setValue(context, rootObject, value); + } + + public String getExpressionString() { + return this.resolveExpression().getExpressionString(); + } + + private Expression resolveExpression() { + Locale locale = LocaleContextHolder.getLocale(); + Expression expression = this.expressionSource.getExpression(this.key, locale); + Assert.state(expression != null, "Unable to resolve Expression with key '" + this.key + "'"); + return expression; + } + +} diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XmlValidator.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionSource.java similarity index 60% rename from spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XmlValidator.java rename to spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionSource.java index b32ddb8816..fca593da59 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XmlValidator.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2010 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. @@ -14,12 +14,20 @@ * limitations under the License. */ -package org.springframework.integration.xml.router; +package org.springframework.integration.expression; -import javax.xml.transform.Source; +import java.util.Locale; -public interface XmlValidator { - - public boolean isValid(Source source) ; +import org.springframework.expression.Expression; + +/** + * Strategy interface for retrieving Expressions. + * + * @author Mark Fisher + * @since 2.0 + */ +public interface ExpressionSource { + + Expression getExpression(String key, Locale locale); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/expression/ReloadableResourceBundleExpressionSource.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/ReloadableResourceBundleExpressionSource.java new file mode 100644 index 0000000000..fd70724c98 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/ReloadableResourceBundleExpressionSource.java @@ -0,0 +1,572 @@ +/* + * Copyright 2002-2010 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.expression; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.util.Assert; +import org.springframework.util.DefaultPropertiesPersister; +import org.springframework.util.PropertiesPersister; +import org.springframework.util.StringUtils; + +/** + * {@link ExpressionSource} implementation that accesses resource bundles using specified basenames. + * This class uses {@link java.util.Properties} instances as its custom data structure for expressions, + * loading them via a {@link org.springframework.util.PropertiesPersister} strategy: The default + * strategy is capable of loading properties files with a specific character encoding, if desired. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @since 2.0 + * @see #setCacheSeconds + * @see #setBasenames + * @see #setDefaultEncoding + * @see #setFileEncodings + * @see #setPropertiesPersister + * @see #setResourceLoader + * @see org.springframework.util.DefaultPropertiesPersister + * @see org.springframework.core.io.DefaultResourceLoader + * @see java.util.ResourceBundle + */ +public class ReloadableResourceBundleExpressionSource implements ExpressionSource, ResourceLoaderAware { + + private static final String PROPERTIES_SUFFIX = ".properties"; + + private static final String XML_SUFFIX = ".xml"; + + private static final Log logger = LogFactory.getLog(ReloadableResourceBundleExpressionSource.class); + + + private volatile String[] basenames = new String[0]; + + private volatile String defaultEncoding; + + private volatile Properties fileEncodings; + + private volatile boolean fallbackToSystemLocale = true; + + private volatile long cacheMillis = -1; + + private volatile PropertiesPersister propertiesPersister = new DefaultPropertiesPersister(); + + private volatile ResourceLoader resourceLoader = new DefaultResourceLoader(); + + /** Cache to hold filename lists per Locale */ + private final Map>> cachedFilenames = + new HashMap>>(); + + /** Cache to hold already loaded properties per filename */ + private final Map cachedProperties = new HashMap(); + + /** Cache to hold merged loaded properties per locale */ + private final Map cachedMergedProperties = new HashMap(); + + private final ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + + + /** + * Set a single basename, following the basic ResourceBundle convention of + * not specifying file extension or language codes, but referring to a Spring + * resource location: e.g. "META-INF/expressions" for "META-INF/expressions.properties", + * "META-INF/expressions_en.properties", etc. + *

XML properties files are also supported: .g. "META-INF/expressions" will find + * and load "META-INF/expressions.xml", "META-INF/expressions_en.xml", etc as well. + * @param basename the single basename + * @see #setBasenames + * @see org.springframework.core.io.ResourceEditor + * @see java.util.ResourceBundle + */ + public void setBasename(String basename) { + setBasenames(new String[] {basename}); + } + + /** + * Set an array of basenames, each following the basic ResourceBundle convention + * of not specifying file extension or language codes, but referring to a Spring + * resource location: e.g. "META-INF/expressions" for "META-INF/expressions.properties", + * "META-INF/expressions_en.properties", etc. + *

XML properties files are also supported: .g. "META-INF/expressions" will find + * and load "META-INF/expressions.xml", "META-INF/expressions_en.xml", etc as well. + *

The associated resource bundles will be checked sequentially when resolving + * an expression key. Note that expression definitions in a previous resource + * bundle will override ones in a later bundle, due to the sequential lookup. + * @param basenames an array of basenames + * @see #setBasename + * @see java.util.ResourceBundle + */ + public void setBasenames(String[] basenames) { + if (basenames != null) { + this.basenames = new String[basenames.length]; + for (int i = 0; i < basenames.length; i++) { + String basename = basenames[i]; + Assert.hasText(basename, "Basename must not be empty"); + this.basenames[i] = basename.trim(); + } + } + else { + this.basenames = new String[0]; + } + } + + /** + * Set the default charset to use for parsing properties files. + * Used if no file-specific charset is specified for a file. + *

Default is none, using the java.util.Properties + * default encoding. + *

Only applies to classic properties files, not to XML files. + * @param defaultEncoding the default charset + * @see #setFileEncodings + * @see org.springframework.util.PropertiesPersister#load + */ + public void setDefaultEncoding(String defaultEncoding) { + this.defaultEncoding = defaultEncoding; + } + + /** + * Set per-file charsets to use for parsing properties files. + *

Only applies to classic properties files, not to XML files. + * @param fileEncodings Properties with filenames as keys and charset + * names as values. Filenames have to match the basename syntax, + * with optional locale-specific appendices: e.g. "META-INF/expressions" + * or "META-INF/expressions_en". + * @see #setBasenames + * @see org.springframework.util.PropertiesPersister#load + */ + public void setFileEncodings(Properties fileEncodings) { + this.fileEncodings = fileEncodings; + } + + /** + * Set whether to fall back to the system Locale if no files for a specific + * Locale have been found. Default is "true"; if this is turned off, the only + * fallback will be the default file (e.g. "expressions.properties" for + * basename "expressions"). + *

Falling back to the system Locale is the default behavior of + * java.util.ResourceBundle. However, this is often not + * desirable in an application server environment, where the system Locale + * is not relevant to the application at all: Set this flag to "false" + * in such a scenario. + */ + public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) { + this.fallbackToSystemLocale = fallbackToSystemLocale; + } + + /** + * Set the number of seconds to cache loaded properties files. + *

    + *
  • Default is "-1", indicating to cache forever (just like + * java.util.ResourceBundle). + *
  • A positive number will cache loaded properties files for the given + * number of seconds. This is essentially the interval between refresh checks. + * Note that a refresh attempt will first check the last-modified timestamp + * of the file before actually reloading it; so if files don't change, this + * interval can be set rather low, as refresh attempts will not actually reload. + *
  • A value of "0" will check the last-modified timestamp of the file on + * every expression access. Do not use this in a production environment! + *
+ */ + public void setCacheSeconds(int cacheSeconds) { + this.cacheMillis = (cacheSeconds * 1000); + } + + /** + * Set the PropertiesPersister to use for parsing properties files. + *

The default is a DefaultPropertiesPersister. + * @see org.springframework.util.DefaultPropertiesPersister + */ + public void setPropertiesPersister(PropertiesPersister propertiesPersister) { + this.propertiesPersister = + (propertiesPersister != null ? propertiesPersister : new DefaultPropertiesPersister()); + } + + /** + * Set the ResourceLoader to use for loading bundle properties files. + *

The default is a DefaultResourceLoader. Will get overridden by the + * ApplicationContext if running in a context, as it implements the + * ResourceLoaderAware interface. Can be manually overridden when + * running outside of an ApplicationContext. + * @see org.springframework.core.io.DefaultResourceLoader + * @see org.springframework.context.ResourceLoaderAware + */ + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader()); + } + + + /** + * Resolves the given key in the retrieved bundle files to an Expression. + */ + public Expression getExpression(String key, Locale locale) { + String expressionString = this.getExpressionString(key, locale); + if (expressionString != null) { + return this.parser.parseExpression(expressionString); + } + return null; + } + + private String getExpressionString(String key, Locale locale) { + if (this.cacheMillis < 0) { + PropertiesHolder propHolder = getMergedProperties(locale); + String result = propHolder.getProperty(key); + if (result != null) { + return result; + } + } + else { + for (String basename : this.basenames) { + List filenames = calculateAllFilenames(basename, locale); + for (String filename : filenames) { + PropertiesHolder propHolder = getProperties(filename); + String result = propHolder.getProperty(key); + if (result != null) { + return result; + } + } + } + } + return null; + } + + /** + * Get a PropertiesHolder that contains the actually visible properties + * for a Locale, after merging all specified resource bundles. + * Either fetches the holder from the cache or freshly loads it. + *

Only used when caching resource bundle contents forever, i.e. + * with cacheSeconds < 0. Therefore, merged properties are always + * cached forever. + */ + private PropertiesHolder getMergedProperties(Locale locale) { + synchronized (this.cachedMergedProperties) { + PropertiesHolder mergedHolder = this.cachedMergedProperties.get(locale); + if (mergedHolder != null) { + return mergedHolder; + } + Properties mergedProps = new Properties(); + mergedHolder = new PropertiesHolder(mergedProps, -1); + for (int i = this.basenames.length - 1; i >= 0; i--) { + List filenames = calculateAllFilenames(this.basenames[i], locale); + for (int j = filenames.size() - 1; j >= 0; j--) { + String filename = filenames.get(j); + PropertiesHolder propHolder = getProperties(filename); + if (propHolder.getProperties() != null) { + mergedProps.putAll(propHolder.getProperties()); + } + } + } + this.cachedMergedProperties.put(locale, mergedHolder); + return mergedHolder; + } + } + + /** + * Calculate all filenames for the given bundle basename and Locale. + * Will calculate filenames for the given Locale, the system Locale + * (if applicable), and the default file. + * @param basename the basename of the bundle + * @param locale the locale + * @return the List of filenames to check + * @see #setFallbackToSystemLocale + * @see #calculateFilenamesForLocale + */ + private List calculateAllFilenames(String basename, Locale locale) { + synchronized (this.cachedFilenames) { + Map> localeMap = this.cachedFilenames.get(basename); + if (localeMap != null) { + List filenames = localeMap.get(locale); + if (filenames != null) { + return filenames; + } + } + List filenames = new ArrayList(7); + filenames.addAll(calculateFilenamesForLocale(basename, locale)); + if (this.fallbackToSystemLocale && !locale.equals(Locale.getDefault())) { + List fallbackFilenames = calculateFilenamesForLocale(basename, Locale.getDefault()); + for (String fallbackFilename : fallbackFilenames) { + if (!filenames.contains(fallbackFilename)) { + // Entry for fallback locale that isn't already in filenames list. + filenames.add(fallbackFilename); + } + } + } + filenames.add(basename); + if (localeMap != null) { + localeMap.put(locale, filenames); + } + else { + localeMap = new HashMap>(); + localeMap.put(locale, filenames); + this.cachedFilenames.put(basename, localeMap); + } + return filenames; + } + } + + /** + * Calculate the filenames for the given bundle basename and Locale, + * appending language code, country code, and variant code. + * E.g.: basename "expressions", Locale "de_AT_oo" -> "expressions_de_AT_OO", + * "expressions_de_AT", "expressions_de". + *

Follows the rules defined by {@link java.util.Locale#toString()}. + * @param basename the basename of the bundle + * @param locale the locale + * @return the List of filenames to check + */ + private List calculateFilenamesForLocale(String basename, Locale locale) { + List result = new ArrayList(3); + String language = locale.getLanguage(); + String country = locale.getCountry(); + String variant = locale.getVariant(); + StringBuilder temp = new StringBuilder(basename); + + temp.append('_'); + if (language.length() > 0) { + temp.append(language); + result.add(0, temp.toString()); + } + + temp.append('_'); + if (country.length() > 0) { + temp.append(country); + result.add(0, temp.toString()); + } + + if (variant.length() > 0 && (language.length() > 0 || country.length() > 0)) { + temp.append('_').append(variant); + result.add(0, temp.toString()); + } + + return result; + } + + + /** + * Get a PropertiesHolder for the given filename, either from the + * cache or freshly loaded. + * @param filename the bundle filename (basename + Locale) + * @return the current PropertiesHolder for the bundle + */ + private PropertiesHolder getProperties(String filename) { + synchronized (this.cachedProperties) { + PropertiesHolder propHolder = this.cachedProperties.get(filename); + if (propHolder != null && + (propHolder.getRefreshTimestamp() < 0 || + propHolder.getRefreshTimestamp() > System.currentTimeMillis() - this.cacheMillis)) { + return propHolder; + } + return refreshProperties(filename, propHolder); + } + } + + /** + * Refresh the PropertiesHolder for the given bundle filename. + * The holder can be null if not cached before, or a timed-out cache entry + * (potentially getting re-validated against the current last-modified timestamp). + * @param filename the bundle filename (basename + Locale) + * @param propHolder the current PropertiesHolder for the bundle + */ + private PropertiesHolder refreshProperties(String filename, PropertiesHolder propHolder) { + long refreshTimestamp = (this.cacheMillis < 0) ? -1 : System.currentTimeMillis(); + + Resource resource = this.resourceLoader.getResource(filename + PROPERTIES_SUFFIX); + if (!resource.exists()) { + resource = this.resourceLoader.getResource(filename + XML_SUFFIX); + } + + if (resource.exists()) { + long fileTimestamp = -1; + if (this.cacheMillis >= 0) { + // Last-modified timestamp of file will just be read if caching with timeout. + try { + fileTimestamp = resource.lastModified(); + if (propHolder != null && propHolder.getFileTimestamp() == fileTimestamp) { + if (logger.isDebugEnabled()) { + logger.debug("Re-caching properties for filename [" + filename + "] - file hasn't been modified"); + } + propHolder.setRefreshTimestamp(refreshTimestamp); + return propHolder; + } + } + catch (IOException ex) { + // Probably a class path resource: cache it forever. + if (logger.isDebugEnabled()) { + logger.debug( + resource + " could not be resolved in the file system - assuming that is hasn't changed", ex); + } + fileTimestamp = -1; + } + } + try { + Properties props = loadProperties(resource, filename); + propHolder = new PropertiesHolder(props, fileTimestamp); + } + catch (IOException ex) { + if (logger.isWarnEnabled()) { + logger.warn("Could not parse properties file [" + resource.getFilename() + "]", ex); + } + // Empty holder representing "not valid". + propHolder = new PropertiesHolder(); + } + } + + else { + // Resource does not exist. + if (logger.isDebugEnabled()) { + logger.debug("No properties file found for [" + filename + "] - neither plain properties nor XML"); + } + // Empty holder representing "not found". + propHolder = new PropertiesHolder(); + } + + propHolder.setRefreshTimestamp(refreshTimestamp); + this.cachedProperties.put(filename, propHolder); + return propHolder; + } + + /** + * Load the properties from the given resource. + * @param resource the resource to load from + * @param filename the original bundle filename (basename + Locale) + * @return the populated Properties instance + * @throws IOException if properties loading failed + */ + private Properties loadProperties(Resource resource, String filename) throws IOException { + InputStream is = resource.getInputStream(); + Properties props = new Properties(); + try { + if (resource.getFilename().endsWith(XML_SUFFIX)) { + if (logger.isDebugEnabled()) { + logger.debug("Loading properties [" + resource.getFilename() + "]"); + } + this.propertiesPersister.loadFromXml(props, is); + } + else { + String encoding = null; + if (this.fileEncodings != null) { + encoding = this.fileEncodings.getProperty(filename); + } + if (encoding == null) { + encoding = this.defaultEncoding; + } + if (encoding != null) { + if (logger.isDebugEnabled()) { + logger.debug("Loading properties [" + resource.getFilename() + "] with encoding '" + encoding + "'"); + } + this.propertiesPersister.load(props, new InputStreamReader(is, encoding)); + } + else { + if (logger.isDebugEnabled()) { + logger.debug("Loading properties [" + resource.getFilename() + "]"); + } + this.propertiesPersister.load(props, is); + } + } + return props; + } + finally { + is.close(); + } + } + + + /** + * Clear the resource bundle cache. + * Subsequent resolve calls will lead to reloading of the properties files. + */ + public void clearCache() { + logger.debug("Clearing entire resource bundle cache"); + synchronized (this.cachedProperties) { + this.cachedProperties.clear(); + } + synchronized (this.cachedMergedProperties) { + this.cachedMergedProperties.clear(); + } + } + + @Override + public String toString() { + return getClass().getName() + ": basenames=[" + StringUtils.arrayToCommaDelimitedString(this.basenames) + "]"; + } + + + /** + * PropertiesHolder for caching. + * Stores the last-modified timestamp of the source file for efficient + * change detection, and the timestamp of the last refresh attempt + * (updated every time the cache entry gets re-validated). + */ + private class PropertiesHolder { + + private Properties properties; + + private long fileTimestamp = -1; + + private long refreshTimestamp = -1; + + + public PropertiesHolder(Properties properties, long fileTimestamp) { + this.properties = properties; + this.fileTimestamp = fileTimestamp; + } + + public PropertiesHolder() { + } + + public Properties getProperties() { + return properties; + } + + public long getFileTimestamp() { + return fileTimestamp; + } + + public void setRefreshTimestamp(long refreshTimestamp) { + this.refreshTimestamp = refreshTimestamp; + } + + public long getRefreshTimestamp() { + return refreshTimestamp; + } + + public String getProperty(String code) { + if (this.properties == null) { + return null; + } + return this.properties.getProperty(code); + } + + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/filter/ExpressionEvaluatingSelector.java b/spring-integration-core/src/main/java/org/springframework/integration/filter/ExpressionEvaluatingSelector.java index 5b13de83b7..e1ab8a6a08 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/filter/ExpressionEvaluatingSelector.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/filter/ExpressionEvaluatingSelector.java @@ -16,6 +16,10 @@ package org.springframework.integration.filter; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.core.MessageSelector; import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; @@ -28,7 +32,14 @@ import org.springframework.integration.handler.ExpressionEvaluatingMessageProces */ public class ExpressionEvaluatingSelector extends AbstractMessageProcessingSelector { - public ExpressionEvaluatingSelector(String expression) { + private static final ExpressionParser expressionParser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + + + public ExpressionEvaluatingSelector(String expressionString) { + super(new ExpressionEvaluatingMessageProcessor(expressionParser.parseExpression(expressionString), Boolean.class)); + } + + public ExpressionEvaluatingSelector(Expression expression) { super(new ExpressionEvaluatingMessageProcessor(expression, Boolean.class)); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java b/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java index 300dd8f71a..d81fc8137f 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/filter/MessageFilter.java @@ -100,14 +100,24 @@ public class MessageFilter extends AbstractReplyProducingMessageHandler { @Override protected Object handleRequestMessage(Message message) { - if (this.selector.accept(message)) { - return message; - } + Throwable filterException = null; + try { + if (this.selector.accept(message)) { + return message; + } + } catch (Exception e) { + filterException = e; + } if (this.discardChannel != null) { this.getMessagingTemplate().send(this.discardChannel, message); } if (this.throwExceptionOnRejection) { - throw new MessageRejectedException(message); + if (filterException != null){ + throw new MessageRejectedException(message, "MessageFilter '" + this.getComponentName() + "' rejected Message", filterException); + } + else { + throw new MessageRejectedException(message, "MessageFilter '" + this.getComponentName() + "' rejected Message"); + } } return null; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java index 523dc46774..92a9096728 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/DelayHandler.java @@ -31,6 +31,7 @@ import org.springframework.integration.MessageHeaders; import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.context.IntegrationObjectSupport; import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.core.MessageProducer; import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.message.ErrorMessage; import org.springframework.integration.store.MessageStore; @@ -70,7 +71,7 @@ import org.springframework.util.Assert; * @author Mark Fisher * @since 1.0.3 */ -public class DelayHandler extends IntegrationObjectSupport implements MessageHandler, Ordered, DisposableBean { +public class DelayHandler extends IntegrationObjectSupport implements MessageHandler, MessageProducer, Ordered, DisposableBean { private final Log logger = LogFactory.getLog(this.getClass()); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessor.java index cf85907536..f40ad5a76d 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessor.java @@ -18,10 +18,7 @@ package org.springframework.integration.handler; import org.springframework.context.expression.MapAccessor; import org.springframework.expression.Expression; -import org.springframework.expression.ExpressionParser; import org.springframework.expression.ParseException; -import org.springframework.expression.spel.SpelParserConfiguration; -import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.Message; import org.springframework.util.Assert; @@ -34,25 +31,27 @@ import org.springframework.util.Assert; */ public class ExpressionEvaluatingMessageProcessor extends AbstractMessageProcessor { - private final ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); - private final Expression expression; private final Class expectedType; - public ExpressionEvaluatingMessageProcessor(String expression) { + /** + * Create an {@link ExpressionEvaluatingMessageProcessor} for the given expression. + */ + public ExpressionEvaluatingMessageProcessor(Expression expression) { this(expression, null); } /** - * Create an {@link ExpressionEvaluatingMessageProcessor} for the given expression String. + * Create an {@link ExpressionEvaluatingMessageProcessor} for the given expression + * and expected type for its evaluation result. */ - public ExpressionEvaluatingMessageProcessor(String expression, Class expectedType) { - Assert.hasLength(expression, "The expression must be non empty"); + public ExpressionEvaluatingMessageProcessor(Expression expression, Class expectedType) { + Assert.notNull(expression, "The expression must not be null"); try { - this.expression = parser.parseExpression(expression); + this.expression = expression; this.getEvaluationContext().addPropertyAccessor(new MapAccessor()); this.expectedType = expectedType; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/LoggingHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/LoggingHandler.java index 433e9d620e..14e1aa18df 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/LoggingHandler.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/LoggingHandler.java @@ -1,23 +1,21 @@ /* * Copyright 2002-2010 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. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. */ package org.springframework.integration.handler; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.List; import org.springframework.context.expression.MapAccessor; import org.springframework.expression.EvaluationContext; @@ -25,20 +23,22 @@ import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.integration.Message; +import org.springframework.integration.dispatcher.AggregateMessageDeliveryException; import org.springframework.util.StringUtils; /** - * MessageHandler implementation that simply logs the Message or its payload - * depending on the value of the 'shouldLogFullMessage' property. If logging - * the payload, and it is assignable to Throwable, it will log the stack trace. - * By default, it will log the payload only. + * MessageHandler implementation that simply logs the Message or its payload depending on the value of the + * 'shouldLogFullMessage' property. If logging the payload, and it is assignable to Throwable, it will log the stack + * trace. By default, it will log the payload only. * * @author Mark Fisher * @since 1.0.1 */ public class LoggingHandler extends AbstractMessageHandler { - private static enum Level { FATAL, ERROR, WARN, INFO, DEBUG, TRACE } + private static enum Level { + FATAL, ERROR, WARN, INFO, DEBUG, TRACE + } private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); @@ -48,18 +48,18 @@ public class LoggingHandler extends AbstractMessageHandler { private final EvaluationContext evaluationContext; - /** * Create a LoggingHandler with the given log level (case-insensitive). - *

The valid levels are: FATAL, ERROR, WARN, INFO, DEBUG, or TRACE + *

+ * The valid levels are: FATAL, ERROR, WARN, INFO, DEBUG, or TRACE */ public LoggingHandler(String level) { try { this.level = Level.valueOf(level.toUpperCase()); - } - catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Invalid log level '" + level + - "'. The (case-insensitive) supported values are: " + StringUtils.arrayToCommaDelimitedString(Level.values())); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid log level '" + level + + "'. The (case-insensitive) supported values are: " + + StringUtils.arrayToCommaDelimitedString(Level.values())); } StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); evaluationContext.addPropertyAccessor(new MapAccessor()); @@ -67,18 +67,17 @@ public class LoggingHandler extends AbstractMessageHandler { this.expression = EXPRESSION_PARSER.parseExpression("payload"); } - public void setExpression(String expressionString) { this.expression = EXPRESSION_PARSER.parseExpression(expressionString); } /** - * Specify whether to log the full Message. Otherwise, only the payload - * will be logged. This value is false by default. + * Specify whether to log the full Message. Otherwise, only the payload will be logged. This value is + * false by default. */ public void setShouldLogFullMessage(boolean shouldLogFullMessage) { - this.expression = (shouldLogFullMessage) ? EXPRESSION_PARSER.parseExpression("#root") - : EXPRESSION_PARSER.parseExpression("payload"); + this.expression = (shouldLogFullMessage) ? EXPRESSION_PARSER.parseExpression("#root") : EXPRESSION_PARSER + .parseExpression("payload"); } @Override @@ -91,40 +90,47 @@ public class LoggingHandler extends AbstractMessageHandler { Object logMessage = this.expression.getValue(this.evaluationContext, message); if (logMessage instanceof Throwable) { StringWriter stringWriter = new StringWriter(); - ((Throwable) logMessage).printStackTrace(new PrintWriter(stringWriter, true)); + if (logMessage instanceof AggregateMessageDeliveryException) { + stringWriter.append(((Throwable) logMessage).getMessage()); + for (Exception exception : (List) ((AggregateMessageDeliveryException)logMessage).getAggregatedExceptions()) { + exception.printStackTrace(new PrintWriter(stringWriter, true)); + } + } else { + ((Throwable) logMessage).printStackTrace(new PrintWriter(stringWriter, true)); + } logMessage = stringWriter.toString(); } switch (this.level) { - case FATAL : - if (logger.isFatalEnabled()) { - logger.fatal(logMessage); - } - break; - case ERROR : - if (logger.isErrorEnabled()) { - logger.error(logMessage); - } - break; - case WARN : - if (logger.isWarnEnabled()) { - logger.warn(logMessage); - } - break; - case INFO : - if (logger.isInfoEnabled()) { - logger.info(logMessage); - } - break; - case DEBUG : - if (logger.isDebugEnabled()) { - logger.debug(logMessage); - } - break; - case TRACE : - if (logger.isTraceEnabled()) { - logger.trace(logMessage); - } - break; + case FATAL: + if (logger.isFatalEnabled()) { + logger.fatal(logMessage); + } + break; + case ERROR: + if (logger.isErrorEnabled()) { + logger.error(logMessage); + } + break; + case WARN: + if (logger.isWarnEnabled()) { + logger.warn(logMessage); + } + break; + case INFO: + if (logger.isInfoEnabled()) { + logger.info(logMessage); + } + break; + case DEBUG: + if (logger.isDebugEnabled()) { + logger.debug(logMessage); + } + break; + case TRACE: + if (logger.isTraceEnabled()) { + logger.trace(logMessage); + } + break; } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractChannelNameResolvingMessageRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractChannelNameResolvingMessageRouter.java deleted file mode 100644 index 259c32aa84..0000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractChannelNameResolvingMessageRouter.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2002-2010 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.router; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.support.ConversionServiceFactory; -import org.springframework.integration.Message; -import org.springframework.integration.MessageChannel; -import org.springframework.integration.MessagingException; -import org.springframework.integration.support.channel.BeanFactoryChannelResolver; -import org.springframework.integration.support.channel.ChannelResolutionException; -import org.springframework.integration.support.channel.ChannelResolver; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * A base class for router implementations that return only the channel name(s) - * rather than {@link MessageChannel} instances. - * - * @author Mark Fisher - * @author Jonas Partner - */ -public abstract class AbstractChannelNameResolvingMessageRouter extends AbstractMessageRouter { - - private volatile String prefix; - - private volatile String suffix; - - private volatile ChannelResolver channelResolver; - - private volatile boolean ignoreChannelNameResolutionFailures; - - - /** - * Specify the {@link ChannelResolver} strategy to use. - * The default is a BeanFactoryChannelResolver. - */ - public void setChannelResolver(ChannelResolver channelResolver) { - Assert.notNull(channelResolver, "'channelResolver' must not be null"); - this.channelResolver = channelResolver; - } - - /** - * Specify a prefix to be added to each channel name prior to resolution. - */ - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - /** - * Specify a suffix to be added to each channel name prior to resolution. - */ - public void setSuffix(String suffix) { - this.suffix = suffix; - } - - /** - * Specify whether this router should ignore any failure to resolve a channel name to - * an actual MessageChannel instance when delegating to the ChannelResolver strategy. - */ - public void setIgnoreChannelNameResolutionFailures(boolean ignoreChannelNameResolutionFailures) { - this.ignoreChannelNameResolutionFailures = ignoreChannelNameResolutionFailures; - } - - @Override - public void onInit() { - BeanFactory beanFactory = this.getBeanFactory(); - if (this.channelResolver == null && beanFactory != null) { - this.channelResolver = new BeanFactoryChannelResolver(beanFactory); - } - } - - private MessageChannel resolveChannelForName(String channelName, Message message) { - Assert.state(this.channelResolver != null, - "unable to resolve channel names, no ChannelResolver available"); - MessageChannel channel = null; - try { - channel = this.channelResolver.resolveChannelName(channelName); - } - catch (ChannelResolutionException e) { - if (!this.ignoreChannelNameResolutionFailures) { - throw new MessagingException(message, - "failed to resolve channel name '" + channelName + "'", e); - } - } - if (channel == null && !this.ignoreChannelNameResolutionFailures) { - throw new MessagingException(message, - "failed to resolve channel name '" + channelName + "'"); - } - return channel; - } - - @Override - protected Collection determineTargetChannels(Message message) { - this.afterPropertiesSet(); - Collection channels = new ArrayList(); - Collection channelsReturned = this.getChannelIndicatorList(message); - addToCollection(channels, channelsReturned, message); - return channels; - } - - @SuppressWarnings("unchecked") - private void addToCollection(Collection channels, Collection channelIndicators, Message message) { - if (channelIndicators == null) { - return; - } - for (Object channelIndicator : channelIndicators) { - if (channelIndicator == null) { - continue; - } - else if (channelIndicator instanceof MessageChannel) { - channels.add((MessageChannel) channelIndicator); - } - else if (channelIndicator instanceof MessageChannel[]) { - channels.addAll(Arrays.asList((MessageChannel[]) channelIndicator)); - } - else if (channelIndicator instanceof String) { - addChannelFromString(channels, (String) channelIndicator, message); - } - else if (channelIndicator instanceof String[]) { - for (String indicatorName : (String[]) channelIndicator) { - addChannelFromString(channels, indicatorName, message); - } - } - else if (channelIndicator instanceof Collection) { - addToCollection(channels, (Collection) channelIndicator, message); - } - else if (this.getRequiredConversionService().canConvert(channelIndicator.getClass(), String.class)) { - addChannelFromString(channels, - this.getConversionService().convert(channelIndicator, String.class), message); - } - else { - throw new MessagingException( - "unsupported return type for router [" + channelIndicator.getClass() + "]"); - } - } - } - - private void addChannelFromString(Collection channels, String channelName, Message message) { - if (channelName.indexOf(',') != -1) { - for (String name : StringUtils.commaDelimitedListToStringArray(channelName)) { - addChannelFromString(channels, name, message); - } - return; - } - if (this.prefix != null) { - channelName = this.prefix + channelName; - } - if (this.suffix != null) { - channelName = channelName + suffix; - } - MessageChannel channel = resolveChannelForName(channelName, message); - if (channel != null) { - channels.add(channel); - } - } - - private ConversionService getRequiredConversionService() { - if (this.getConversionService() == null) { - this.setConversionService(ConversionServiceFactory.createDefaultConversionService()); - } - return this.getConversionService(); - } - - /** - * Subclasses must implement this method to return the channel indicators. - */ - protected abstract List getChannelIndicatorList(Message message); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractMessageProcessingRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractMessageProcessingRouter.java index 0e51c3d3b8..1e4d2611aa 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractMessageProcessingRouter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractMessageProcessingRouter.java @@ -32,7 +32,7 @@ import org.springframework.util.Assert; * @author Mark Fisher * @since 2.0 */ -class AbstractMessageProcessingRouter extends AbstractChannelNameResolvingMessageRouter { +class AbstractMessageProcessingRouter extends AbstractMessageRouter { private final MessageProcessor messageProcessor; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractMessageRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractMessageRouter.java index c314d4c149..b91370bdc0 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractMessageRouter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractMessageRouter.java @@ -16,8 +16,16 @@ package org.springframework.integration.router; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.ConversionServiceFactory; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.MessageDeliveryException; @@ -25,11 +33,18 @@ import org.springframework.integration.MessagingException; import org.springframework.integration.core.MessagingTemplate; import org.springframework.integration.handler.AbstractMessageHandler; import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.support.channel.BeanFactoryChannelResolver; +import org.springframework.integration.support.channel.ChannelResolutionException; +import org.springframework.integration.support.channel.ChannelResolver; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; /** - * Base class for Message Routers. + * Base class for all Message Routers. * * @author Mark Fisher + * @author Oleg Zhurakousky */ public abstract class AbstractMessageRouter extends AbstractMessageHandler { @@ -42,6 +57,67 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler { private volatile boolean applySequence; private final MessagingTemplate messagingTemplate = new MessagingTemplate(); + + private volatile String prefix; + + private volatile String suffix; + + private volatile ChannelResolver channelResolver; + + private volatile boolean ignoreChannelNameResolutionFailures; + + protected volatile Map channelIdentifierMap = new ConcurrentHashMap(); + + /** + * Specify the {@link ChannelResolver} strategy to use. + * The default is a BeanFactoryChannelResolver. + */ + public void setChannelResolver(ChannelResolver channelResolver) { + Assert.notNull(channelResolver, "'channelResolver' must not be null"); + this.channelResolver = channelResolver; + } + + /** + * Specify a prefix to be added to each channel name prior to resolution. + */ + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + /** + * Specify a suffix to be added to each channel name prior to resolution. + */ + public void setSuffix(String suffix) { + this.suffix = suffix; + } + + /** + * Specify whether this router should ignore any failure to resolve a channel name to + * an actual MessageChannel instance when delegating to the ChannelResolver strategy. + */ + public void setIgnoreChannelNameResolutionFailures(boolean ignoreChannelNameResolutionFailures) { + this.ignoreChannelNameResolutionFailures = ignoreChannelNameResolutionFailures; + } + /** + * Allows you to set the map which will map channel identifiers to channel names. + * Channel names will be resolve via {@link ChannelResolver} + * @param channelIdentifierMap + */ + public void setChannelIdentifierMap(Map channelIdentifierMap) { + this.channelIdentifierMap.clear(); + this.channelIdentifierMap.putAll(channelIdentifierMap); + } + + public void setChannelMapping(String channelIdentifier, String channelName){ + this.channelIdentifierMap.put(channelIdentifier, channelName); + } + /** + * Removes channel mapping for a give channel identifier + * @param channelIdentifier + */ + public void removeChannelMapping(String channelIdentifier){ + this.channelIdentifierMap.remove(channelIdentifier); + } /** * Set the default channel where Messages should be sent if channel resolution fails to return any channels. If no @@ -99,6 +175,33 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler { protected MessagingTemplate getMessagingTemplate() { return this.messagingTemplate; } + + @Override + public void onInit() { + BeanFactory beanFactory = this.getBeanFactory(); + if (this.channelResolver == null && beanFactory != null) { + this.channelResolver = new BeanFactoryChannelResolver(beanFactory); + } + } + + protected Collection determineTargetChannels(Message message) { + this.afterPropertiesSet(); + Collection channels = new ArrayList(); + Collection channelsReturned = this.getChannelIndicatorList(message); + addToCollection(channels, channelsReturned, message); + return channels; + } + + protected ConversionService getRequiredConversionService() { + if (this.getConversionService() == null) { + this.setConversionService(ConversionServiceFactory.createDefaultConversionService()); + } + return this.getConversionService(); + } + /** + * Subclasses must implement this method to return the channel indicators. + */ + protected abstract List getChannelIndicatorList(Message message); @Override protected void handleMessageInternal(Message message) { @@ -137,9 +240,89 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler { } } - /** - * Subclasses must implement this method to return the target channels for a given Message. - */ - protected abstract Collection determineTargetChannels(Message message); + private MessageChannel resolveChannelForName(String channelName, Message message) { + Assert.state(this.channelResolver != null, + "unable to resolve channel names, no ChannelResolver available"); + MessageChannel channel = null; + try { + channel = this.channelResolver.resolveChannelName(channelName); + } + catch (ChannelResolutionException e) { + if (!this.ignoreChannelNameResolutionFailures) { + throw new MessagingException(message, + "failed to resolve channel name '" + channelName + "'", e); + } + } + if (channel == null && !this.ignoreChannelNameResolutionFailures) { + throw new MessagingException(message, + "failed to resolve channel name '" + channelName + "'"); + } + return channel; + } + + private void addChannelFromString(Collection channels, String channelIdentifier, Message message) { + if (channelIdentifier.indexOf(',') != -1) { + for (String name : StringUtils.commaDelimitedListToStringArray(channelIdentifier)) { + addChannelFromString(channels, name, message); + } + return; + } + if (this.prefix != null) { + channelIdentifier = this.prefix + channelIdentifier; + } + if (this.suffix != null) { + channelIdentifier = channelIdentifier + suffix; + } + /* + * Some routers due to their complex nature will already resolve 'channelIdentifier' + * to 'channelName' (e.g., PTR, EMETR) + */ + String channelName = channelIdentifier; + if (!CollectionUtils.isEmpty(channelIdentifierMap) && channelIdentifierMap.containsKey(channelIdentifier)){ + channelName = channelIdentifierMap.get(channelIdentifier); + } + if (this.channelResolver != null){ + MessageChannel channel = resolveChannelForName(channelName, message); + if (channel != null) { + channels.add(channel); + } + } + } + + private void addToCollection(Collection channels, Collection channelIndicators, Message message) { + if (channelIndicators == null) { + return; + } + for (Object channelIndicator : channelIndicators) { + if (channelIndicator == null) { + continue; + } + else if (channelIndicator instanceof MessageChannel) { + channels.add((MessageChannel) channelIndicator); + } + else if (channelIndicator instanceof MessageChannel[]) { + channels.addAll(Arrays.asList((MessageChannel[]) channelIndicator)); + } + else if (channelIndicator instanceof String) { + addChannelFromString(channels, (String) channelIndicator, message); + } + else if (channelIndicator instanceof String[]) { + for (String indicatorName : (String[]) channelIndicator) { + addChannelFromString(channels, indicatorName, message); + } + } + else if (channelIndicator instanceof Collection) { + addToCollection(channels, (Collection) channelIndicator, message); + } + else if (this.getRequiredConversionService().canConvert(channelIndicator.getClass(), String.class)) { + addChannelFromString(channels, + this.getConversionService().convert(channelIndicator, String.class), message); + } + else { + throw new MessagingException( + "unsupported return type for router [" + channelIndicator.getClass() + "]"); + } + } + } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractSingleChannelNameRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractSingleChannelNameRouter.java index 9ba06ed75d..3923f88f0d 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractSingleChannelNameRouter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractSingleChannelNameRouter.java @@ -27,7 +27,7 @@ import org.springframework.integration.Message; * * @author Mark Fisher */ -public abstract class AbstractSingleChannelNameRouter extends AbstractChannelNameResolvingMessageRouter { +public abstract class AbstractSingleChannelNameRouter extends AbstractMessageRouter { @Override protected final List getChannelIndicatorList(Message message) { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractSingleChannelRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractSingleChannelRouter.java deleted file mode 100644 index a160d2fa20..0000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractSingleChannelRouter.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2002-2008 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.router; - -import java.util.Collection; -import java.util.Collections; - -import org.springframework.integration.Message; -import org.springframework.integration.MessageChannel; - -/** - * Extends {@link AbstractMessageRouter} to support router implementations that - * always return a single {@link MessageChannel} instance (or null). - * - * @author Mark Fisher - */ -public abstract class AbstractSingleChannelRouter extends AbstractMessageRouter { - - @Override - protected final Collection determineTargetChannels(Message message) { - MessageChannel channel = this.determineTargetChannel(message); - return (channel != null) ? Collections.singletonList(channel) : null; - } - - /** - * Subclasses must implement this method to return the target channel. - */ - protected abstract MessageChannel determineTargetChannel(Message message); - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/ErrorMessageExceptionTypeRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/ErrorMessageExceptionTypeRouter.java index d0c632e72d..1dcb2d051a 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/ErrorMessageExceptionTypeRouter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/ErrorMessageExceptionTypeRouter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2010 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,12 +16,11 @@ package org.springframework.integration.router; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Collections; +import java.util.List; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; -import org.springframework.util.Assert; /** * A Message Router that resolves the target {@link MessageChannel} for @@ -29,34 +28,26 @@ import org.springframework.util.Assert; * the most specific cause of the error for which a channel-mapping exists. * * @author Mark Fisher - */ -public class ErrorMessageExceptionTypeRouter extends AbstractSingleChannelRouter { - - private volatile Map, MessageChannel> exceptionTypeChannelMap = - new ConcurrentHashMap, MessageChannel>(); - - - public void setExceptionTypeChannelMap(Map, MessageChannel> exceptionTypeChannelMap) { - Assert.notNull(exceptionTypeChannelMap, "exceptionTypeChannelMap must not be null"); - this.exceptionTypeChannelMap = exceptionTypeChannelMap; - } - + * @author Oleg Zhurakousky + */ +public class ErrorMessageExceptionTypeRouter extends AbstractMessageRouter { @Override - protected MessageChannel determineTargetChannel(Message message) { - MessageChannel channel = null; + protected List getChannelIndicatorList(Message message) { + String channelName = null; + String channelIdentifier = null; Object payload = message.getPayload(); if (payload != null && (payload instanceof Throwable)) { Throwable mostSpecificCause = (Throwable) payload; while (mostSpecificCause != null) { - MessageChannel mappedChannel = this.exceptionTypeChannelMap.get(mostSpecificCause.getClass()); - if (mappedChannel != null) { - channel = mappedChannel; + channelIdentifier = mostSpecificCause.getClass().getName(); + if (channelIdentifierMap != null){ + String tempChannelName = channelIdentifierMap.get(channelIdentifier); + channelName = tempChannelName == null ? channelName : tempChannelName; } mostSpecificCause = mostSpecificCause.getCause(); } } - return channel; + return Collections.singletonList((Object)channelName); } - } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/ExpressionEvaluatingRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/ExpressionEvaluatingRouter.java index 86827fed6a..ec1810ef5a 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/ExpressionEvaluatingRouter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/ExpressionEvaluatingRouter.java @@ -16,6 +16,7 @@ package org.springframework.integration.router; +import org.springframework.expression.Expression; import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; /** @@ -28,7 +29,7 @@ import org.springframework.integration.handler.ExpressionEvaluatingMessageProces */ public class ExpressionEvaluatingRouter extends AbstractMessageProcessingRouter { - public ExpressionEvaluatingRouter(String expression) { + public ExpressionEvaluatingRouter(Expression expression) { super(new ExpressionEvaluatingMessageProcessor(expression)); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/HeaderValueRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/HeaderValueRouter.java index 0553a50fba..dc6652d5d2 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/HeaderValueRouter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/HeaderValueRouter.java @@ -30,7 +30,7 @@ import org.springframework.util.StringUtils; * @author Mark Fisher * @since 1.0.3 */ -public class HeaderValueRouter extends AbstractChannelNameResolvingMessageRouter { +public class HeaderValueRouter extends AbstractMessageRouter { private final String headerName; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/PayloadTypeRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/PayloadTypeRouter.java index d8221ec928..6951895fe7 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/PayloadTypeRouter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/PayloadTypeRouter.java @@ -16,39 +16,67 @@ package org.springframework.integration.router; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Collections; +import java.util.List; +import org.springframework.beans.factory.BeanFactory; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; -import org.springframework.integration.util.ClassUtils; +import org.springframework.integration.support.channel.BeanFactoryChannelResolver; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; /** * A Message Router that resolves the {@link MessageChannel} based on the * {@link Message Message's} payload type. * * @author Mark Fisher + * @author Oleg Zhurakousky */ -public class PayloadTypeRouter extends AbstractSingleChannelRouter { - - private volatile Map, MessageChannel> payloadTypeChannelMap = - new ConcurrentHashMap, MessageChannel>(); - - - public void setPayloadTypeChannelMap(Map, MessageChannel> payloadTypeChannelMap) { - Assert.notNull(payloadTypeChannelMap, "payloadTypeChannelMap must not be null"); - this.payloadTypeChannelMap = payloadTypeChannelMap; - } - +public class PayloadTypeRouter extends AbstractMessageRouter { + /** + * Will select the most appropriate channel name matching channel identifiers + * which are fully qualifies class name to type available while traversing payload type. + * To resolve ties and conflicts (e.g., Serializable and String) it will match: + * 1. Type name to channel identifier else... + * 2. Name of the subclass of the type to channel identifier elc... + * 3. Name of the Interface of the type to channel identifier while also + * preferring direct interface over in-direct subclass + * + */ @Override - protected MessageChannel determineTargetChannel(Message message) { - Class closestMatch = ClassUtils.findClosestMatch( - message.getPayload().getClass(), this.payloadTypeChannelMap.keySet(), true); - if (closestMatch != null) { - return this.payloadTypeChannelMap.get(closestMatch); + protected List getChannelIndicatorList(Message message) { + Class firstInterfaceMatch = null; + Class type = message.getPayload().getClass(); + + while (type != null && !CollectionUtils.isEmpty(channelIdentifierMap)) { + Class[] interfaces = type.getInterfaces(); + // first try to find a match amongst the interfaces and also check if there is more then one + for (Class interfase : interfaces) { + if (channelIdentifierMap.containsKey(interfase.getName())){ + if (firstInterfaceMatch != null){ + throw new IllegalStateException("Unresolvable ambiguity while attempting to find closest match for [" + + type.getName() + "]. Candidate types [" + firstInterfaceMatch.getName() + "] and [" + interfase.getName() + + "] have equal weight."); + } + else { + firstInterfaceMatch = interfase; + } + } + } + // the actual type should favor the possible interface match + String channelName = channelIdentifierMap.get(type.getName()); + if (!StringUtils.hasText(channelName)){ + if (firstInterfaceMatch != null){ + return Collections.singletonList((Object)channelIdentifierMap.get(firstInterfaceMatch.getName())); + } + } + else { + return Collections.singletonList((Object)channelName); + } + type = type.getSuperclass(); } return null; } - } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouter.java index 749afddea9..ee0ea2cdf1 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/RecipientListRouter.java @@ -17,8 +17,8 @@ package org.springframework.integration.router; import java.util.ArrayList; -import java.util.Collection; import java.util.List; + import org.springframework.beans.factory.InitializingBean; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; @@ -51,6 +51,7 @@ import org.springframework.util.Assert; * solution. * * @author Mark Fisher + * @author Oleg Zhurakousky */ public class RecipientListRouter extends AbstractMessageRouter implements InitializingBean { @@ -88,10 +89,10 @@ public class RecipientListRouter extends AbstractMessageRouter implements Initia public final void onInit() { Assert.notEmpty(this.recipients, "a non-empty recipient list is required"); } - - @Override - protected Collection determineTargetChannels(Message message) { - List channels = new ArrayList(); + + @Override + protected List getChannelIndicatorList(Message message) { + List channels = new ArrayList(); List recipientList = this.recipients; for (Recipient recipient : recipientList) { if (recipient.accept(message)) { @@ -100,8 +101,7 @@ public class RecipientListRouter extends AbstractMessageRouter implements Initia } return channels; } - - + public static class Recipient { private final MessageChannel channel; @@ -125,5 +125,4 @@ public class RecipientListRouter extends AbstractMessageRouter implements Initia return this.channel; } } - } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/scheduling/PollerMetadata.java b/spring-integration-core/src/main/java/org/springframework/integration/scheduling/PollerMetadata.java index 45f93ebf78..1084c49c9d 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/scheduling/PollerMetadata.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/scheduling/PollerMetadata.java @@ -17,9 +17,9 @@ package org.springframework.integration.scheduling; import java.util.List; +import java.util.concurrent.Executor; import org.aopalliance.aop.Advice; -import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.Trigger; import org.springframework.scheduling.support.PeriodicTrigger; @@ -39,7 +39,7 @@ public class PollerMetadata { private List adviceChain; - private volatile TaskExecutor taskExecutor; + private volatile Executor taskExecutor; public void setTrigger(Trigger trigger) { this.trigger = trigger; @@ -82,11 +82,11 @@ public class PollerMetadata { return this.adviceChain; } - public void setTaskExecutor(TaskExecutor taskExecutor) { + public void setTaskExecutor(Executor taskExecutor) { this.taskExecutor = taskExecutor; } - public TaskExecutor getTaskExecutor() { + public Executor getTaskExecutor() { return this.taskExecutor; } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/splitter/ExpressionEvaluatingSplitter.java b/spring-integration-core/src/main/java/org/springframework/integration/splitter/ExpressionEvaluatingSplitter.java index 999fe697dd..52baa2be8d 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/splitter/ExpressionEvaluatingSplitter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/splitter/ExpressionEvaluatingSplitter.java @@ -18,6 +18,7 @@ package org.springframework.integration.splitter; import java.util.Collection; +import org.springframework.expression.Expression; import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; /** @@ -32,7 +33,7 @@ import org.springframework.integration.handler.ExpressionEvaluatingMessageProces public class ExpressionEvaluatingSplitter extends AbstractMessageProcessingSplitter { @SuppressWarnings({"unchecked", "rawtypes"}) - public ExpressionEvaluatingSplitter(String expression) { + public ExpressionEvaluatingSplitter(Expression expression) { super(new ExpressionEvaluatingMessageProcessor(expression, Collection.class)); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/ExpressionEvaluatingTransformer.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/ExpressionEvaluatingTransformer.java index 0b2de2a56a..679ced3f28 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/transformer/ExpressionEvaluatingTransformer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/ExpressionEvaluatingTransformer.java @@ -16,6 +16,7 @@ package org.springframework.integration.transformer; +import org.springframework.expression.Expression; import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; /** @@ -28,7 +29,7 @@ import org.springframework.integration.handler.ExpressionEvaluatingMessageProces */ public class ExpressionEvaluatingTransformer extends AbstractMessageProcessingTransformer { - public ExpressionEvaluatingTransformer(String expression) { + public ExpressionEvaluatingTransformer(Expression expression) { super(new ExpressionEvaluatingMessageProcessor(expression)); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/transformer/HeaderEnricher.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/HeaderEnricher.java index dd67cddd4c..0e8aaff1c9 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/transformer/HeaderEnricher.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/transformer/HeaderEnricher.java @@ -21,9 +21,12 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.Message; import org.springframework.integration.MessagingException; import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; @@ -168,15 +171,25 @@ public class HeaderEnricher implements Transformer { static class ExpressionEvaluatingHeaderValueMessageProcessor extends AbstractHeaderValueMessageProcessor implements BeanFactoryAware { + private static final ExpressionParser expressionParser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + private final ExpressionEvaluatingMessageProcessor targetProcessor; /** - * Create a header value processor for the given expression String and the expected type + * Create a header value processor for the given Expression and the expected type + * of the expression evaluation result. The expectedType may be null if unknown. + */ + public ExpressionEvaluatingHeaderValueMessageProcessor(Expression expression, Class expectedType) { + this.targetProcessor = new ExpressionEvaluatingMessageProcessor(expression, expectedType); + } + + /** + * Create a header value processor for the given expression string and the expected type * of the expression evaluation result. The expectedType may be null if unknown. */ public ExpressionEvaluatingHeaderValueMessageProcessor(String expressionString, Class expectedType) { - this.targetProcessor = new ExpressionEvaluatingMessageProcessor(expressionString, expectedType); - //this.targetProcessor.setExpectedType(expectedType); + Expression expression = expressionParser.parseExpression(expressionString); + this.targetProcessor = new ExpressionEvaluatingMessageProcessor(expression, expectedType); } public void setBeanFactory(BeanFactory beanFactory) { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/BeanFactoryTypeConverter.java b/spring-integration-core/src/main/java/org/springframework/integration/util/BeanFactoryTypeConverter.java index 8ef4817b53..7b66b71983 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/util/BeanFactoryTypeConverter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/BeanFactoryTypeConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2010 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,17 +16,25 @@ package org.springframework.integration.util; import java.beans.PropertyEditor; +import java.util.Collection; import org.springframework.beans.BeansException; import org.springframework.beans.SimpleTypeConverter; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.ConversionServiceFactory; import org.springframework.expression.TypeConverter; - +import org.springframework.util.CollectionUtils; +/** + * + * @author Dave Syer + * @author Oleg Zhurakousky + * + */ public class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware { private SimpleTypeConverter delegate = new SimpleTypeConverter(); @@ -89,13 +97,21 @@ public class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware if (targetType.getType() == Void.class || targetType.getType() == Void.TYPE) { return null; } + if (value instanceof Collection + && CollectionUtils.isEmpty((Collection) value) + && Collection.class.isAssignableFrom(targetType.getObjectType())){ + return value; + } if (conversionService.canConvert(sourceType, targetType)) { return conversionService.convert(value, sourceType, targetType); } + if (!String.class.isAssignableFrom(sourceType.getType())) { PropertyEditor editor = delegate.findCustomEditor(sourceType.getType(), null); - editor.setValue(value); - return editor.getAsText(); + if (editor != null){ // INT-1441 + editor.setValue(value); + return editor.getAsText(); + } } return delegate.convertIfNecessary(value, targetType.getType()); } diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd index ce725abcb3..87f42e81e6 100644 --- a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd +++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd @@ -1388,6 +1388,9 @@ + + + @@ -1929,6 +1932,7 @@ Name of the header whose value to use. + @@ -2357,7 +2361,12 @@ Name of the header whose value to use. - + + + + + + @@ -2369,6 +2378,28 @@ Name of the header whose value to use. + + + + + The key for retrieving the expression from an ExpressionSource. + + + + + + + The reference to an ExpressionSource. + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/ExpressionEvaluatingCorrelationStrategyTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/ExpressionEvaluatingCorrelationStrategyTests.java index 5cc196f450..51f6b2bc65 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/aggregator/ExpressionEvaluatingCorrelationStrategyTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/aggregator/ExpressionEvaluatingCorrelationStrategyTests.java @@ -1,20 +1,36 @@ +/* + * Copyright 2002-2010 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.aggregator; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import org.junit.Test; - +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.message.GenericMessage; /** * @author Alex Peters - * */ public class ExpressionEvaluatingCorrelationStrategyTests { private ExpressionEvaluatingCorrelationStrategy strategy; + @Test(expected = IllegalArgumentException.class) public void testCreateInstanceWithEmptyExpressionFails() throws Exception { strategy = new ExpressionEvaluatingCorrelationStrategy(""); @@ -22,12 +38,15 @@ public class ExpressionEvaluatingCorrelationStrategyTests { @Test(expected = IllegalArgumentException.class) public void testCreateInstanceWithNullExpressionFails() throws Exception { - strategy = new ExpressionEvaluatingCorrelationStrategy(null); + Expression nullExpression = null; + strategy = new ExpressionEvaluatingCorrelationStrategy(nullExpression); } @Test public void testCorrelationKeyWithMethodInvokingExpression() throws Exception { - strategy = new ExpressionEvaluatingCorrelationStrategy("payload.substring(0,1)"); + ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + Expression expression = parser.parseExpression("payload.substring(0,1)"); + strategy = new ExpressionEvaluatingCorrelationStrategy(expression); Object correlationKey = strategy.getCorrelationKey(new GenericMessage("bla")); assertThat(correlationKey, is(String.class)); assertThat((String) correlationKey, is("b")); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorTests.java index 4c2ef0de8d..5ff32d7a6a 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorTests.java @@ -20,17 +20,17 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import java.lang.reflect.Method; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.junit.Before; import org.junit.Test; - import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.integration.Message; -import org.springframework.integration.channel.MapBasedChannelResolver; import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.support.channel.BeanFactoryChannelResolver; +import org.springframework.integration.support.channel.ChannelResolver; /** * @author Mark Fisher @@ -39,14 +39,16 @@ import org.springframework.integration.channel.QueueChannel; */ public class MessagePublishingInterceptorTests { - private final MapBasedChannelResolver channelResolver = new MapBasedChannelResolver(); + private ChannelResolver channelResolver; private final QueueChannel testChannel = new QueueChannel(); @Before public void setup() { - channelResolver.setChannelMap(Collections.singletonMap("c", testChannel)); + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + channelResolver = new BeanFactoryChannelResolver(beanFactory); + beanFactory.registerSingleton("c", testChannel); } @Test diff --git a/spring-integration-core/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorUsageTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorUsageTests-context.xml index 38fdfa20d4..04dea82f3b 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorUsageTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorUsageTests-context.xml @@ -37,19 +37,19 @@ - + - - - - - + class="org.springframework.integration.support.channel.BeanFactoryChannelResolver"> + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/channel/MapBasedChannelResolverTests.java b/spring-integration-core/src/test/java/org/springframework/integration/channel/MapBasedChannelResolverTests.java deleted file mode 100644 index 2120776651..0000000000 --- a/spring-integration-core/src/test/java/org/springframework/integration/channel/MapBasedChannelResolverTests.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2002-2008 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.channel; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.Test; - -import org.springframework.integration.MessageChannel; - -/** - * @author Mark Fisher - */ -public class MapBasedChannelResolverTests { - - @Test - public void mapContainsChannel() { - MessageChannel testChannel = new QueueChannel(); - Map channelMap = new HashMap(); - channelMap.put("testChannel", testChannel); - MapBasedChannelResolver resolver = new MapBasedChannelResolver(); - resolver.setChannelMap(channelMap); - MessageChannel result = resolver.resolveChannelName("testChannel"); - assertNotNull(result); - assertEquals(testChannel, result); - } - - @Test - public void mapDoesNotContainChannel() { - MessageChannel testChannel = new QueueChannel(); - Map channelMap = new HashMap(); - channelMap.put("testChannel", testChannel); - MapBasedChannelResolver resolver = new MapBasedChannelResolver(); - resolver.setChannelMap(channelMap); - MessageChannel result = resolver.resolveChannelName("noSuchChannel"); - assertNull(result); - } - - @Test - public void emptyMap() { - Map channelMap = new HashMap(); - MapBasedChannelResolver resolver = new MapBasedChannelResolver(); - resolver.setChannelMap(channelMap); - MessageChannel result = resolver.resolveChannelName("testChannel"); - assertNull(result); - } - - @Test(expected = IllegalArgumentException.class) - public void nullMapRejected() { - MapBasedChannelResolver resolver = new MapBasedChannelResolver(); - resolver.setChannelMap(null); - } - -} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/RouterFactoryBeanDelegationTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/RouterFactoryBeanDelegationTests-context.xml index 9fdff832d3..45dd357678 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/RouterFactoryBeanDelegationTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/RouterFactoryBeanDelegationTests-context.xml @@ -19,9 +19,9 @@ - + - + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/core/MessagingTemplateTests.java b/spring-integration-core/src/test/java/org/springframework/integration/core/MessagingTemplateTests.java index 571605c360..e3d48e8192 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/core/MessagingTemplateTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/core/MessagingTemplateTests.java @@ -31,12 +31,12 @@ import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.support.StaticApplicationContext; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.channel.AbstractMessageChannel; import org.springframework.integration.channel.DirectChannel; -import org.springframework.integration.channel.MapBasedChannelResolver; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.endpoint.PollingConsumer; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; @@ -45,7 +45,9 @@ import org.springframework.integration.mapping.OutboundMessageMapper; import org.springframework.integration.message.GenericMessage; import org.springframework.integration.scheduling.PollerMetadata; import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.support.channel.BeanFactoryChannelResolver; import org.springframework.integration.support.channel.ChannelResolutionException; +import org.springframework.integration.support.channel.ChannelResolver; import org.springframework.integration.support.converter.SimpleMessageConverter; import org.springframework.integration.test.util.TestUtils; import org.springframework.integration.test.util.TestUtils.TestApplicationContext; @@ -306,15 +308,27 @@ public class MessagingTemplateTests { @Test public void sendByChannelNameWithCustomChannelResolver() { QueueChannel testChannel = new QueueChannel(); - Map channelMap = new HashMap(); - channelMap.put("testChannel", testChannel); - MapBasedChannelResolver channelResolver = new MapBasedChannelResolver(channelMap); + final QueueChannel anotherChannel = new QueueChannel(); + + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("testChannel", testChannel); + MessagingTemplate template = new MessagingTemplate(); - template.setChannelResolver(channelResolver); + template.setBeanFactory(beanFactory); + template.afterPropertiesSet(); Message message = MessageBuilder.withPayload("test").build(); template.send("testChannel", message); assertEquals(message, testChannel.receive(0)); + + template.setChannelResolver(new ChannelResolver() { + public MessageChannel resolveChannelName(String channelName) { + return anotherChannel; + } + }); + message = MessageBuilder.withPayload("test").build(); + template.send("testChannel", message); + assertEquals(message, anotherChannel.receive(0)); } @Test(expected = IllegalStateException.class) @@ -352,11 +366,11 @@ public class MessagingTemplateTests { @Test public void receiveByChannelNameWithCustomChannelResolver() { QueueChannel testChannel = new QueueChannel(); - Map channelMap = new HashMap(); - channelMap.put("testChannel", testChannel); - MapBasedChannelResolver channelResolver = new MapBasedChannelResolver(channelMap); + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("testChannel", testChannel); + MessagingTemplate template = new MessagingTemplate(); - template.setChannelResolver(channelResolver); + template.setBeanFactory(beanFactory); template.afterPropertiesSet(); Message message = MessageBuilder.withPayload("test").build(); testChannel.send(message); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/PollingLifecycleTests.java b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/PollingLifecycleTests.java new file mode 100644 index 0000000000..e070cc0e8a --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/PollingLifecycleTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2002-2010 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.endpoint; + +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static org.easymock.EasyMock.reset; +import static org.mockito.Mockito.atMost; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.integration.Message; +import org.springframework.integration.MessagingException; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean; +import org.springframework.integration.config.TestErrorHandler; +import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.core.MessageSource; +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.scheduling.PollerMetadata; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.support.PeriodicTrigger; + +/** + * @author Oleg Zhurakousky + * + */ +public class PollingLifecycleTests { + private ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); + private TestErrorHandler errorHandler = new TestErrorHandler(); + + @Before + public void init() throws Exception { + taskScheduler.afterPropertiesSet(); + } + + @Test + public void ensurePollerTaskStops() throws Exception{ + final CountDownLatch latch = new CountDownLatch(1); + QueueChannel channel = new QueueChannel(); + channel.send(new GenericMessage("foo")); + + MessageHandler handler = Mockito.spy(new MessageHandler() { + public void handleMessage(Message message) throws MessagingException { + latch.countDown(); + } + }); + PollingConsumer consumer = new PollingConsumer(channel, handler); + PollerMetadata pollerMetadata = new PollerMetadata(); + pollerMetadata.setTrigger(new PeriodicTrigger(0)); + consumer.setPollerMetadata(pollerMetadata); + consumer.setErrorHandler(errorHandler); + consumer.setTaskScheduler(taskScheduler); + consumer.setBeanFactory(mock(BeanFactory.class)); + consumer.afterPropertiesSet(); + consumer.start(); + assertTrue(latch.await(2, TimeUnit.SECONDS)); + Mockito.verify(handler, times(1)).handleMessage(Mockito.any(Message.class)); + consumer.stop(); + for (int i = 0; i < 10; i++) { + channel.send(new GenericMessage("foo")); + } + Thread.sleep(2000); // give enough time for poller to kick in if it didn't stop properly + // we'll still have a natural race condition between call to stop() and poller polling + // so what we really have to assert is that it doesn't poll for more then once after stop() was called + Mockito.reset(handler); + Mockito.verify(handler, atMost(1)).handleMessage(Mockito.any(Message.class)); + } + + @Test + public void ensurePollerTaskStopsForAdapter() throws Exception{ + final CountDownLatch latch = new CountDownLatch(1); + QueueChannel channel = new QueueChannel(); + + SourcePollingChannelAdapterFactoryBean adapterFactory = new SourcePollingChannelAdapterFactoryBean(); + PollerMetadata pollerMetadata = new PollerMetadata(); + pollerMetadata.setTrigger(new PeriodicTrigger(2000)); + adapterFactory.setPollerMetadata(pollerMetadata); + MessageSource source = spy(new MessageSource() { + public Message receive() { + latch.countDown(); + return new GenericMessage("hello"); + } + }); + adapterFactory.setSource(source); + adapterFactory.setOutputChannel(channel); + adapterFactory.setBeanFactory(mock(ConfigurableBeanFactory.class)); + SourcePollingChannelAdapter adapter = adapterFactory.getObject(); + adapter.setTaskScheduler(taskScheduler); + adapter.afterPropertiesSet(); + adapter.start(); + assertTrue(latch.await(2, TimeUnit.SECONDS)); + assertNotNull(channel.receive(100)); + adapter.stop(); + assertNull(channel.receive(1000)); + Mockito.verify(source, times(1)).receive(); + } +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/expression/DynamicExpressionTests.java b/spring-integration-core/src/test/java/org/springframework/integration/expression/DynamicExpressionTests.java new file mode 100644 index 0000000000..a99535905d --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/expression/DynamicExpressionTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2010 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.expression; + +import static org.junit.Assert.assertEquals; + +import java.io.FileOutputStream; + +import org.junit.After; +import org.junit.Test; + +import org.springframework.core.io.ClassPathResource; + +/** + * @author Mark Fisher + * @since 2.0 + */ +public class DynamicExpressionTests { + + private static final String key = "test.greeting"; + + private static final String basename = "org/springframework/integration/expression/expressions"; + + private static final String filepath = basename + ".properties"; + + + @After + public void resetFile() { + writeExpressionStringToFile("'Hello World!'"); + } + + + @Test + public void expressionUpdate() throws Exception { + ReloadableResourceBundleExpressionSource source = new ReloadableResourceBundleExpressionSource(); + source.setBasename(basename); + source.setCacheSeconds(0); + DynamicExpression expression = new DynamicExpression(key, source); + assertEquals("Hello World!", expression.getValue()); + writeExpressionStringToFile("toUpperCase()"); + assertEquals("FOO", expression.getValue("foo")); + } + + + private static void writeExpressionStringToFile(String expressionString) { + ClassPathResource resource = new ClassPathResource(filepath); + byte[] bytes = new String(key + "=" + expressionString).getBytes(); + try { + new FileOutputStream(resource.getFile()).write(bytes); + } + catch (Exception e) { + throw new IllegalStateException("failed to write expression string to file", e); + } + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/expression/expressions.properties b/spring-integration-core/src/test/java/org/springframework/integration/expression/expressions.properties new file mode 100644 index 0000000000..584df3a98a --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/expression/expressions.properties @@ -0,0 +1 @@ +test.greeting='Hello World!' \ No newline at end of file diff --git a/spring-integration-core/src/test/java/org/springframework/integration/filter/DynamicExpressionFilterIntegrationTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/filter/DynamicExpressionFilterIntegrationTests-context.xml new file mode 100644 index 0000000000..2f8d2b0614 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/filter/DynamicExpressionFilterIntegrationTests-context.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/filter/DynamicExpressionFilterIntegrationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/filter/DynamicExpressionFilterIntegrationTests.java new file mode 100644 index 0000000000..8934f4c62c --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/filter/DynamicExpressionFilterIntegrationTests.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2010 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.filter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.core.PollableChannel; +import org.springframework.integration.message.GenericMessage; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Mark Fisher + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class DynamicExpressionFilterIntegrationTests { + + @Autowired + private MessageChannel input; + + @Autowired + private PollableChannel positives; + + @Autowired + private PollableChannel negatives; + + + @Test + public void simpleExpressionBasedFilter() { + this.input.send(new GenericMessage(1)); + this.input.send(new GenericMessage(0)); + this.input.send(new GenericMessage(99)); + this.input.send(new GenericMessage(-99)); + assertEquals(new Integer(1), positives.receive(0).getPayload()); + assertEquals(new Integer(99), positives.receive(0).getPayload()); + assertEquals(new Integer(0), negatives.receive(0).getPayload()); + assertEquals(new Integer(-99), negatives.receive(0).getPayload()); + assertNull(positives.receive(0)); + assertNull(negatives.receive(0)); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/filter/expressions.properties b/spring-integration-core/src/test/java/org/springframework/integration/filter/expressions.properties new file mode 100644 index 0000000000..17b14a2975 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/filter/expressions.properties @@ -0,0 +1 @@ +filter.positive=payload > 0 \ No newline at end of file diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessorTests.java index d63f5e4ddf..38f6d893fc 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessorTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessorTests.java @@ -32,6 +32,10 @@ import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.io.Resource; import org.springframework.expression.EvaluationException; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.message.GenericMessage; /** @@ -43,6 +47,8 @@ public class ExpressionEvaluatingMessageProcessorTests { private static final Log logger = LogFactory.getLog(ExpressionEvaluatingMessageProcessorTests.class); + private static final ExpressionParser expressionParser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + @Rule public ExpectedException expected = ExpectedException.none(); @@ -50,7 +56,8 @@ public class ExpressionEvaluatingMessageProcessorTests { @Test public void testProcessMessage() { - ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("payload"); + Expression expression = expressionParser.parseExpression("payload"); + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression); assertEquals("foo", processor.processMessage(new GenericMessage("foo"))); } @@ -62,7 +69,8 @@ public class ExpressionEvaluatingMessageProcessorTests { return number+""; } } - ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("#target.stringify(payload)"); + Expression expression = expressionParser.parseExpression("#target.stringify(payload)"); + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression); processor.getEvaluationContext().setVariable("target", new TestTarget()); assertEquals("2", processor.processMessage(new GenericMessage("2"))); } @@ -74,7 +82,8 @@ public class ExpressionEvaluatingMessageProcessorTests { public void ping(String input) { } } - ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("#target.ping(payload)"); + Expression expression = expressionParser.parseExpression("#target.ping(payload)"); + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression); processor.getEvaluationContext().setVariable("target", new TestTarget()); assertEquals(null, processor.processMessage(new GenericMessage("2"))); } @@ -88,7 +97,8 @@ public class ExpressionEvaluatingMessageProcessorTests { } } - ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("#target.find(payload)"); + Expression expression = expressionParser.parseExpression("#target.find(payload)"); + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression); processor.setBeanFactory(new GenericApplicationContext().getBeanFactory()); processor.getEvaluationContext().setVariable("target", new TestTarget()); String result = (String) processor.processMessage(new GenericMessage("classpath:*.properties")); @@ -97,21 +107,24 @@ public class ExpressionEvaluatingMessageProcessorTests { @Test public void testProcessMessageWithDollarInBrackets() { - ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("headers['$id']"); + Expression expression = expressionParser.parseExpression("headers['$id']"); + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression); GenericMessage message = new GenericMessage("foo"); assertEquals(message.getHeaders().getId(), processor.processMessage(message)); } @Test public void testProcessMessageWithDollarPropertyAccess() { - ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("headers.$id"); + Expression expression = expressionParser.parseExpression("headers.$id"); + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression); GenericMessage message = new GenericMessage("foo"); assertEquals(message.getHeaders().getId(), processor.processMessage(message)); } @Test public void testProcessMessageWithStaticKey() { - ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("headers[headers.ID]"); + Expression expression = expressionParser.parseExpression("headers[headers.ID]"); + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression); GenericMessage message = new GenericMessage("foo"); assertEquals(message.getHeaders().getId(), processor.processMessage(message)); } @@ -122,7 +135,8 @@ public class ExpressionEvaluatingMessageProcessorTests { BeanDefinition beanDefinition = new RootBeanDefinition(String.class); beanDefinition.getConstructorArgumentValues().addGenericArgumentValue("bar"); context.registerBeanDefinition("testString", beanDefinition); - ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("payload.concat(@testString)"); + Expression expression = expressionParser.parseExpression("payload.concat(@testString)"); + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression); processor.setBeanFactory(context); GenericMessage message = new GenericMessage("foo"); assertEquals("foobar", processor.processMessage(message)); @@ -134,7 +148,8 @@ public class ExpressionEvaluatingMessageProcessorTests { BeanDefinition beanDefinition = new RootBeanDefinition(String.class); beanDefinition.getConstructorArgumentValues().addGenericArgumentValue("bar"); context.registerBeanDefinition("testString", beanDefinition); - ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("@testString.concat(payload)"); + Expression expression = expressionParser.parseExpression("@testString.concat(payload)"); + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression); processor.setBeanFactory(context); GenericMessage message = new GenericMessage("foo"); assertEquals("barfoo", processor.processMessage(message)); @@ -154,7 +169,8 @@ public class ExpressionEvaluatingMessageProcessorTests { description.appendText("cause to be EvaluationException but was ").appendValue(cause); } }); - ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("payload.fixMe()"); + Expression expression = expressionParser.parseExpression("payload.fixMe()"); + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression); assertEquals("foo", processor.processMessage(new GenericMessage("foo"))); } @@ -172,7 +188,8 @@ public class ExpressionEvaluatingMessageProcessorTests { description.appendText("cause to be UnsupportedOperationException but was ").appendValue(cause); } }); - ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("payload.throwRuntimeException()"); + Expression expression = expressionParser.parseExpression("payload.throwRuntimeException()"); + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression); assertEquals("foo", processor.processMessage(new GenericMessage(new TestPayload()))); } @@ -190,7 +207,8 @@ public class ExpressionEvaluatingMessageProcessorTests { description.appendText("cause to be CheckedException but was ").appendValue(cause); } }); - ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("payload.throwCheckedException()"); + Expression expression = expressionParser.parseExpression("payload.throwCheckedException()"); + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor(expression); assertEquals("foo", processor.processMessage(new GenericMessage(new TestPayload()))); } @@ -213,5 +231,5 @@ public class ExpressionEvaluatingMessageProcessorTests { super(string); } } - + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/LoggingHandlerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/LoggingHandlerTests.java index 160119e168..0be4db2ce6 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/handler/LoggingHandlerTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/LoggingHandlerTests.java @@ -18,7 +18,6 @@ package org.springframework.integration.handler; 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.support.MessageBuilder; @@ -42,7 +41,6 @@ public class LoggingHandlerTests { input.send(MessageBuilder.withPayload(bean).setHeader("foo", "bar").build()); } - public static class TestBean { private final String name; diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/ErrorMessageExceptionTypeRouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/ErrorMessageExceptionTypeRouterTests.java index 5fc1824f9d..7f0f096aed 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/ErrorMessageExceptionTypeRouterTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/ErrorMessageExceptionTypeRouterTests.java @@ -22,20 +22,24 @@ import static org.junit.Assert.assertNull; import java.util.HashMap; import java.util.Map; +import org.junit.Before; import org.junit.Test; - +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.integration.Message; -import org.springframework.integration.MessageChannel; import org.springframework.integration.MessageDeliveryException; import org.springframework.integration.MessageHandlingException; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.message.ErrorMessage; import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.support.channel.BeanFactoryChannelResolver; /** * @author Mark Fisher + * @author Oleg Zhurakousky */ public class ErrorMessageExceptionTypeRouterTests { + + private DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); private QueueChannel illegalArgumentChannel = new QueueChannel(); @@ -46,6 +50,15 @@ public class ErrorMessageExceptionTypeRouterTests { private QueueChannel messageDeliveryExceptionChannel = new QueueChannel(); private QueueChannel defaultChannel = new QueueChannel(); + + @Before + public void prepare(){ + beanFactory.registerSingleton("illegalArgumentChannel", illegalArgumentChannel); + beanFactory.registerSingleton("runtimeExceptionChannel", runtimeExceptionChannel); + beanFactory.registerSingleton("messageHandlingExceptionChannel", messageHandlingExceptionChannel); + beanFactory.registerSingleton("messageDeliveryExceptionChannel", messageDeliveryExceptionChannel); + beanFactory.registerSingleton("defaultChannel", defaultChannel); + } @Test @@ -56,12 +69,14 @@ public class ErrorMessageExceptionTypeRouterTests { MessageHandlingException error = new MessageHandlingException(failedMessage, "failed", middleCause); ErrorMessage message = new ErrorMessage(error); ErrorMessageExceptionTypeRouter router = new ErrorMessageExceptionTypeRouter(); - Map, MessageChannel> exceptionTypeChannelMap = - new HashMap, MessageChannel>(); - exceptionTypeChannelMap.put(IllegalArgumentException.class, illegalArgumentChannel); - exceptionTypeChannelMap.put(RuntimeException.class, runtimeExceptionChannel); - exceptionTypeChannelMap.put(MessageHandlingException.class, messageHandlingExceptionChannel); - router.setExceptionTypeChannelMap(exceptionTypeChannelMap); + Map exceptionTypeChannelMap = new HashMap(); + exceptionTypeChannelMap.put(IllegalArgumentException.class.getName(), "illegalArgumentChannel"); + exceptionTypeChannelMap.put(RuntimeException.class.getName(), "runtimeExceptionChannel"); + exceptionTypeChannelMap.put(MessageHandlingException.class.getName(), "messageHandlingExceptionChannel"); + router.setChannelIdentifierMap(exceptionTypeChannelMap); + + router.setBeanFactory(beanFactory); + router.setDefaultOutputChannel(defaultChannel); router.handleMessage(message); assertNotNull(illegalArgumentChannel.receive(1000)); @@ -78,11 +93,12 @@ public class ErrorMessageExceptionTypeRouterTests { MessageHandlingException error = new MessageHandlingException(failedMessage, "failed", middleCause); ErrorMessage message = new ErrorMessage(error); ErrorMessageExceptionTypeRouter router = new ErrorMessageExceptionTypeRouter(); - Map, MessageChannel> exceptionTypeChannelMap = - new HashMap, MessageChannel>(); - exceptionTypeChannelMap.put(RuntimeException.class, runtimeExceptionChannel); - exceptionTypeChannelMap.put(MessageHandlingException.class, messageHandlingExceptionChannel); - router.setExceptionTypeChannelMap(exceptionTypeChannelMap); + Map exceptionTypeChannelMap = new HashMap(); + exceptionTypeChannelMap.put(RuntimeException.class.getName(), "runtimeExceptionChannel"); + exceptionTypeChannelMap.put(MessageHandlingException.class.getName(), "runtimeExceptionChannel"); + router.setChannelIdentifierMap(exceptionTypeChannelMap); + router.setBeanFactory(beanFactory); + router.setDefaultOutputChannel(defaultChannel); router.handleMessage(message); assertNotNull(runtimeExceptionChannel.receive(1000)); @@ -99,10 +115,10 @@ public class ErrorMessageExceptionTypeRouterTests { MessageHandlingException error = new MessageHandlingException(failedMessage, "failed", middleCause); ErrorMessage message = new ErrorMessage(error); ErrorMessageExceptionTypeRouter router = new ErrorMessageExceptionTypeRouter(); - Map, MessageChannel> exceptionTypeChannelMap = - new HashMap, MessageChannel>(); - exceptionTypeChannelMap.put(MessageHandlingException.class, messageHandlingExceptionChannel); - router.setExceptionTypeChannelMap(exceptionTypeChannelMap); + Map exceptionTypeChannelMap = new HashMap(); + exceptionTypeChannelMap.put(MessageHandlingException.class.getName(), "messageHandlingExceptionChannel"); + router.setChannelIdentifierMap(exceptionTypeChannelMap); + router.setBeanFactory(beanFactory); router.setDefaultOutputChannel(defaultChannel); router.handleMessage(message); assertNotNull(messageHandlingExceptionChannel.receive(1000)); @@ -135,10 +151,10 @@ public class ErrorMessageExceptionTypeRouterTests { MessageHandlingException error = new MessageHandlingException(failedMessage, "failed", middleCause); ErrorMessage message = new ErrorMessage(error); ErrorMessageExceptionTypeRouter router = new ErrorMessageExceptionTypeRouter(); - Map, MessageChannel> exceptionTypeChannelMap = - new HashMap, MessageChannel>(); - exceptionTypeChannelMap.put(MessageDeliveryException.class, messageDeliveryExceptionChannel); - router.setExceptionTypeChannelMap(exceptionTypeChannelMap); + Map exceptionTypeChannelMap = new HashMap(); + exceptionTypeChannelMap.put(MessageDeliveryException.class.getName(), "messageDeliveryExceptionChannel"); + router.setChannelIdentifierMap(exceptionTypeChannelMap); + router.setBeanFactory(beanFactory); router.setResolutionRequired(true); router.handleMessage(message); } @@ -151,12 +167,12 @@ public class ErrorMessageExceptionTypeRouterTests { MessageHandlingException error = new MessageHandlingException(failedMessage, "failed", middleCause); Message message = new GenericMessage(error); ErrorMessageExceptionTypeRouter router = new ErrorMessageExceptionTypeRouter(); - Map, MessageChannel> exceptionTypeChannelMap = - new HashMap, MessageChannel>(); - exceptionTypeChannelMap.put(IllegalArgumentException.class, illegalArgumentChannel); - exceptionTypeChannelMap.put(RuntimeException.class, runtimeExceptionChannel); - exceptionTypeChannelMap.put(MessageHandlingException.class, messageHandlingExceptionChannel); - router.setExceptionTypeChannelMap(exceptionTypeChannelMap); + Map exceptionTypeChannelMap = new HashMap(); + exceptionTypeChannelMap.put(IllegalArgumentException.class.getName(), "illegalArgumentChannel"); + exceptionTypeChannelMap.put(RuntimeException.class.getName(), "runtimeExceptionChannel"); + exceptionTypeChannelMap.put(MessageHandlingException.class.getName(), "messageHandlingExceptionChannel"); + router.setChannelIdentifierMap(exceptionTypeChannelMap); + router.setBeanFactory(beanFactory); router.setDefaultOutputChannel(defaultChannel); router.handleMessage(message); assertNotNull(illegalArgumentChannel.receive(1000)); @@ -173,11 +189,11 @@ public class ErrorMessageExceptionTypeRouterTests { MessageHandlingException error = new MessageHandlingException(failedMessage, "failed", middleCause); ErrorMessage message = new ErrorMessage(error); ErrorMessageExceptionTypeRouter router = new ErrorMessageExceptionTypeRouter(); - Map, MessageChannel> exceptionTypeChannelMap = - new HashMap, MessageChannel>(); - exceptionTypeChannelMap.put(IllegalArgumentException.class, illegalArgumentChannel); - exceptionTypeChannelMap.put(MessageHandlingException.class, messageHandlingExceptionChannel); - router.setExceptionTypeChannelMap(exceptionTypeChannelMap); + Map exceptionTypeChannelMap = new HashMap(); + exceptionTypeChannelMap.put(IllegalArgumentException.class.getName(), "illegalArgumentChannel"); + exceptionTypeChannelMap.put(MessageHandlingException.class.getName(), "messageHandlingExceptionChannel"); + router.setChannelIdentifierMap(exceptionTypeChannelMap); + router.setBeanFactory(beanFactory); router.setDefaultOutputChannel(defaultChannel); router.handleMessage(message); assertNotNull(illegalArgumentChannel.receive(1000)); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/HeaderValueRouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/HeaderValueRouterTests.java index ddd7c0428f..0ee38d90f1 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/HeaderValueRouterTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/HeaderValueRouterTests.java @@ -16,23 +16,26 @@ package org.springframework.integration.router; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import org.junit.Test; - import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.support.StaticApplicationContext; import org.springframework.integration.Message; -import org.springframework.integration.channel.MapBasedChannelResolver; +import org.springframework.integration.MessageChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.support.channel.BeanFactoryChannelResolver; +import org.springframework.integration.support.channel.ChannelResolver; /** * @author Mark Fisher + * @author Oleg Zhurakousky */ public class HeaderValueRouterTests { @@ -61,6 +64,7 @@ public class HeaderValueRouterTests { routerBeanDefinition.getPropertyValues().addPropertyValue("resolutionRequired", "true"); context.registerBeanDefinition("router", routerBeanDefinition); context.registerBeanDefinition("testChannel", new RootBeanDefinition(QueueChannel.class)); + context.registerBeanDefinition("newChannel", new RootBeanDefinition(QueueChannel.class)); context.refresh(); MessageHandler handler = (MessageHandler) context.getBean("router"); Message message = MessageBuilder.withPayload("test").setHeader("testHeaderName", "testChannel").build(); @@ -69,6 +73,20 @@ public class HeaderValueRouterTests { Message result = channel.receive(1000); assertNotNull(result); assertSame(message, result); + + // validate dynamics + HeaderValueRouter router = (HeaderValueRouter) context.getBean("router"); + router.setChannelMapping("testChannel", "newChannel"); + router.handleMessage(message); + QueueChannel newChannel = (QueueChannel) context.getBean("newChannel"); + result = newChannel.receive(10); + assertNotNull(result); + + router.removeChannelMapping("testChannel"); + router.handleMessage(message); + result = channel.receive(1000); + assertNotNull(result); + assertSame(message, result); } @Test @@ -76,14 +94,12 @@ public class HeaderValueRouterTests { public void resolveChannelNameFromMap() { StaticApplicationContext context = new StaticApplicationContext(); ManagedMap channelMap = new ManagedMap(); - channelMap.put("testKey", new RuntimeBeanReference("testChannel")); - RootBeanDefinition channelResolverBeanDefinition = new RootBeanDefinition(MapBasedChannelResolver.class); - channelResolverBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(channelMap); + channelMap.put("testKey", "testChannel"); RootBeanDefinition routerBeanDefinition = new RootBeanDefinition(HeaderValueRouter.class); routerBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue("testHeaderName"); routerBeanDefinition.getPropertyValues().addPropertyValue("resolutionRequired", "true"); - routerBeanDefinition.getPropertyValues().addPropertyValue("channelResolver", new RuntimeBeanReference("resolver")); - context.registerBeanDefinition("resolver", channelResolverBeanDefinition); + routerBeanDefinition.getPropertyValues().addPropertyValue("channelIdentifierMap", channelMap); + routerBeanDefinition.getPropertyValues().addPropertyValue("beanFactory", context); context.registerBeanDefinition("router", routerBeanDefinition); context.registerBeanDefinition("testChannel", new RootBeanDefinition(QueueChannel.class)); context.refresh(); @@ -95,6 +111,34 @@ public class HeaderValueRouterTests { assertNotNull(result); assertSame(message, result); } + @Test + @SuppressWarnings("unchecked") + public void resolveChannelNameFromMapAndCustomeResolver() { + final StaticApplicationContext context = new StaticApplicationContext(); + ManagedMap channelMap = new ManagedMap(); + channelMap.put("testKey", "testChannel"); + RootBeanDefinition routerBeanDefinition = new RootBeanDefinition(HeaderValueRouter.class); + routerBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue("testHeaderName"); + routerBeanDefinition.getPropertyValues().addPropertyValue("resolutionRequired", "true"); + routerBeanDefinition.getPropertyValues().addPropertyValue("channelIdentifierMap", channelMap); + routerBeanDefinition.getPropertyValues().addPropertyValue("beanFactory", context); + routerBeanDefinition.getPropertyValues().addPropertyValue("channelResolver", new ChannelResolver() { + public MessageChannel resolveChannelName(String channelName) { + return context.getBean("anotherChannel", MessageChannel.class); + } + }); + context.registerBeanDefinition("router", routerBeanDefinition); + context.registerBeanDefinition("testChannel", new RootBeanDefinition(QueueChannel.class)); + context.registerBeanDefinition("anotherChannel", new RootBeanDefinition(QueueChannel.class)); + context.refresh(); + MessageHandler handler = (MessageHandler) context.getBean("router"); + Message message = MessageBuilder.withPayload("test").setHeader("testHeaderName", "testKey").build(); + handler.handleMessage(message); + QueueChannel channel = (QueueChannel) context.getBean("anotherChannel"); + Message result = channel.receive(1000); + assertNotNull(result); + assertSame(message, result); + } @Test public void resolveMultipleChannelsWithStringArray() { @@ -144,4 +188,5 @@ public class HeaderValueRouterTests { assertSame(message, result2); } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/MultiChannelRouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/MultiChannelRouterTests.java index 0029615e44..c9a2ac1f2c 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/MultiChannelRouterTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/MultiChannelRouterTests.java @@ -18,16 +18,19 @@ package org.springframework.integration.router; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; import java.util.List; import org.junit.Test; +import org.springframework.beans.factory.BeanFactory; import org.springframework.integration.Message; import org.springframework.integration.MessagingException; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.channel.TestChannelResolver; import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.support.channel.BeanFactoryChannelResolver; import org.springframework.util.CollectionUtils; /** @@ -37,7 +40,7 @@ public class MultiChannelRouterTests { @Test public void routeWithChannelMapping() { - AbstractChannelNameResolvingMessageRouter router = new AbstractChannelNameResolvingMessageRouter() { + AbstractMessageRouter router = new AbstractMessageRouter() { @SuppressWarnings("unchecked") public List getChannelIndicatorList(Message message) { return CollectionUtils.arrayToList(new String[] {"channel1", "channel2"}); @@ -61,7 +64,7 @@ public class MultiChannelRouterTests { @Test(expected = MessagingException.class) public void channelNameLookupFailure() { - AbstractChannelNameResolvingMessageRouter router = new AbstractChannelNameResolvingMessageRouter() { + AbstractMessageRouter router = new AbstractMessageRouter() { @SuppressWarnings("unchecked") public List getChannelIndicatorList(Message message) { return CollectionUtils.arrayToList(new String[] {"noSuchChannel"} ); @@ -75,12 +78,13 @@ public class MultiChannelRouterTests { @Test(expected = MessagingException.class) public void channelMappingNotAvailable() { - AbstractChannelNameResolvingMessageRouter router = new AbstractChannelNameResolvingMessageRouter() { + AbstractMessageRouter router = new AbstractMessageRouter() { @SuppressWarnings("unchecked") public List getChannelIndicatorList(Message message) { return CollectionUtils.arrayToList(new String[] {"noSuchChannel"}); } }; + router.setBeanFactory(mock(BeanFactory.class)); Message message = new GenericMessage("test"); router.handleMessage(message); } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/PayloadTypeRouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/PayloadTypeRouterTests.java index 6b08bb321c..f38c8478ae 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/PayloadTypeRouterTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/PayloadTypeRouterTests.java @@ -16,6 +16,7 @@ package org.springframework.integration.router; +import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -25,7 +26,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.junit.Test; - +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.MessageHandlingException; @@ -34,6 +35,7 @@ import org.springframework.integration.message.GenericMessage; /** * @author Mark Fisher + * @author Oleg Zhurakousky */ public class PayloadTypeRouterTests { @@ -41,17 +43,46 @@ public class PayloadTypeRouterTests { public void resolveExactMatch() { QueueChannel stringChannel = new QueueChannel(); QueueChannel integerChannel = new QueueChannel(); - Map, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap, MessageChannel>(); - payloadTypeChannelMap.put(String.class, stringChannel); - payloadTypeChannelMap.put(Integer.class, integerChannel); + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("stringChannel", stringChannel); + beanFactory.registerSingleton("integerChannel", integerChannel); + + + Map payloadTypeChannelMap = new ConcurrentHashMap(); + payloadTypeChannelMap.put(String.class.getName(), "stringChannel"); + payloadTypeChannelMap.put(Integer.class.getName(), "integerChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); - router.setPayloadTypeChannelMap(payloadTypeChannelMap); + router.setChannelIdentifierMap(payloadTypeChannelMap); + router.setBeanFactory(beanFactory); + Message message1 = new GenericMessage("test"); Message message2 = new GenericMessage(123); - MessageChannel result1 = router.determineTargetChannel(message1); - MessageChannel result2 = router.determineTargetChannel(message2); + assertEquals(1, router.determineTargetChannels(message1).size()); + MessageChannel result1 = router.determineTargetChannels(message1).iterator().next(); + assertEquals(1, router.determineTargetChannels(message2).size()); + MessageChannel result2 = router.determineTargetChannels(message2).iterator().next(); + assertEquals(stringChannel, result1); assertEquals(integerChannel, result2); + // validate dynamics + QueueChannel newChannel = new QueueChannel(); + beanFactory.registerSingleton("newChannel", newChannel); + router.setChannelMapping(String.class.getName(), "newChannel"); + assertEquals(1, router.determineTargetChannels(message1).size()); + result1 = router.determineTargetChannels(message1).iterator().next(); + assertEquals(newChannel, result1); + // validate nothing happens if mappings were removed and resolutionRequires = false + router.removeChannelMapping(String.class.getName()); + router.removeChannelMapping(Integer.class.getName()); + router.handleMessage(message1); + // validate exception is thrown if mappings were removed and resolutionRequires = true + router.setResolutionRequired(true); + try { + router.handleMessage(message1); + fail(); + } catch (Exception e) { + // ignore + } } @Test @@ -60,10 +91,15 @@ public class PayloadTypeRouterTests { defaultChannel.setBeanName("defaultChannel"); QueueChannel numberChannel = new QueueChannel(); numberChannel.setBeanName("numberChannel"); - Map, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap, MessageChannel>(); - payloadTypeChannelMap.put(Number.class, numberChannel); + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("defaultChannel", defaultChannel); + beanFactory.registerSingleton("numberChannel", numberChannel); + + Map payloadTypeChannelMap = new ConcurrentHashMap(); + payloadTypeChannelMap.put(Number.class.getName(), "numberChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); - router.setPayloadTypeChannelMap(payloadTypeChannelMap); + router.setChannelIdentifierMap(payloadTypeChannelMap); + router.setBeanFactory(beanFactory); router.setDefaultOutputChannel(defaultChannel); Message message = new GenericMessage(99); router.handleMessage(message); @@ -71,6 +107,15 @@ public class PayloadTypeRouterTests { assertNotNull(result); assertEquals(99, result.getPayload()); assertNull(defaultChannel.receive(0)); + + // validate dynamics + QueueChannel newChannel = new QueueChannel(); + beanFactory.registerSingleton("newChannel", newChannel); + router.setChannelMapping(Integer.class.getName(), "newChannel"); + assertEquals(1, router.determineTargetChannels(message).size()); + router.handleMessage(message); + result = newChannel.receive(10); + assertNotNull(result); } @Test @@ -81,11 +126,20 @@ public class PayloadTypeRouterTests { numberChannel.setBeanName("numberChannel"); QueueChannel integerChannel = new QueueChannel(); integerChannel.setBeanName("integerChannel"); - Map, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap, MessageChannel>(); - payloadTypeChannelMap.put(Number.class, numberChannel); - payloadTypeChannelMap.put(Integer.class, integerChannel); + + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("defaultChannel", defaultChannel); + beanFactory.registerSingleton("numberChannel", numberChannel); + beanFactory.registerSingleton("integerChannel", integerChannel); + + Map payloadTypeChannelMap = new ConcurrentHashMap(); + payloadTypeChannelMap.put(Number.class.getName(), "numberChannel"); + payloadTypeChannelMap.put(Integer.class.getName(), "integerChannel"); + PayloadTypeRouter router = new PayloadTypeRouter(); - router.setPayloadTypeChannelMap(payloadTypeChannelMap); + router.setBeanFactory(beanFactory); + router.setChannelIdentifierMap(payloadTypeChannelMap); + router.setDefaultOutputChannel(defaultChannel); Message message = new GenericMessage(99); router.handleMessage(message); @@ -102,10 +156,18 @@ public class PayloadTypeRouterTests { defaultChannel.setBeanName("defaultChannel"); QueueChannel comparableChannel = new QueueChannel(); comparableChannel.setBeanName("comparableChannel"); - Map, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap, MessageChannel>(); - payloadTypeChannelMap.put(Comparable.class, comparableChannel); + + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("defaultChannel", defaultChannel); + beanFactory.registerSingleton("comparableChannel", comparableChannel); + + Map payloadTypeChannelMap = new ConcurrentHashMap(); + payloadTypeChannelMap.put(Comparable.class.getName(), "comparableChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); - router.setPayloadTypeChannelMap(payloadTypeChannelMap); + + router.setBeanFactory(beanFactory); + router.setChannelIdentifierMap(payloadTypeChannelMap); + router.setDefaultOutputChannel(defaultChannel); Message message = new GenericMessage(99); router.handleMessage(message); @@ -123,11 +185,20 @@ public class PayloadTypeRouterTests { numberChannel.setBeanName("numberChannel"); QueueChannel comparableChannel = new QueueChannel(); comparableChannel.setBeanName("comparableChannel"); - Map, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap, MessageChannel>(); - payloadTypeChannelMap.put(Number.class, numberChannel); - payloadTypeChannelMap.put(Comparable.class, comparableChannel); + + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("defaultChannel", defaultChannel); + beanFactory.registerSingleton("numberChannel", numberChannel); + beanFactory.registerSingleton("comparableChannel", comparableChannel); + + Map payloadTypeChannelMap = new ConcurrentHashMap(); + payloadTypeChannelMap.put(Number.class.getName(), "numberChannel"); + payloadTypeChannelMap.put(Comparable.class.getName(), "comparableChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); - router.setPayloadTypeChannelMap(payloadTypeChannelMap); + + router.setBeanFactory(beanFactory); + router.setChannelIdentifierMap(payloadTypeChannelMap); + router.setDefaultOutputChannel(defaultChannel); Message message = new GenericMessage(99); router.handleMessage(message); @@ -136,6 +207,15 @@ public class PayloadTypeRouterTests { assertEquals(99, result.getPayload()); assertNull(numberChannel.receive(0)); assertNull(defaultChannel.receive(0)); + + // validate dynamics + QueueChannel newChannel = new QueueChannel(); + beanFactory.registerSingleton("newChannel", newChannel); + router.setChannelMapping(Integer.class.getName(), "newChannel"); + assertEquals(1, router.determineTargetChannels(message).size()); + router.handleMessage(message); + result = newChannel.receive(10); + assertNotNull(result); } @Test(expected = IllegalStateException.class) @@ -146,11 +226,20 @@ public class PayloadTypeRouterTests { serializableChannel.setBeanName("serializableChannel"); QueueChannel comparableChannel = new QueueChannel(); comparableChannel.setBeanName("comparableChannel"); - Map, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap, MessageChannel>(); - payloadTypeChannelMap.put(Serializable.class, serializableChannel); - payloadTypeChannelMap.put(Comparable.class, comparableChannel); + + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("defaultChannel", defaultChannel); + beanFactory.registerSingleton("serializableChannel", serializableChannel); + beanFactory.registerSingleton("comparableChannel", comparableChannel); + + Map payloadTypeChannelMap = new ConcurrentHashMap(); + payloadTypeChannelMap.put(Serializable.class.getName(), "serializableChannel"); + payloadTypeChannelMap.put(Comparable.class.getName(), "comparableChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); - router.setPayloadTypeChannelMap(payloadTypeChannelMap); + + router.setBeanFactory(beanFactory); + router.setChannelIdentifierMap(payloadTypeChannelMap); + router.setDefaultOutputChannel(defaultChannel); Message message = new GenericMessage("test"); try { @@ -169,11 +258,22 @@ public class PayloadTypeRouterTests { numberChannel.setBeanName("numberChannel"); QueueChannel serializableChannel = new QueueChannel(); serializableChannel.setBeanName("serializableChannel"); - Map, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap, MessageChannel>(); - payloadTypeChannelMap.put(Number.class, numberChannel); - payloadTypeChannelMap.put(Serializable.class, serializableChannel); + + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("defaultChannel", defaultChannel); + beanFactory.registerSingleton("numberChannel", numberChannel); + beanFactory.registerSingleton("serializableChannel", serializableChannel); + + + Map payloadTypeChannelMap = new ConcurrentHashMap(); + + payloadTypeChannelMap.put(Number.class.getName(), "numberChannel"); + payloadTypeChannelMap.put(Serializable.class.getName(), "serializableChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); - router.setPayloadTypeChannelMap(payloadTypeChannelMap); + + router.setBeanFactory(beanFactory); + router.setChannelIdentifierMap(payloadTypeChannelMap); + router.setDefaultOutputChannel(defaultChannel); Message message = new GenericMessage(99); router.handleMessage(message); @@ -190,11 +290,19 @@ public class PayloadTypeRouterTests { QueueChannel integerChannel = new QueueChannel(); stringChannel.setBeanName("stringChannel"); integerChannel.setBeanName("integerChannel"); - Map, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap, MessageChannel>(); - payloadTypeChannelMap.put(String.class, stringChannel); - payloadTypeChannelMap.put(Integer.class, integerChannel); + + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("stringChannel", stringChannel); + beanFactory.registerSingleton("integerChannel", integerChannel); + + Map payloadTypeChannelMap = new ConcurrentHashMap(); + payloadTypeChannelMap.put(String.class.getName(), "stringChannel"); + payloadTypeChannelMap.put(Integer.class.getName(), "integerChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); - router.setPayloadTypeChannelMap(payloadTypeChannelMap); + + router.setBeanFactory(beanFactory); + router.setChannelIdentifierMap(payloadTypeChannelMap); + Message message1 = new GenericMessage("test"); Message message2 = new GenericMessage(123); router.handleMessage(message1); @@ -211,10 +319,19 @@ public class PayloadTypeRouterTests { stringChannel.setBeanName("stringChannel"); QueueChannel defaultChannel = new QueueChannel(); defaultChannel.setBeanName("defaultChannel"); - Map, MessageChannel> payloadTypeChannelMap = new ConcurrentHashMap, MessageChannel>(); - payloadTypeChannelMap.put(String.class, stringChannel); + + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("stringChannel", stringChannel); + beanFactory.registerSingleton("defaultChannel", defaultChannel); + + + Map payloadTypeChannelMap = new ConcurrentHashMap(); + payloadTypeChannelMap.put(String.class.getName(), "stringChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); - router.setPayloadTypeChannelMap(payloadTypeChannelMap); + + router.setBeanFactory(beanFactory); + router.setChannelIdentifierMap(payloadTypeChannelMap); + router.setDefaultOutputChannel(defaultChannel); Message message1 = new GenericMessage("test"); Message message2 = new GenericMessage(123); @@ -227,5 +344,4 @@ public class PayloadTypeRouterTests { assertNotNull(result2); assertEquals(123, result2.getPayload()); } - } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/RouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/RouterTests.java index bc108d9df1..eb71440f2b 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/RouterTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/RouterTests.java @@ -17,16 +17,15 @@ package org.springframework.integration.router; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import org.junit.Test; - +import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.GenericApplicationContext; import org.springframework.integration.Message; -import org.springframework.integration.MessageChannel; import org.springframework.integration.MessageDeliveryException; import org.springframework.integration.MessagingException; import org.springframework.integration.channel.QueueChannel; @@ -36,15 +35,17 @@ import org.springframework.util.CollectionUtils; /** * @author Mark Fisher + * @author Oleg Zhurakousky */ public class RouterTests { @Test public void nullChannelIgnoredByDefault() { AbstractMessageRouter router = new AbstractMessageRouter() { - public List determineTargetChannels(Message message) { + @Override + protected List getChannelIndicatorList(Message message) { return null; - } + } }; Message message = new GenericMessage("test"); router.handleMessage(message); @@ -53,7 +54,8 @@ public class RouterTests { @Test(expected = MessageDeliveryException.class) public void nullChannelThrowsExceptionWhenResolutionRequired() { AbstractMessageRouter router = new AbstractMessageRouter() { - public List determineTargetChannels(Message message) { + @Override + protected List getChannelIndicatorList(Message message) { return null; } }; @@ -65,8 +67,9 @@ public class RouterTests { @Test public void emptyChannelListIgnoredByDefault() { AbstractMessageRouter router = new AbstractMessageRouter() { - public List determineTargetChannels(Message message) { - return Collections.emptyList(); + @Override + protected List getChannelIndicatorList(Message message) { + return null; } }; Message message = new GenericMessage("test"); @@ -76,8 +79,9 @@ public class RouterTests { @Test(expected = MessageDeliveryException.class) public void emptyChannelListThrowsExceptionWhenResolutionRequired() { AbstractMessageRouter router = new AbstractMessageRouter() { - public List determineTargetChannels(Message message) { - return Collections.emptyList(); + @Override + protected List getChannelIndicatorList(Message message) { + return null; } }; router.setResolutionRequired(true); @@ -87,8 +91,9 @@ public class RouterTests { @Test public void nullChannelNameArrayIgnoredByDefault() { - AbstractChannelNameResolvingMessageRouter router = new AbstractChannelNameResolvingMessageRouter() { - protected List getChannelIndicatorList(Message message) { + AbstractMessageRouter router = new AbstractMessageRouter() { + @Override + protected List getChannelIndicatorList(Message message) { return null; } }; @@ -100,7 +105,7 @@ public class RouterTests { @Test(expected = MessageDeliveryException.class) public void nullChannelNameArrayThrowsExceptionWhenResolutionRequired() { - AbstractChannelNameResolvingMessageRouter router = new AbstractChannelNameResolvingMessageRouter() { + AbstractMessageRouter router = new AbstractMessageRouter() { protected List getChannelIndicatorList(Message message) { return null; } @@ -115,7 +120,7 @@ public class RouterTests { @Test public void emptyChannelNameArrayIgnoredByDefault() { - AbstractChannelNameResolvingMessageRouter router = new AbstractChannelNameResolvingMessageRouter() { + AbstractMessageRouter router = new AbstractMessageRouter() { protected List getChannelIndicatorList(Message message) { return new ArrayList(); } @@ -128,7 +133,7 @@ public class RouterTests { @Test(expected = MessageDeliveryException.class) public void emptyChannelNameArrayThrowsExceptionWhenResolutionRequired() { - AbstractChannelNameResolvingMessageRouter router = new AbstractChannelNameResolvingMessageRouter() { + AbstractMessageRouter router = new AbstractMessageRouter() { @SuppressWarnings("unchecked") protected List getChannelIndicatorList(Message message) { return CollectionUtils.arrayToList(new String[] {}); @@ -148,17 +153,19 @@ public class RouterTests { return "notImportant"; } }; + router.setBeanFactory(mock(BeanFactory.class)); router.handleMessage(new GenericMessage("this should fail")); } @Test(expected = MessagingException.class) public void channelMappingIsRequiredWhenResolvingChannelNamesWithMultiChannelRouter() { - AbstractChannelNameResolvingMessageRouter router = new AbstractChannelNameResolvingMessageRouter() { + AbstractMessageRouter router = new AbstractMessageRouter() { @SuppressWarnings("unchecked") protected List getChannelIndicatorList(Message message){ return CollectionUtils.arrayToList(new String[] { "notImportant" }); } }; + router.setBeanFactory(mock(BeanFactory.class)); router.handleMessage(new GenericMessage("this should fail")); } @@ -180,7 +187,7 @@ public class RouterTests { @Test public void beanFactoryWithMultiChannelRouter() { - AbstractChannelNameResolvingMessageRouter router = new AbstractChannelNameResolvingMessageRouter() { + AbstractMessageRouter router = new AbstractMessageRouter() { @SuppressWarnings("unchecked") protected List getChannelIndicatorList(Message message) { return CollectionUtils.arrayToList(new String[] { "testChannel" }); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/SingleChannelRouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/SingleChannelRouterTests.java deleted file mode 100644 index 8dd2a15526..0000000000 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/SingleChannelRouterTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2002-2010 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.router; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import org.junit.Test; - -import org.springframework.integration.Message; -import org.springframework.integration.MessageChannel; -import org.springframework.integration.MessagingException; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.channel.TestChannelResolver; -import org.springframework.integration.message.GenericMessage; - -/** - * @author Mark Fisher - */ -public class SingleChannelRouterTests { - - @Test - public void routeWithChannelResolver() { - final QueueChannel channel = new QueueChannel(); - AbstractSingleChannelRouter router = new AbstractSingleChannelRouter() { - public MessageChannel determineTargetChannel(Message message) { - return channel; - } - }; - Message message = new GenericMessage("test"); - router.handleMessage(message); - Message result = channel.receive(25); - assertNotNull(result); - assertEquals("test", result.getPayload()); - } - - @Test - public void routeWithChannelNameResolver() { - AbstractSingleChannelNameRouter router = new AbstractSingleChannelNameRouter() { - public String determineTargetChannelName(Message message) { - return "testChannel"; - } - }; - QueueChannel channel = new QueueChannel(); - TestChannelResolver channelResolver = new TestChannelResolver(); - channelResolver.addChannel("testChannel", channel); - router.setChannelResolver(channelResolver); - Message message = new GenericMessage("test"); - router.handleMessage(message); - Message result = channel.receive(25); - assertNotNull(result); - assertEquals("test", result.getPayload()); - } - - @Test - public void nullChannelResultIgnored() { - AbstractSingleChannelRouter router = new AbstractSingleChannelRouter() { - public MessageChannel determineTargetChannel(Message message) { - return null; - } - }; - Message message = new GenericMessage("test"); - router.handleMessage(message); - } - - @Test(expected = MessagingException.class) - public void channelNameResolutionFailure() { - AbstractSingleChannelNameRouter router = new AbstractSingleChannelNameRouter() { - public String determineTargetChannelName(Message message) { - return "noSuchChannel"; - } - }; - TestChannelResolver channelResolver = new TestChannelResolver(); - router.setChannelResolver(channelResolver); - Message message = new GenericMessage("test"); - router.handleMessage(message); - } - -} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/config/DynamicExpressionRouterIntegrationTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/router/config/DynamicExpressionRouterIntegrationTests-context.xml new file mode 100644 index 0000000000..e4432e4e5c --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/config/DynamicExpressionRouterIntegrationTests-context.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/config/DynamicExpressionRouterIntegrationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/config/DynamicExpressionRouterIntegrationTests.java new file mode 100644 index 0000000000..8d4149ccf8 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/config/DynamicExpressionRouterIntegrationTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2010 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.router.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.core.PollableChannel; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Mark Fisher + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class DynamicExpressionRouterIntegrationTests { + + @Autowired + private MessageChannel input; + + @Autowired + private PollableChannel even; + + @Autowired + private PollableChannel odd; + + + @Test + public void dynamicExpressionBasedRouter() { + TestBean testBean1 = new TestBean(1); + TestBean testBean2 = new TestBean(2); + TestBean testBean3 = new TestBean(3); + TestBean testBean4 = new TestBean(4); + Message message1 = MessageBuilder.withPayload(testBean1).build(); + Message message2 = MessageBuilder.withPayload(testBean2).build(); + Message message3 = MessageBuilder.withPayload(testBean3).build(); + Message message4 = MessageBuilder.withPayload(testBean4).build(); + this.input.send(message1); + this.input.send(message2); + this.input.send(message3); + this.input.send(message4); + assertEquals(testBean1, odd.receive(0).getPayload()); + assertEquals(testBean2, even.receive(0).getPayload()); + assertEquals(testBean3, odd.receive(0).getPayload()); + assertEquals(testBean4, even.receive(0).getPayload()); + assertNull(odd.receive(0)); + assertNull(even.receive(0)); + } + + + static class TestBean { + + private final int number; + + public TestBean(int number) { + this.number = number; + } + + public int getNumber() { + return this.number; + } + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/config/PayloadTypeRouterParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/config/PayloadTypeRouterParserTests.java index 6bc966ea96..b996af909d 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/config/PayloadTypeRouterParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/config/PayloadTypeRouterParserTests.java @@ -61,15 +61,6 @@ public class PayloadTypeRouterParserTests { assertTrue(chanel2.receive(0).getPayload() instanceof Integer); } - @Test(expected=BeanDefinitionStoreException.class) - public void testFakeTypes(){ - ByteArrayInputStream stream = new ByteArrayInputStream(routerConfigFakeType.getBytes()); - GenericApplicationContext ac = new GenericApplicationContext(); - XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(ac); - reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_XSD); - reader.loadBeanDefinitions(new InputStreamResource(stream)); - } - @Test(expected=BeanDefinitionStoreException.class) public void testNoMappingElement(){ ByteArrayInputStream stream = new ByteArrayInputStream(routerConfigNoMaping.getBytes()); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterParserTests.java index 0aff6b9dac..3dc8b4e7e5 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterParserTests.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify; import java.util.Collection; import java.util.Collections; +import java.util.List; import org.junit.Test; import org.mockito.Mockito; @@ -205,9 +206,10 @@ public class RouterParserTests { this.channel = channel; } + @Override - protected Collection determineTargetChannels(Message message) { - return Collections.singletonList(this.channel); + protected List getChannelIndicatorList(Message message) { + return Collections.singletonList((Object)this.channel); } } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests-context.xml index fcce0a6a52..ebb93cb347 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests-context.xml @@ -19,7 +19,7 @@ - diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java index e7af29a277..0d6fb5bd6e 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/config/RouterWithMappingTests.java @@ -23,10 +23,14 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; +import org.springframework.integration.config.ConsumerEndpointFactoryBean; import org.springframework.integration.core.PollableChannel; +import org.springframework.integration.router.AbstractMessageRouter; import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.test.util.TestUtils; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -39,6 +43,10 @@ public class RouterWithMappingTests { @Autowired private MessageChannel expressionRouter; + + @Autowired + @Qualifier("spelRouter") + private ConsumerEndpointFactoryBean spelRouter; @Autowired private MessageChannel pojoRouter; @@ -79,6 +87,13 @@ public class RouterWithMappingTests { assertNotNull(defaultChannelForExpression.receive(0)); assertNull(fooChannelForExpression.receive(0)); assertNull(barChannelForExpression.receive(0)); + // validate dynamics + AbstractMessageRouter router = (AbstractMessageRouter) TestUtils.getPropertyValue(spelRouter, "handler"); + router.setChannelMapping("baz", "fooChannelForExpression"); + expressionRouter.send(message3); + assertNull(defaultChannelForExpression.receive(10)); + assertNotNull(fooChannelForExpression.receive(10)); + assertNull(barChannelForExpression.receive(0)); } @Test diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/config/expressions.properties b/spring-integration-core/src/test/java/org/springframework/integration/router/config/expressions.properties new file mode 100644 index 0000000000..16bcf95361 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/config/expressions.properties @@ -0,0 +1 @@ +router.oddeven=payload.number % 2 == 0 ? 'even' : 'odd' \ No newline at end of file diff --git a/spring-integration-core/src/test/java/org/springframework/integration/splitter/DynamicExpressionSplitterIntegrationTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/splitter/DynamicExpressionSplitterIntegrationTests-context.xml new file mode 100644 index 0000000000..bd5fc433d1 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/splitter/DynamicExpressionSplitterIntegrationTests-context.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/splitter/DynamicExpressionSplitterIntegrationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/splitter/DynamicExpressionSplitterIntegrationTests.java new file mode 100644 index 0000000000..b51b8fa181 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/splitter/DynamicExpressionSplitterIntegrationTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2010 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.splitter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.ArrayList; +import java.util.List; + +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.core.PollableChannel; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Mark Fisher + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class DynamicExpressionSplitterIntegrationTests { + + @Autowired + private MessageChannel input; + + @Autowired + private PollableChannel output; + + + @Test + public void simple() { + Message message = MessageBuilder.withPayload(new TestBean()).setHeader("foo", "foo").build(); + this.input.send(message); + Message one = output.receive(0); + Message two = output.receive(0); + Message three = output.receive(0); + Message four = output.receive(0); + assertEquals(new Integer(1), one.getPayload()); + assertEquals("foo", one.getHeaders().get("foo")); + assertEquals(new Integer(2), two.getPayload()); + assertEquals("foo", two.getHeaders().get("foo")); + assertEquals(new Integer(3), three.getPayload()); + assertEquals("foo", three.getHeaders().get("foo")); + assertEquals(new Integer(4), four.getPayload()); + assertEquals("foo", four.getHeaders().get("foo")); + assertNull(output.receive(0)); + } + + + static class TestBean { + + private final List numbers = new ArrayList(); + + public TestBean() { + for (int i = 1; i <= 10; i++) { + this.numbers.add(i); + } + } + + public List getNumbers() { + return this.numbers; + } + + public String[] split(String s) { + return s.split(","); + } + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/splitter/expressions.properties b/spring-integration-core/src/test/java/org/springframework/integration/splitter/expressions.properties new file mode 100644 index 0000000000..9bfa728289 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/splitter/expressions.properties @@ -0,0 +1 @@ +split.lessThan5=payload.numbers.?[#this < 5] \ No newline at end of file diff --git a/spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionHeaderEnricherIntegrationTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionHeaderEnricherIntegrationTests-context.xml new file mode 100644 index 0000000000..69a903fd3f --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionHeaderEnricherIntegrationTests-context.xml @@ -0,0 +1,24 @@ + + + + + + + + +
+ +
+
+ + + + + +
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionHeaderEnricherIntegrationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionHeaderEnricherIntegrationTests.java new file mode 100644 index 0000000000..cad648d41d --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionHeaderEnricherIntegrationTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2010 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.transformer; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.core.PollableChannel; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Mark Fisher + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class DynamicExpressionHeaderEnricherIntegrationTests { + + @Autowired + private MessageChannel input; + + @Autowired + private PollableChannel output; + + + @Test + public void dynamicExpressionHeader() { + Message message = MessageBuilder.withPayload("test").build(); + this.input.send(message); + Message result = output.receive(0); + assertEquals("foo", result.getHeaders().get("testHeader")); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionTransformerIntegrationTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionTransformerIntegrationTests-context.xml new file mode 100644 index 0000000000..77186977b2 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionTransformerIntegrationTests-context.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionTransformerIntegrationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionTransformerIntegrationTests.java new file mode 100644 index 0000000000..241782fea9 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionTransformerIntegrationTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2010 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.transformer; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.core.PollableChannel; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Mark Fisher + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class DynamicExpressionTransformerIntegrationTests { + + @Autowired + private MessageChannel input; + + @Autowired + private PollableChannel output; + + + @Test + public void transformWithDynamicExpression() { + Message message = MessageBuilder.withPayload(new TestBean()).setHeader("bar", 123).build(); + this.input.send(message); + Message result = output.receive(0); + assertEquals("test123", result.getPayload()); + } + + + static class TestBean { + + public String getFoo() { + return "test"; + } + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/transformer/expressions.properties b/spring-integration-core/src/test/java/org/springframework/integration/transformer/expressions.properties new file mode 100644 index 0000000000..90fa9aeacb --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/transformer/expressions.properties @@ -0,0 +1,2 @@ +test.transform=payload.foo + headers.bar +test.header='foo' diff --git a/spring-integration-core/src/test/java/org/springframework/integration/util/BeanFactoryTypeConverterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/util/BeanFactoryTypeConverterTests.java new file mode 100644 index 0000000000..a2b5ae8ddc --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/util/BeanFactoryTypeConverterTests.java @@ -0,0 +1,29 @@ +/** + * + */ +package org.springframework.integration.util; + +import static junit.framework.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.springframework.core.convert.TypeDescriptor; + +/** + * @author Oleg Zhurakousky + * + */ +public class BeanFactoryTypeConverterTests { + + @Test + public void testEmptyCollectionConversion(){ + BeanFactoryTypeConverter typeConverter = new BeanFactoryTypeConverter(); + List sourceObject = new ArrayList(); + // source type doesn't even matter + ArrayList convertedCollection = + (ArrayList) typeConverter.convertValue(sourceObject, null, TypeDescriptor.forObject(new ArrayList())); + assertEquals(sourceObject, convertedCollection); + } +} diff --git a/spring-integration-event/.settings/com.springsource.sts.config.flow.prefs b/spring-integration-event/.settings/com.springsource.sts.config.flow.prefs index 920caf3658..73c9299da9 100644 --- a/spring-integration-event/.settings/com.springsource.sts.config.flow.prefs +++ b/spring-integration-event/.settings/com.springsource.sts.config.flow.prefs @@ -1,3 +1,3 @@ -#Wed Sep 22 11:50:06 EDT 2010 -//com.springsource.sts.config.flow.coordinates\:http\://www.springframework.org/schema/integration\:/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventInboundChannelAdapterParserTests-context.xml=\n\n\n\n\n\n\n\n\n\n\n\n\n\n +#Fri Oct 08 14:30:53 EDT 2010 +//com.springsource.sts.config.flow.coordinates\:http\://www.springframework.org/schema/integration\:/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventInboundChannelAdapterParserTests-context.xml=\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n eclipse.preferences.version=1 diff --git a/spring-integration-event/src/main/java/org/springframework/integration/event/ApplicationEventInboundChannelAdapter.java b/spring-integration-event/src/main/java/org/springframework/integration/event/ApplicationEventInboundChannelAdapter.java index 709881de0d..41f0b99773 100644 --- a/spring-integration-event/src/main/java/org/springframework/integration/event/ApplicationEventInboundChannelAdapter.java +++ b/spring-integration-event/src/main/java/org/springframework/integration/event/ApplicationEventInboundChannelAdapter.java @@ -21,14 +21,17 @@ import java.util.concurrent.CopyOnWriteArraySet; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.endpoint.MessageProducerSupport; import org.springframework.integration.support.MessageBuilder; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** - * An inbound Channel Adapter that passes Spring - * {@link ApplicationEvent ApplicationEvents} within messages. + * An inbound Channel Adapter that passes Spring {@link ApplicationEvent ApplicationEvents} within messages. + * If a {@link #setPayloadExpression(String) payloadExpression} is provided, it will be evaluated against + * the ApplicationEvent instance to create the Message payload. * * @author Mark Fisher */ @@ -36,6 +39,10 @@ public class ApplicationEventInboundChannelAdapter extends MessageProducerSuppor private final Set> eventTypes = new CopyOnWriteArraySet>(); + private volatile Expression payloadExpression; + + private final SpelExpressionParser parser = new SpelExpressionParser(); + /** * Set the list of event types (classes that extend ApplicationEvent) that @@ -51,6 +58,24 @@ public class ApplicationEventInboundChannelAdapter extends MessageProducerSuppor } } + /** + * Provide an expression to be evaluated against the received ApplicationEvent + * instance (the "root object") in order to create the Message payload. If none + * is provided, the ApplicationEvent itself will be used as the payload. + */ + public void setPayloadExpression(String payloadExpression) { + if (payloadExpression == null) { + this.payloadExpression = null; + } + else { + this.payloadExpression = this.parser.parseExpression(payloadExpression); + } + } + + public String getComponentType() { + return "event:inbound-channel-adapter"; + } + public void onApplicationEvent(ApplicationEvent event) { if (CollectionUtils.isEmpty(this.eventTypes)) { this.sendEventAsMessage(event); @@ -65,11 +90,8 @@ public class ApplicationEventInboundChannelAdapter extends MessageProducerSuppor } private void sendEventAsMessage(ApplicationEvent event) { - this.sendMessage(MessageBuilder.withPayload(event).build()); - } - - public String getComponentType(){ - return "event:inbound-channel-adapter"; + Object payload = (this.payloadExpression != null) ? this.payloadExpression.getValue(event) : event; + this.sendMessage(MessageBuilder.withPayload(payload).build()); } @Override diff --git a/spring-integration-event/src/main/java/org/springframework/integration/event/config/EventInboundChannelAdapterParser.java b/spring-integration-event/src/main/java/org/springframework/integration/event/config/EventInboundChannelAdapterParser.java index 3b605e5024..37d924f60c 100644 --- a/spring-integration-event/src/main/java/org/springframework/integration/event/config/EventInboundChannelAdapterParser.java +++ b/spring-integration-event/src/main/java/org/springframework/integration/event/config/EventInboundChannelAdapterParser.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.integration.event.config; import org.springframework.beans.factory.support.AbstractBeanDefinition; @@ -30,11 +31,11 @@ import org.w3c.dom.Element; public class EventInboundChannelAdapterParser extends AbstractChannelAdapterParser{ @Override - protected AbstractBeanDefinition doParse(Element element, - ParserContext parserContext, String channelName) { + protected AbstractBeanDefinition doParse(Element element, ParserContext parserContext, String channelName) { BeanDefinitionBuilder adapterBuilder = BeanDefinitionBuilder.rootBeanDefinition(ApplicationEventInboundChannelAdapter.class); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(adapterBuilder, element, "channel", "outputChannel"); IntegrationNamespaceUtils.setValueIfAttributeDefined(adapterBuilder, element, "event-types"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(adapterBuilder, element, "payload-expression"); return adapterBuilder.getBeanDefinition(); } diff --git a/spring-integration-event/src/main/resources/org/springframework/integration/event/config/spring-integration-event-2.0.xsd b/spring-integration-event/src/main/resources/org/springframework/integration/event/config/spring-integration-event-2.0.xsd index 407511a771..c57993c7fe 100644 --- a/spring-integration-event/src/main/resources/org/springframework/integration/event/config/spring-integration-event-2.0.xsd +++ b/spring-integration-event/src/main/resources/org/springframework/integration/event/config/spring-integration-event-2.0.xsd @@ -47,7 +47,15 @@ types will be sent [OPTIONAL] - + + + + + + diff --git a/spring-integration-event/src/test/java/org/springframework/integration/event/ApplicationEventInboundChannelAdapterTests.java b/spring-integration-event/src/test/java/org/springframework/integration/event/ApplicationEventInboundChannelAdapterTests.java index b3b27c4fb8..e044983b9e 100644 --- a/spring-integration-event/src/test/java/org/springframework/integration/event/ApplicationEventInboundChannelAdapterTests.java +++ b/spring-integration-event/src/test/java/org/springframework/integration/event/ApplicationEventInboundChannelAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2010 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. @@ -53,8 +53,8 @@ public class ApplicationEventInboundChannelAdapterTests { assertEquals("event2", ((ApplicationEvent) message3.getPayload()).getSource()); } - @SuppressWarnings("unchecked") @Test + @SuppressWarnings("unchecked") public void onlyConfiguredEventTypesAreSent() { QueueChannel channel = new QueueChannel(); ApplicationEventInboundChannelAdapter adapter = new ApplicationEventInboundChannelAdapter(); @@ -93,6 +93,24 @@ public class ApplicationEventInboundChannelAdapterTests { assertEquals(ContextClosedEvent.class, closedEventMessage.getPayload().getClass()); } + @Test + public void payloadExpressionEvaluatedAgainstApplicationEvent() { + QueueChannel channel = new QueueChannel(); + ApplicationEventInboundChannelAdapter adapter = new ApplicationEventInboundChannelAdapter(); + adapter.setPayloadExpression("'received: ' + source"); + adapter.setOutputChannel(channel); + Message message1 = channel.receive(0); + assertNull(message1); + adapter.onApplicationEvent(new TestApplicationEvent1()); + adapter.onApplicationEvent(new TestApplicationEvent2()); + Message message2 = channel.receive(20); + assertNotNull(message2); + assertEquals("received: event1", message2.getPayload()); + Message message3 = channel.receive(20); + assertNotNull(message3); + assertEquals("received: event2", message3.getPayload()); + } + @SuppressWarnings("serial") private static class TestApplicationEvent1 extends ApplicationEvent { @@ -100,6 +118,8 @@ public class ApplicationEventInboundChannelAdapterTests { public TestApplicationEvent1() { super("event1"); } + + } diff --git a/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventInboundChannelAdapterParserTests-context.xml b/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventInboundChannelAdapterParserTests-context.xml index bf71e51806..835a4cd39a 100644 --- a/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventInboundChannelAdapterParserTests-context.xml +++ b/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventInboundChannelAdapterParserTests-context.xml @@ -30,7 +30,13 @@ - + + + + + + + diff --git a/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventInboundChannelAdapterParserTests.java b/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventInboundChannelAdapterParserTests.java index 3e7240d423..d1e5d9eebc 100644 --- a/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventInboundChannelAdapterParserTests.java +++ b/spring-integration-event/src/test/java/org/springframework/integration/event/config/EventInboundChannelAdapterParserTests.java @@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.expression.Expression; import org.springframework.integration.Message; import org.springframework.integration.core.PollableChannel; import org.springframework.integration.event.ApplicationEventInboundChannelAdapter; @@ -62,9 +63,9 @@ public class EventInboundChannelAdapterParserTests { DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter); Assert.assertEquals(context.getBean("input"), adapterAccessor.getPropertyValue("outputChannel")); } - - @SuppressWarnings("unchecked") + @Test + @SuppressWarnings("unchecked") public void validateEventParserWithEventTypes() { Object adapter = context.getBean("eventAdapterFiltered"); Assert.assertNotNull(adapter); @@ -77,9 +78,9 @@ public class EventInboundChannelAdapterParserTests { assertTrue(eventTypes.contains(SampleEvent.class)); assertTrue(eventTypes.contains(AnotherSampleEvent.class)); } - - @SuppressWarnings("unchecked") + @Test + @SuppressWarnings("unchecked") public void validateEventParserWithEventTypesAndPlaceholder() { Object adapter = context.getBean("eventAdapterFilteredPlaceHolder"); Assert.assertNotNull(adapter); @@ -108,6 +109,16 @@ public class EventInboundChannelAdapterParserTests { assertEquals(SampleEvent.class, message.getPayload().getClass()); } + @Test + public void validatePayloadExpression() { + Object adapter = context.getBean("eventAdapterSpel"); + Assert.assertNotNull(adapter); + Assert.assertTrue(adapter instanceof ApplicationEventInboundChannelAdapter); + DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter); + Expression expression = (Expression) adapterAccessor.getPropertyValue("payloadExpression"); + Assert.assertEquals("source + '-test'", expression.getExpressionString()); + } + @SuppressWarnings("serial") public static class SampleEvent extends ApplicationEvent { diff --git a/spring-integration-event/template.mf b/spring-integration-event/template.mf index f344fe42f9..b9ec36fe94 100644 --- a/spring-integration-event/template.mf +++ b/spring-integration-event/template.mf @@ -4,8 +4,9 @@ Bundle-Vendor: SpringSource Bundle-ManifestVersion: 2 Import-Template: org.springframework.integration.*;version="[2.0.0, 2.0.1)", - org.springframework.context;version="[3.0.3, 4.0.0)", - org.springframework.util;version="[3.0.3, 4.0.0)", org.springframework.beans.*;version="[3.0.3, 4.0.0)", + org.springframework.context;version="[3.0.3, 4.0.0)", + org.springframework.expression.*;version="[3.0.3, 4.0.0)", + org.springframework.util;version="[3.0.3, 4.0.0)", org.apache.commons.logging;version="[1.1.1, 2.0.0)", org.w3c.dom.*;version="0" diff --git a/spring-integration-ftp/pom.xml b/spring-integration-ftp/pom.xml index b72b350d77..1e205193b1 100644 --- a/spring-integration-ftp/pom.xml +++ b/spring-integration-ftp/pom.xml @@ -5,6 +5,7 @@ org.springframework.integration spring-integration-parent 2.0.0.BUILD-SNAPSHOT + ../spring-integration-parent/pom.xml org.springframework.integration spring-integration-ftp @@ -30,37 +31,31 @@ cglib cglib-nodep - ${cglib.version} test org.easymock easymock - ${org.easymock.version} test org.easymock easymockclassextension - ${org.easymock.version} test junit junit - ${junit.version} test org.springframework spring-context-support - ${org.springframework.version} compile org.springframework spring-test - ${org.springframework.version} test diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandler.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandler.java index fbd915bc00..d3311c3143 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandler.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2010 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. @@ -13,8 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.integration.ftp; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.SocketException; +import java.nio.charset.Charset; + import org.apache.commons.lang.SystemUtils; import org.apache.commons.net.ftp.FTPClient; import org.springframework.beans.factory.InitializingBean; @@ -22,18 +32,12 @@ import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.integration.Message; import org.springframework.integration.MessageDeliveryException; -import org.springframework.integration.MessageHandlingException; -import org.springframework.integration.MessageRejectedException; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.file.DefaultFileNameGenerator; import org.springframework.integration.file.FileNameGenerator; import org.springframework.util.Assert; import org.springframework.util.FileCopyUtils; -import java.io.*; -import java.net.SocketException; - - /** * A {@link org.springframework.integration.core.MessageHandler} implementation that sends files to an FTP server. * @@ -41,14 +45,21 @@ import java.net.SocketException; * @author Mark Fisher * @author Josh Long */ -public class FtpSendingMessageHandler implements MessageHandler, - InitializingBean { +public class FtpSendingMessageHandler implements MessageHandler, InitializingBean { + private static final String TEMPORARY_FILE_SUFFIX = ".writing"; - private FtpClientPool ftpClientPool; - private FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator(); - private File temporaryBufferFolderFile; - private Resource temporaryBufferFolder = new FileSystemResource(SystemUtils.getJavaIoTmpDir()); - private String charset; + + + private volatile FtpClientPool ftpClientPool; + + private volatile FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator(); + + private volatile File temporaryBufferFolderFile; + + private volatile Resource temporaryBufferFolder = new FileSystemResource(SystemUtils.getJavaIoTmpDir()); + + private volatile String charset = Charset.defaultCharset().name(); + public FtpSendingMessageHandler() { } @@ -57,10 +68,23 @@ public class FtpSendingMessageHandler implements MessageHandler, this.ftpClientPool = ftpClientPool; } + public void setFtpClientPool(FtpClientPool ftpClientPool) { this.ftpClientPool = ftpClientPool; } + public void setTemporaryBufferFolder(Resource temporaryBufferFolder) { + this.temporaryBufferFolder = temporaryBufferFolder; + } + + public void setFileNameGenerator(FileNameGenerator fileNameGenerator) { + this.fileNameGenerator = fileNameGenerator; + } + + public void setCharset(String charset) { + this.charset = charset; + } + public void afterPropertiesSet() throws Exception { Assert.notNull(ftpClientPool, "'ftpClientPool' must not be null"); Assert.notNull(temporaryBufferFolder, @@ -70,95 +94,65 @@ public class FtpSendingMessageHandler implements MessageHandler, /* Ugh this needs to be put in a convenient place accessible for all the file:, sftp:, and ftp:* adapters */ - private File handleFileMessage(File sourceFile, File tempFile, - File resultFile) throws IOException { + private File handleFileMessage(File sourceFile, File tempFile, File resultFile) throws IOException { if (sourceFile.renameTo(resultFile)) { return resultFile; } - FileCopyUtils.copy(sourceFile, tempFile); tempFile.renameTo(resultFile); - return resultFile; } - private File handleByteArrayMessage(byte[] bytes, File tempFile, - File resultFile) throws IOException { + private File handleByteArrayMessage(byte[] bytes, File tempFile, File resultFile) throws IOException { FileCopyUtils.copy(bytes, tempFile); tempFile.renameTo(resultFile); - return resultFile; } private File handleStringMessage(String content, File tempFile, File resultFile, String charset) throws IOException { - OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream( - tempFile), charset); + OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(tempFile), charset); FileCopyUtils.copy(content, writer); tempFile.renameTo(resultFile); - return resultFile; } - public void setTemporaryBufferFolder(Resource temporaryBufferFolder) { - this.temporaryBufferFolder = temporaryBufferFolder; - } - - public void setFileNameGenerator(FileNameGenerator fileNameGenerator) { - this.fileNameGenerator = fileNameGenerator; - } - - private File redeemForStorableFile(Message msg) - throws MessageDeliveryException { + private File redeemForStorableFile(Message message) throws MessageDeliveryException { try { - Object payload = msg.getPayload(); - String generateFileName = this.fileNameGenerator.generateFileName(msg); - File tempFile = new File(temporaryBufferFolderFile, - generateFileName + TEMPORARY_FILE_SUFFIX); - File resultFile = new File(temporaryBufferFolderFile, - generateFileName); + Object payload = message.getPayload(); + String generateFileName = this.fileNameGenerator.generateFileName(message); + File tempFile = new File(temporaryBufferFolderFile, generateFileName + TEMPORARY_FILE_SUFFIX); + File resultFile = new File(temporaryBufferFolderFile, generateFileName); File sendableFile; - if (payload instanceof String) { - sendableFile = this.handleStringMessage((String) payload, - tempFile, resultFile, this.charset); - } else if (payload instanceof File) { - sendableFile = this.handleFileMessage((File) payload, tempFile, - resultFile); - } else if (payload instanceof byte[]) { - sendableFile = this.handleByteArrayMessage((byte[]) payload, - tempFile, resultFile); - } else { + sendableFile = this.handleStringMessage((String) payload, tempFile, resultFile, this.charset); + } + else if (payload instanceof File) { + sendableFile = this.handleFileMessage((File) payload, tempFile, resultFile); + } + else if (payload instanceof byte[]) { + sendableFile = this.handleByteArrayMessage((byte[]) payload, tempFile, resultFile); + } + else { sendableFile = null; } - return sendableFile; - } catch (Throwable th) { - throw new MessageDeliveryException(msg); } - } - - public void setCharset(String charset) { - this.charset = charset; + catch (Throwable th) { + throw new MessageDeliveryException(message); + } } /* Ugh this needs to be put in a convenient place accessible for all the file:, sftp:, and ftp:* adapters */ - public void handleMessage(Message message) - throws MessageRejectedException, MessageHandlingException, - MessageDeliveryException { + public void handleMessage(Message message) { Assert.notNull(message, "'message' must not be null"); - Object payload = message.getPayload(); - Assert.notNull(payload, "Message payload must not be null"); - File file = this.redeemForStorableFile(message); - if ((file != null) && file.exists()) { FTPClient client = null; boolean sentSuccesfully; - try { client = getFtpClient(); sentSuccesfully = sendFile(file, client); @@ -167,14 +161,17 @@ public class FtpSendingMessageHandler implements MessageHandler, "File [" + file + "] not found in local working directory; it was moved or deleted unexpectedly", e); - } catch (IOException e) { + } + catch (IOException e) { throw new MessageDeliveryException(message, "Error transferring file [" + file + "] from local working directory to remote FTP directory", e); - } catch (Exception e) { + } + catch (Exception e) { throw new MessageDeliveryException(message, "Error handling message for file [" + file + "]", e); - } finally { + } + finally { if (file.exists()) { try { file.delete(); @@ -182,12 +179,10 @@ public class FtpSendingMessageHandler implements MessageHandler, /// noop } } - if (client != null) { ftpClientPool.releaseClient(client); } } - if (!sentSuccesfully) { throw new MessageDeliveryException(message, "Failed to store file '" + file + "'"); @@ -195,22 +190,19 @@ public class FtpSendingMessageHandler implements MessageHandler, } } - private boolean sendFile(File file, FTPClient client) - throws FileNotFoundException, IOException { + private boolean sendFile(File file, FTPClient client) throws FileNotFoundException, IOException { FileInputStream fileInputStream = new FileInputStream(file); boolean sent = client.storeFile(file.getName(), fileInputStream); fileInputStream.close(); - return sent; } private FTPClient getFtpClient() throws SocketException, IOException { FTPClient client; client = this.ftpClientPool.getClient(); - Assert.state(client != null, - FtpClientPool.class.getSimpleName() + - " returned 'null' client this most likely a bug in the pool implementation."); - + Assert.state(client != null, FtpClientPool.class.getSimpleName() + + " returned 'null' client this most likely a bug in the pool implementation."); return client; } + } diff --git a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandlerFactoryBean.java b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandlerFactoryBean.java index 017e9b490e..13c6f107bf 100644 --- a/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandlerFactoryBean.java +++ b/spring-integration-ftp/src/main/java/org/springframework/integration/ftp/FtpSendingMessageHandlerFactoryBean.java @@ -71,9 +71,10 @@ public class FtpSendingMessageHandlerFactoryBean extends AbstractFactoryBeanorg.springframework.integration spring-integration-parent 2.0.0.BUILD-SNAPSHOT + ../spring-integration-parent/pom.xml org.springframework.integration spring-integration-groovy @@ -24,19 +25,16 @@ org.springframework spring-context-support - ${org.springframework.version} org.springframework spring-test - ${org.springframework.version} test junit junit - ${junit.version} test diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/DefaultInboundRequestMapper.java b/spring-integration-http/src/main/java/org/springframework/integration/http/DefaultInboundRequestMapper.java deleted file mode 100644 index b014325af0..0000000000 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/DefaultInboundRequestMapper.java +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright 2002-2010 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.http; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.servlet.ServletRequest; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.integration.Message; -import org.springframework.integration.support.MessageBuilder; -import org.springframework.util.FileCopyUtils; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.multipart.MultipartException; -import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.multipart.MultipartHttpServletRequest; -import org.springframework.web.multipart.MultipartResolver; - -/** - * Default implementation of {@link InboundRequestMapper} for inbound HttpServletRequests. - * The request will be mapped according to the following rules: - *
    - *
  • For a GET request or a POST request with a Content-Type of - * "application/x-www-form-urlencoded", the parameter Map will be copied as the - * payload. The map will be an instance of {@link MultiValueMap} where the keys are - * Strings and the values are Lists of Strings. Those Lists are populated from the - * String array values of the original request parameter Map as described for the - * {@link ServletRequest#getParameterMap()} method.
  • - *
  • If a MultipartResolver has been provided, and a multipart request is - * detected, the multipart file content will be converted to String for any - * "text" content type, or byte arrays otherwise.
  • - *
  • For other request types, the request body will be used as the payload - * and the type will depend on the Content-Type header value. If it begins with - * "text", a String will be created. If the Content-Type is - * "application/x-java-serialized-object", the request body will be expected to - * contain a Serializable Object, and that will be used as the message payload. - * Otherwise, the payload will be a byte array.
  • - *
- * In all cases, the original request headers will be passed in the - * MessageHeaders. Likewise, the following headers will be added: - *
    - *
  • {@link HttpHeaders#REQUEST_URL}
  • - *
  • {@link HttpHeaders#REQUEST_METHOD}
  • - *
  • {@link HttpHeaders#USER_PRINCIPAL} (if available)
  • - *
- * - * @author Mark Fisher - * @author Oleg Zhurakousky - * @since 1.0.2 - */ -public class DefaultInboundRequestMapper implements InboundRequestMapper { - - private final Log logger = LogFactory.getLog(getClass()); - - private volatile MultipartResolver multipartResolver; - - private volatile String multipartCharset = null; - - private volatile boolean copyUploadedFiles; - - - /** - * Specify the {@link MultipartResolver} to use when checking requests. - * If no resolver is provided, this mapper will not support multipart - * requests. - */ - public void setMultipartResolver(MultipartResolver multipartResolver) { - this.multipartResolver = multipartResolver; - } - - /** - * Specify the charset name to use when converting multipart file content - * into Strings. - */ - public void setMultipartCharset(String multipartCharset) { - this.multipartCharset = multipartCharset; - } - - /** - * Specify whether uploaded multipart files should be copied to a temporary - * file on the server. If this is set to 'true', the payload map will - * contain a File instance as the value for each multipart file entry. - * Otherwise the uploaded file's content will be converted to either a - * String or byte array based on the content-type (String for "text/*" and - * byte array otherwise). The default value is false. - */ - public void setCopyUploadedFiles(boolean copyUploadedFiles) { - this.copyUploadedFiles = copyUploadedFiles; - } - - public Message toMessage(HttpServletRequest request) throws Exception { - try { - request = this.checkMultipart(request); - Object payload = createPayloadFromRequest(request); - MessageBuilder builder = MessageBuilder.withPayload(payload); - this.populateHeaders(request, builder); - return builder.build(); - } - finally { - this.cleanupMultipart(request); - } - } - - /** - * Convert the request into a multipart request to make multiparts available. - * If no multipart resolver is set, simply use the existing request. - * @param request current HTTP request - * @return the processed request (multipart wrapper if necessary) - * @see MultipartResolver#resolveMultipart - */ - private HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { - if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { - if (request instanceof MultipartHttpServletRequest) { - logger.debug("Request is already a MultipartHttpServletRequest"); - } - else { - return this.multipartResolver.resolveMultipart(request); - } - } - return request; - } - - /** - * Clean up any resources used by the given multipart request (if any). - * @param request current HTTP request - * @see MultipartResolver#cleanupMultipart - */ - private void cleanupMultipart(HttpServletRequest request) { - if (this.multipartResolver != null && request instanceof MultipartHttpServletRequest) { - this.multipartResolver.cleanupMultipart((MultipartHttpServletRequest) request); - } - } - - private Object createPayloadFromRequest(HttpServletRequest request) throws Exception { - Object payload = null; - String contentType = request.getContentType() != null ? request.getContentType() : ""; - if (request instanceof MultipartHttpServletRequest) { - payload = this.createPayloadFromMultipartRequest((MultipartHttpServletRequest) request); - } - else if (contentType.startsWith("multipart/form-data")) { - throw new IllegalArgumentException("Content-Type of 'multipart/form-data' requires a MultipartResolver." + - " Try configuring a MultipartResolver within the ApplicationContext."); - } - else if (request.getMethod().equals("GET")) { - if (logger.isDebugEnabled()) { - logger.debug("received GET request, using parameter map as payload"); - } - payload = this.createPayloadFromParameterMap(request); - } - else if (contentType.startsWith("application/x-www-form-urlencoded")) { - if (logger.isDebugEnabled()) { - logger.debug("received " + request.getMethod() - + " request with form data, using parameter map as payload"); - } - payload = createPayloadFromParameterMap(request); - } - else if (contentType.startsWith("text")) { - if (logger.isDebugEnabled()) { - logger.debug("received " + request.getMethod() - + " request, creating payload with text content"); - } - payload = createPayloadFromTextContent(request); - } - else if (contentType.startsWith("application/x-java-serialized-object")) { - payload = createPayloadFromSerializedObject(request); - } - else { - payload = createPayloadFromInputStream(request); - } - return payload; - } - - @SuppressWarnings("unchecked") - private Object createPayloadFromMultipartRequest(MultipartHttpServletRequest multipartRequest) { - Map payloadMap = new HashMap(multipartRequest.getParameterMap()); - Map fileMap = multipartRequest.getFileMap(); - for (Map.Entry entry : fileMap.entrySet()) { - MultipartFile multipartFile = entry.getValue(); - if (multipartFile.isEmpty()) { - continue; - } - try { - if (this.copyUploadedFiles) { - File tmpFile = File.createTempFile("si_", null); - multipartFile.transferTo(tmpFile); - payloadMap.put(entry.getKey(), tmpFile); - if (logger.isDebugEnabled()) { - logger.debug("copied uploaded file [" + multipartFile.getOriginalFilename() + - "] to temporary file [" + tmpFile.getAbsolutePath() + "]"); - } - } - else if (multipartFile.getContentType() != null && multipartFile.getContentType().startsWith("text")) { - String multipartFileAsString = this.multipartCharset != null ? - new String(multipartFile.getBytes(), this.multipartCharset) : - new String(multipartFile.getBytes()); - payloadMap.put(entry.getKey(), multipartFileAsString); - } - else { - payloadMap.put(entry.getKey(), multipartFile.getBytes()); - } - } - catch (IOException e) { - throw new IllegalArgumentException("Cannot read contents of multipart file", e); - } - } - return Collections.unmodifiableMap(payloadMap); - } - - @SuppressWarnings("unchecked") - private Object createPayloadFromParameterMap(HttpServletRequest request) { - return new UnmodifiableRequestParameterMap(request.getParameterMap()); - } - - private Object createPayloadFromTextContent(HttpServletRequest request) throws IOException { - String charset = request.getCharacterEncoding() != null ? request.getCharacterEncoding() : "utf-8"; - return new String(FileCopyUtils.copyToByteArray(request.getInputStream()), charset); - } - - private Object createPayloadFromSerializedObject(HttpServletRequest request) { - try { - return new ObjectInputStream(request.getInputStream()).readObject(); - } - catch (Exception e) { - throw new IllegalArgumentException("failed to deserialize Object in request", e); - } - } - - private byte[] createPayloadFromInputStream(HttpServletRequest request) throws Exception { - InputStream stream = request.getInputStream(); - int length = request.getContentLength(); - if (length == -1) { - throw new ResponseStatusCodeException(HttpServletResponse.SC_LENGTH_REQUIRED); - } - if (logger.isDebugEnabled()) { - logger.debug("received " + request.getMethod() + " request, " - + "creating byte array payload with content lenth: " + length); - } - byte[] bytes = new byte[length]; - stream.read(bytes, 0, length); - return bytes; - } - - private void populateHeaders(HttpServletRequest request, MessageBuilder builder) { - Enumeration headerNames = request.getHeaderNames(); - if (headerNames != null) { - while (headerNames.hasMoreElements()) { - String headerName = (String) headerNames.nextElement(); - Enumeration headerEnum = request.getHeaders(headerName); - if (headerEnum != null) { - List headers = new ArrayList(); - while (headerEnum.hasMoreElements()) { - headers.add(headerEnum.nextElement()); - } - if (headers.size() == 1) { - builder.setHeader(headerName, headers.get(0)); - } - else if (headers.size() > 1) { - builder.setHeader(headerName, headers); - } - } - } - } - builder.setHeader(HttpHeaders.REQUEST_URL, request.getRequestURL().toString()); - builder.setHeader(HttpHeaders.REQUEST_METHOD, request.getMethod()); - builder.setHeader(HttpHeaders.USER_PRINCIPAL, request.getUserPrincipal()); - } - - - /** - * Map class that extends {@link LinkedMultiValueMap} and implements Serializable. - * The contents of the map are unmodifiable, so calling any modification operation - * (e.g. put, add, or remove) will result in an UnsupportedOperationException. - */ - @SuppressWarnings("serial") - private static class UnmodifiableRequestParameterMap - extends LinkedMultiValueMap implements Serializable { // TODO: in 3.0.1 LMVM implements Serializable - - UnmodifiableRequestParameterMap(Map parameters) { - for (Map.Entry entry : parameters.entrySet()) { - super.put(entry.getKey(), Arrays.asList(entry.getValue())); - } - } - - @Override - public void add(String key, String value) { - throw new UnsupportedOperationException(); - } - - @Override - public void clear() { - throw new UnsupportedOperationException(); - } - - @Override - public List put(String key, List value) { - throw new UnsupportedOperationException(); - } - - @Override - public void putAll(Map> m) { - throw new UnsupportedOperationException(); - } - - @Override - public List remove(Object key) { - throw new UnsupportedOperationException(); - } - - @Override - public void set(String key, String value) { - throw new UnsupportedOperationException(); - } - - @Override - public void setAll(Map values) { - throw new UnsupportedOperationException(); - } - - @Override - public Map toSingleValueMap() { - return Collections.unmodifiableMap(super.toSingleValueMap()); - } - - } - -} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParser.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParser.java index 7c8af1976a..e04ea2509c 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParser.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParser.java @@ -69,6 +69,7 @@ public class HttpOutboundChannelAdapterParser extends AbstractOutboundChannelAda IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "extract-payload"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "expected-response-type"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "request-factory"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-handler"); List uriVariableElements = DomUtils.getChildElementsByTagName(element, "uri-variable"); if (!CollectionUtils.isEmpty(uriVariableElements)) { Map uriVariableExpressions = new HashMap(); diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundGatewayParser.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundGatewayParser.java index 105684dc0c..febb3347b6 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundGatewayParser.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpOutboundGatewayParser.java @@ -75,6 +75,7 @@ public class HttpOutboundGatewayParser extends AbstractConsumerEndpointParser { IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "expected-response-type"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-timeout", "sendTimeout"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "request-factory"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-handler"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel"); List uriVariableElements = DomUtils.getChildElementsByTagName(element, "uri-variable"); if (!CollectionUtils.isEmpty(uriVariableElements)) { diff --git a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.0.xsd b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.0.xsd index 201555339b..9a5fd60fc6 100644 --- a/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.0.xsd +++ b/spring-integration-http/src/main/resources/org/springframework/integration/http/config/spring-integration-http-2.0.xsd @@ -245,6 +245,9 @@ + @@ -252,6 +255,18 @@ + + + + + + + + + + + @@ -354,6 +372,18 @@ + + + + + + + + + + message = (Message) mapper.toMessage(request); - assertThat(message.getPayload(), is(SIMPLE_STRING)); - } - - @Test - public void complexUtf8TextMapping() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setContentType("text"); - // don't forget to specify the character encoding on the request or you - // will end up with unpredictable results! - request.setCharacterEncoding("utf-8"); - byte[] bytes = COMPLEX_STRING.getBytes("utf-8"); - request.setContent(bytes); - Message message = (Message) mapper.toMessage(request); - assertThat(message.getPayload(), is(COMPLEX_STRING)); - } - - @Test - public void newlineTest() throws Exception { - String content = "foo\nbar\n"; - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setContentType("text"); - byte[] bytes = content.getBytes(); - request.setContent(bytes); - Message message = (Message) mapper.toMessage(request); - assertThat(message.getPayload(), is(content)); - } - - @Test - public void emptyStringTest() throws Exception { - String content = ""; - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setContentType("text"); - byte[] bytes = content.getBytes(); - request.setContent(bytes); - Message message = (Message) mapper.toMessage(request); - assertThat(message.getPayload(), is(content)); - } - - @Test - public void multipartUpload() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - MultiValueMap files = new LinkedMultiValueMap(); - MultipartFile file = new StubMultipartFile("file", "testFile.txt", "foo"); - files.add("file", file); - Map params = new HashMap(); - MultipartHttpServletRequest multipartRequest = new DefaultMultipartHttpServletRequest(request, files, params); - mapper.setCopyUploadedFiles(true); - Message result = mapper.toMessage(multipartRequest); - File tmpFile = (File) ((Map) result.getPayload()).get("file"); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - FileCopyUtils.copy(new FileInputStream(tmpFile), baos); - assertThat(baos.toString(), is("foo")); - tmpFile.deleteOnExit(); - } - - @Test - public void testProcessMessageWithDollar() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setContentType("text"); - request.setCharacterEncoding("utf-8"); - byte[] bytes = SIMPLE_STRING.getBytes("utf-8"); - request.setContent(bytes); - Message message = (Message) mapper.toMessage(request); - ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("headers['$http_requestUrl']"); - assertEquals(message.getHeaders().get(HttpHeaders.REQUEST_URL), processor.processMessage(message)); - } - -} diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests-context.xml index 045930c401..4a82103e37 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests-context.xml @@ -24,6 +24,7 @@ expected-response-type="java.lang.Boolean" mapped-request-headers="requestHeader1, requestHeader2" request-factory="testRequestFactory" + error-handler="testErrorHandler" order="77" auto-startup="false"> @@ -31,6 +32,8 @@ + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java index b2dfb1e809..0b690a9444 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundChannelAdapterParserTests.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.io.IOException; import java.util.Map; import org.junit.Test; @@ -32,12 +33,14 @@ import org.springframework.context.ApplicationContext; import org.springframework.expression.Expression; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.integration.endpoint.AbstractEndpoint; import org.springframework.integration.http.HttpRequestExecutingMessageHandler; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.ObjectUtils; +import org.springframework.web.client.ResponseErrorHandler; /** * @author Mark Fisher @@ -94,6 +97,8 @@ public class HttpOutboundChannelAdapterParserTests { assertEquals(converterListBean, templateAccessor.getPropertyValue("messageConverters")); Object requestFactoryBean = this.applicationContext.getBean("testRequestFactory"); assertEquals(requestFactoryBean, requestFactory); + Object errorHandlerBean = this.applicationContext.getBean("testErrorHandler"); + assertEquals(errorHandlerBean, templateAccessor.getPropertyValue("errorHandler")); assertEquals("http://localhost/test2/{foo}", handlerAccessor.getPropertyValue("uri")); assertEquals(HttpMethod.GET, handlerAccessor.getPropertyValue("httpMethod")); assertEquals("UTF-8", handlerAccessor.getPropertyValue("charset")); @@ -111,4 +116,15 @@ public class HttpOutboundChannelAdapterParserTests { assertTrue(ObjectUtils.containsElement(mappedRequestHeaders, "requestHeader2")); } + + public static class StubErrorHandler implements ResponseErrorHandler { + + public boolean hasError(ClientHttpResponse response) throws IOException { + return false; + } + + public void handleError(ClientHttpResponse response) throws IOException { + } + } + } diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml index a36d0c3d90..5069345cc3 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests-context.xml @@ -31,6 +31,7 @@ expected-response-type="java.lang.String" mapped-request-headers="requestHeader1, requestHeader2" mapped-response-headers="responseHeader" + error-handler="testErrorHandler" reply-channel="replies" charset="UTF-8" order="77" @@ -40,6 +41,8 @@ + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java index c0a89de85d..80ba14e3b1 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/config/HttpOutboundGatewayParserTests.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.io.IOException; import java.util.Map; import org.junit.Test; @@ -33,6 +34,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.expression.Expression; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.integration.MessageChannel; import org.springframework.integration.endpoint.AbstractEndpoint; @@ -40,6 +42,7 @@ import org.springframework.integration.http.HttpRequestExecutingMessageHandler; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.ObjectUtils; +import org.springframework.web.client.ResponseErrorHandler; /** * @author Mark Fisher @@ -105,6 +108,8 @@ public class HttpOutboundGatewayParserTests { assertEquals(false, handlerAccessor.getPropertyValue("extractPayload")); Object requestFactoryBean = this.applicationContext.getBean("testRequestFactory"); assertEquals(requestFactoryBean, requestFactory); + Object errorHandlerBean = this.applicationContext.getBean("testErrorHandler"); + assertEquals(errorHandlerBean, templateAccessor.getPropertyValue("errorHandler")); Object sendTimeout = new DirectFieldAccessor( handlerAccessor.getPropertyValue("messagingTemplate")).getPropertyValue("sendTimeout"); assertEquals(new Long("1234"), sendTimeout); @@ -122,4 +127,15 @@ public class HttpOutboundGatewayParserTests { assertEquals("responseHeader", mappedResponseHeaders[0]); } + + public static class StubErrorHandler implements ResponseErrorHandler { + + public boolean hasError(ClientHttpResponse response) throws IOException { + return false; + } + + public void handleError(ClientHttpResponse response) throws IOException { + } + } + } diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/AttributePollingChannelAdapterParser.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/AttributePollingChannelAdapterParser.java index 03bc5bfba8..c20050e5ce 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/AttributePollingChannelAdapterParser.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/AttributePollingChannelAdapterParser.java @@ -39,7 +39,7 @@ public class AttributePollingChannelAdapterParser extends AbstractPollingInbound protected String parseSource(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition( "org.springframework.integration.jmx.AttributePollingMessageSource"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "mbean-server", "server"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "server", "server"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "object-name"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "attribute-name"); return BeanDefinitionReaderUtils.registerWithGeneratedName( diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/JmxNamespaceHandler.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/JmxNamespaceHandler.java index 4ae2ece98b..eff9e98816 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/JmxNamespaceHandler.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/JmxNamespaceHandler.java @@ -33,7 +33,7 @@ public class JmxNamespaceHandler extends AbstractIntegrationNamespaceHandler { this.registerBeanDefinitionParser("attribute-polling-channel-adapter", new AttributePollingChannelAdapterParser()); this.registerBeanDefinitionParser("notification-listening-channel-adapter", new NotificationListeningChannelAdapterParser()); this.registerBeanDefinitionParser("notification-publishing-channel-adapter", new NotificationPublishingChannelAdapterParser()); - this.registerBeanDefinitionParser("mbean-exporter", new MBeanExporterParser()); + this.registerBeanDefinitionParser("mbean-export", new MBeanExporterParser()); this.registerBeanDefinitionParser("control-bus", new ControlBusParser()); } diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterParser.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterParser.java index 1af4ef2837..161c3f26c3 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterParser.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/MBeanExporterParser.java @@ -46,12 +46,12 @@ public class MBeanExporterParser extends AbstractSingleBeanDefinitionParser { protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { Object mbeanServer = getMBeanServer(element, parserContext); builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "domain"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "default-domain"); builder.addPropertyValue("server", mbeanServer); } private Object getMBeanServer(Element element, ParserContext parserContext) { - String mbeanServer = element.getAttribute("mbean-server"); + String mbeanServer = element.getAttribute("server"); if (StringUtils.hasText(mbeanServer)) { return new RuntimeBeanReference(mbeanServer); } diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParser.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParser.java index 36739b5257..06bdfe53a8 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParser.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/NotificationListeningChannelAdapterParser.java @@ -48,7 +48,7 @@ public class NotificationListeningChannelAdapterParser extends AbstractSimpleBea parserContext.getReaderContext().error("The 'channel' attribute is required.", source); } builder.addPropertyReference("outputChannel", channel); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "mbean-server", "server"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "server"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "notification-filter", "filter"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "handback"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "send-timeout"); diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/OperationInvokingChannelAdapterParser.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/OperationInvokingChannelAdapterParser.java index ad1134798b..55c22940e3 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/OperationInvokingChannelAdapterParser.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/OperationInvokingChannelAdapterParser.java @@ -39,7 +39,7 @@ public class OperationInvokingChannelAdapterParser extends AbstractOutboundChann protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition( "org.springframework.integration.jmx.OperationInvokingMessageHandler"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "mbean-server", "server"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "server"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "object-name"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "operation-name"); return builder.getBeanDefinition(); diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayParser.java b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayParser.java index fd05a28fd5..086d9e1825 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayParser.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/jmx/config/OperationInvokingOutboundGatewayParser.java @@ -37,7 +37,7 @@ public class OperationInvokingOutboundGatewayParser extends AbstractConsumerEndp ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition( "org.springframework.integration.jmx.OperationInvokingMessageHandler"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "mbean-server", "server"); + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "server"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "object-name"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "operation-name"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "outputChannel"); diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMetrics.java similarity index 80% rename from spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMonitor.java rename to spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMetrics.java index eb4f549a66..8c4f780dd1 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMonitor.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMetrics.java @@ -20,9 +20,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; -import org.springframework.jmx.export.annotation.ManagedMetric; import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.jmx.support.MetricType; import org.springframework.util.StopWatch; /** @@ -33,7 +31,7 @@ import org.springframework.util.StopWatch; * @author Helena Edelson */ @ManagedResource -public class DirectChannelMonitor implements MethodInterceptor, MessageChannelMonitor { +public class DirectChannelMetrics implements MethodInterceptor, MessageChannelMetrics { protected final Log logger = LogFactory.getLog(getClass()); @@ -61,7 +59,7 @@ public class DirectChannelMonitor implements MethodInterceptor, MessageChannelMo private final String name; - public DirectChannelMonitor(String name) { + public DirectChannelMetrics(String name) { this.name = name; } @@ -109,7 +107,7 @@ public class DirectChannelMonitor implements MethodInterceptor, MessageChannelMo timer.stop(); if ((Boolean)result) { sendSuccessRatio.success(); - sendDuration.append(timer.getTotalTimeSeconds()); + sendDuration.append(timer.getTotalTimeMillis()); } else { sendSuccessRatio.failure(); sendErrorCount.incrementAndGet(); @@ -130,53 +128,52 @@ public class DirectChannelMonitor implements MethodInterceptor, MessageChannelMo } } } + + public synchronized void reset() { + sendDuration.reset(); + sendErrorRate.reset(); + sendSuccessRatio.reset(); + sendRate.reset(); + sendCount.set(0); + sendErrorCount.set(0); + } - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Sends") public int getSendCount() { return sendCount.get(); } - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Send Errors") public int getSendErrorCount() { return sendErrorCount.get(); } - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Time Since Last Send in Seconds") public double getTimeSinceLastSend() { return sendRate.getTimeSinceLastMeasurement(); } - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Rate per Second") public double getMeanSendRate() { return sendRate.getMean(); } - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Error Rate per Second") public double getMeanErrorRate() { return sendErrorRate.getMean(); } - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Mean Channel Error Ratio per Minute") public double getMeanErrorRatio() { return 1 - sendSuccessRatio.getMean(); } - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Mean Duration") public double getMeanSendDuration() { return sendDuration.getMean(); } - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Min Duration") public double getMinSendDuration() { return sendDuration.getMin(); } - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Max Duration") public double getMaxSendDuration() { return sendDuration.getMax(); } - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Standard Deviation Duration") public double getStandardDeviationSendDuration() { return sendDuration.getStandardDeviation(); } diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/ExponentialMovingAverage.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/ExponentialMovingAverage.java index 8a9de55043..70fd1e725f 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/ExponentialMovingAverage.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/ExponentialMovingAverage.java @@ -25,17 +25,17 @@ package org.springframework.integration.monitor; */ public class ExponentialMovingAverage { - private int count; + private volatile int count; - private double weight; + private volatile double weight; - private double sum; + private volatile double sum; - private double sumSquares; + private volatile double sumSquares; - private double min; + private volatile double min; - private double max; + private volatile double max; private final double decay; @@ -49,12 +49,21 @@ public class ExponentialMovingAverage { this.decay = 1 - 1. / window; } + public synchronized void reset() { + weight = 0; + sum = 0; + sumSquares = 0; + count = 0; + min = 0; + max = 0; + } + /** * Add a new measurement to the series. * * @param value the measurement to append */ - public void append(double value) { + public synchronized void append(double value) { if (value > max || count == 0) max = value; if (value < min || count == 0) diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/ExponentialMovingAverageRate.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/ExponentialMovingAverageRate.java index aa096c29bb..3a4fabfd90 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/ExponentialMovingAverageRate.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/ExponentialMovingAverageRate.java @@ -32,13 +32,13 @@ public class ExponentialMovingAverageRate { private final ExponentialMovingAverage rates; - private double weight; + private volatile double weight; - private double sum; + private volatile double sum; - private double min; + private volatile double min; - private double max; + private volatile double max; private volatile long t0 = System.currentTimeMillis(); @@ -57,10 +57,19 @@ public class ExponentialMovingAverageRate { this.period = period * 1000; // convert to millisecs } + public synchronized void reset() { + min = 0; + max = 0; + weight = 0; + sum = 0; + t0 = System.currentTimeMillis(); + rates.reset(); + } + /** * Add a new event to the series. */ - public void increment() { + public synchronized void increment() { long t = System.currentTimeMillis(); double value = t > t0 ? (t - t0) / period : 0; diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/ExponentialMovingAverageRatio.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/ExponentialMovingAverageRatio.java index 48297f2bb0..f44efd6655 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/ExponentialMovingAverageRatio.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/ExponentialMovingAverageRatio.java @@ -28,9 +28,9 @@ package org.springframework.integration.monitor; */ public class ExponentialMovingAverageRatio { - private double weight; + private volatile double weight; - private double sum; + private volatile double sum; private volatile long t0 = System.currentTimeMillis(); @@ -61,7 +61,14 @@ public class ExponentialMovingAverageRatio { append(0); } - private void append(int value) { + public synchronized void reset() { + weight = 0; + sum = 0; + t0 = System.currentTimeMillis(); + cumulative.reset(); + } + + private synchronized void append(int value) { long t = System.currentTimeMillis(); double alpha = Math.exp((t0 - t) * lapse); @@ -92,7 +99,8 @@ public class ExponentialMovingAverageRatio { public double getMean() { int count = cumulative.getCount(); if (count == 0) { - return 0; + // Optimistic to start: success rate is 100% + return 1; } long t = System.currentTimeMillis(); double alpha = Math.exp((t0 - t) * lapse); diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java index ef5644f7aa..43946ca853 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/IntegrationMBeanExporter.java @@ -13,7 +13,6 @@ package org.springframework.integration.monitor; import java.lang.reflect.Field; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -22,8 +21,10 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReentrantLock; +import org.aopalliance.aop.Advice; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.aop.Advisor; import org.springframework.aop.PointcutAdvisor; import org.springframework.aop.TargetSource; import org.springframework.aop.framework.Advised; @@ -42,6 +43,7 @@ import org.springframework.integration.MessageChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.core.MessageProducer; +import org.springframework.integration.core.MessageSource; import org.springframework.integration.core.PollableChannel; import org.springframework.integration.endpoint.AbstractEndpoint; import org.springframework.jmx.export.MBeanExporter; @@ -87,25 +89,27 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP private static final Log logger = LogFactory.getLog(IntegrationMBeanExporter.class); - public static final String DEFAULT_DOMAIN = "spring.application"; - - private Set channelKeys = new HashSet(); - - private Set handlerKeys = new HashSet(); + public static final String DEFAULT_DOMAIN = "org.springframework.integration"; private final AnnotationJmxAttributeSource attributeSource = new AnnotationJmxAttributeSource(); private ListableBeanFactory beanFactory; - private Map anonymousCounters = new HashMap(); + private Map anonymousHandlerCounters = new HashMap(); - private Set handlers = new HashSet(); + private Map anonymousSourceCounters = new HashMap(); - private Set channels = new HashSet(); + private Set handlers = new HashSet(); - private Map channelsByName = new HashMap(); + private Set sources = new HashSet(); - private Map handlersByName = new HashMap(); + private Set channels = new HashSet(); + + private Map channelsByName = new HashMap(); + + private Map handlersByName = new HashMap(); + + private Map sourcesByName = new HashMap(); private Map objectNamesByName = new HashMap(); @@ -152,7 +156,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP * * @param domain the domain name to set */ - public void setDomain(String domain) { + public void setDefaultDomain(String domain) { this.domain = domain; } @@ -163,35 +167,55 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof MessageHandler) { - SimpleMessageHandlerMonitor monitor = null; - if (bean instanceof MessageProducer){ // we need to maintain semantics of the handler also being a producer see INT-1431 - monitor = new SimpleMessageProducingHandlerMonitor((MessageHandler) bean); - } else { - monitor = new SimpleMessageHandlerMonitor((MessageHandler) bean); - } - handlers.add(monitor); - return monitor; + + if (bean instanceof Advised) { + for (Advisor advisor : ((Advised) bean).getAdvisors()) { + Advice advice = advisor.getAdvice(); + if (advice instanceof MessageHandlerMetrics || advice instanceof MessageSourceMetrics + || advice instanceof MessageChannelMetrics) { + // Already advised - so probably a factory bean product + return bean; + } + } } + + if (bean instanceof MessageHandler) { + SimpleMessageHandlerMetrics monitor = null; + if (bean instanceof MessageProducer) { + // We need to maintain semantics of the handler also being a producer + monitor = new SimpleMessageProducingHandlerMetrics((MessageHandler) bean); + } else { + monitor = new SimpleMessageHandlerMetrics((MessageHandler) bean); + } + Object advised = applyHandlerInterceptor(bean, monitor, beanClassLoader); + handlers.add(monitor); + return advised; + } else if (bean instanceof MessageSource) { + SimpleMessageSourceMetrics monitor = new SimpleMessageSourceMetrics((MessageSource) bean); + Object advised = applySourceInterceptor(bean, monitor, beanClassLoader); + sources.add(monitor); + return advised; + } + if (bean instanceof MessageChannel) { - DirectChannelMonitor monitor; + DirectChannelMetrics monitor; if (bean instanceof PollableChannel) { Object target = extractTarget(bean); if (target instanceof QueueChannel) { - monitor = new QueueChannelMonitor((QueueChannel) target, beanName); + monitor = new QueueChannelMetrics((QueueChannel) target, beanName); + } else { + monitor = new PollableChannelMetrics(beanName); } - else { - monitor = new PollableChannelMonitor(beanName); - } - } - else { - monitor = new DirectChannelMonitor(beanName); + } else { + monitor = new DirectChannelMetrics(beanName); } Object advised = applyChannelInterceptor(bean, monitor, beanClassLoader); channels.add(monitor); return advised; } + return bean; + } public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { @@ -215,8 +239,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP this.lifecycleLock.lock(); try { return this.running; - } - finally { + } finally { this.lifecycleLock.unlock(); } } @@ -231,8 +254,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP logger.info("started " + this); } } - } - finally { + } finally { this.lifecycleLock.unlock(); } } @@ -247,8 +269,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP logger.info("stopped " + this); } } - } - finally { + } finally { this.lifecycleLock.unlock(); } } @@ -258,50 +279,74 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP try { this.stop(); callback.run(); - } - finally { + } finally { this.lifecycleLock.unlock(); } } protected void doStop() { + unregisterBeans(); + channelsByName.clear(); + handlersByName.clear(); + sourcesByName.clear(); } protected void doStart() { registerChannels(); registerHandlers(); + registerSources(); logger.info("Summary on start: " + objectNamesByName); } @Override public void destroy() { super.destroy(); - for (MessageChannelMonitor monitor : channels) { + for (MessageChannelMetrics monitor : channels) { logger.info("Summary on shutdown: " + monitor); } - for (MessageHandlerMonitor monitor : handlers) { + for (MessageHandlerMetrics monitor : handlers) { logger.info("Summary on shutdown: " + monitor); } } @ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Channel Count") - public double getChannelCount() { - return channelKeys.size(); + public int getChannelCount() { + return channelsByName.size(); } @ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageHandler Handler Count") - public double getHandlerCount() { - return handlerKeys.size(); + public int getHandlerCount() { + return handlersByName.size(); } @ManagedAttribute - public Collection getHandlerNames() { - return handlersByName.keySet(); + public String[] getHandlerNames() { + return handlersByName.keySet().toArray(new String[0]); + } + + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Active Handler Count") + public int getActiveHandlerCount() { + int count = 0; + for (MessageHandlerMetrics monitor : handlers) { + count += monitor.getActiveCount(); + } + return count; + } + + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Queued Message Count") + public int getQueuedMessageCount() { + int count = 0; + for (MessageChannelMetrics monitor : channels) { + if (monitor instanceof QueueChannelMetrics) { + count += ((QueueChannelMetrics) monitor).getQueueSize(); + } + } + return count; } @ManagedAttribute - public Collection getChannelNames() { - return channelsByName.keySet(); + public String[] getChannelNames() { + return channelsByName.keySet().toArray(new String[0]); } @ManagedOperation(description = "Get the JMX object name (as a String) for the specified Spring bean name") @@ -324,8 +369,8 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP public int getChannelReceiveCount(String name) { if (channelsByName.containsKey(name)) { - if (channelsByName.get(name) instanceof PollableChannelMonitor) { - return ((PollableChannelMonitor) channelsByName.get(name)).getReceiveCount(); + if (channelsByName.get(name) instanceof PollableChannelMetrics) { + return ((PollableChannelMetrics) channelsByName.get(name)).getReceiveCount(); } } logger.debug("No channel found for (" + name + ")"); @@ -349,7 +394,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } private void registerChannels() { - for (DirectChannelMonitor monitor : channels) { + for (DirectChannelMetrics monitor : channels) { String name = monitor.getName(); // Only register once... if (!channelsByName.containsKey(name)) { @@ -365,8 +410,8 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } private void registerHandlers() { - for (SimpleMessageHandlerMonitor source : handlers) { - MessageHandlerMonitor monitor = enhanceMonitor(source); + for (SimpleMessageHandlerMetrics source : handlers) { + MessageHandlerMetrics monitor = enhanceHandlerMonitor(source); String name = monitor.getName(); // Only register once... if (!handlersByName.containsKey(name)) { @@ -380,13 +425,43 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } } - private Object applyChannelInterceptor(Object bean, DirectChannelMonitor interceptor, ClassLoader beanClassLoader) { + private void registerSources() { + for (SimpleMessageSourceMetrics source : sources) { + MessageSourceMetrics monitor = enhanceSourceMonitor(source); + String name = monitor.getName(); + // Only register once... + if (!sourcesByName.containsKey(name)) { + String beanKey = getSourceBeanKey(monitor); + if (name != null) { + sourcesByName.put(name, monitor); + objectNamesByName.put(name, beanKey); + } + registerBeanNameOrInstance(monitor, beanKey); + } + } + } + + private Object applyChannelInterceptor(Object bean, DirectChannelMetrics interceptor, ClassLoader beanClassLoader) { NameMatchMethodPointcutAdvisor channelsAdvice = new NameMatchMethodPointcutAdvisor(interceptor); channelsAdvice.addMethodName("send"); channelsAdvice.addMethodName("receive"); return applyAdvice(bean, channelsAdvice, beanClassLoader); } + private Object applyHandlerInterceptor(Object bean, SimpleMessageHandlerMetrics interceptor, + ClassLoader beanClassLoader) { + NameMatchMethodPointcutAdvisor handlerAdvice = new NameMatchMethodPointcutAdvisor(interceptor); + handlerAdvice.addMethodName("handleMessage"); + return applyAdvice(bean, handlerAdvice, beanClassLoader); + } + + private Object applySourceInterceptor(Object bean, SimpleMessageSourceMetrics interceptor, + ClassLoader beanClassLoader) { + NameMatchMethodPointcutAdvisor sourceAdvice = new NameMatchMethodPointcutAdvisor(interceptor); + sourceAdvice.addMethodName("receive"); + return applyAdvice(bean, sourceAdvice, beanClassLoader); + } + private Object extractTarget(Object bean) { if (!(bean instanceof Advised)) { return bean; @@ -397,8 +472,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } try { return extractTarget(advised.getTargetSource().getTarget()); - } - catch (Exception e) { + } catch (Exception e) { logger.error("Could not extract target", e); return null; } @@ -410,8 +484,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP if (bean instanceof Advised) { ((Advised) bean).addAdvisor(advisor); return bean; - } - else { + } else { ProxyFactory proxyFactory = new ProxyFactory(bean); proxyFactory.addAdvisor(advisor); return proxyFactory.getProxy(beanClassLoader); @@ -428,12 +501,18 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP return String.format(domain + ":type=MessageChannel,name=%s" + getStaticNames(), name); } - private String getHandlerBeanKey(MessageHandlerMonitor handler) { + private String getHandlerBeanKey(MessageHandlerMetrics handler) { // This ordering of keys seems to work with default settings of JConsole return String.format(domain + ":type=MessageHandler,name=%s,bean=%s" + getStaticNames(), handler.getName(), handler.getSource()); } + private String getSourceBeanKey(MessageSourceMetrics handler) { + // This ordering of keys seems to work with default settings of JConsole + return String.format(domain + ":type=MessageSource,name=%s,bean=%s" + getStaticNames(), handler.getName(), + handler.getSource()); + } + private String getStaticNames() { if (objectNameStaticProperties.isEmpty()) { return ""; @@ -445,9 +524,9 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP return builder.toString(); } - private MessageHandlerMonitor enhanceMonitor(SimpleMessageHandlerMonitor monitor) { + private MessageHandlerMetrics enhanceHandlerMonitor(SimpleMessageHandlerMetrics monitor) { - MessageHandlerMonitor result = monitor; + MessageHandlerMetrics result = monitor; if (monitor.getName() != null && monitor.getSource() != null) { return monitor; @@ -464,12 +543,11 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP endpoint = beanFactory.getBean(beanName); Object field = null; try { - field = getField(endpoint, "handler"); + field = extractTarget(getField(endpoint, "handler")); + } catch (Exception e) { + logger.trace("Could not get handler from bean = " + beanName); } - catch (Exception e) { - logger.debug("Could not get handler from bean = " + beanName); - } - if (field == monitor) { + if (field == monitor.getMessageHandler()) { name = beanName; break; } @@ -485,18 +563,17 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP if (targetSource != null) { try { target = targetSource.getTarget(); - } - catch (Exception e) { + } catch (Exception e) { logger.debug("Could not get handler from bean = " + name); } } } Object field = getField(target, "inputChannel"); if (field != null) { - if (!anonymousCounters.containsKey(field)) { - anonymousCounters.put(field, new AtomicLong()); + if (!anonymousHandlerCounters.containsKey(field)) { + anonymousHandlerCounters.put(field, new AtomicLong()); } - AtomicLong count = anonymousCounters.get(field); + AtomicLong count = anonymousHandlerCounters.get(field); long total = count.incrementAndGet(); String suffix = ""; /* @@ -512,7 +589,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP if (endpoint instanceof Lifecycle) { // Wrap the monitor in a lifecycle so it exposes the start/stop operations - result = new LifecycleMessageHandlerMonitor((Lifecycle) endpoint, monitor); + result = new LifecycleMessageHandlerMetrics((Lifecycle) endpoint, monitor); } if (name == null) { @@ -527,6 +604,86 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } + private MessageSourceMetrics enhanceSourceMonitor(SimpleMessageSourceMetrics monitor) { + + MessageSourceMetrics result = monitor; + + if (monitor.getName() != null && monitor.getSource() != null) { + return monitor; + } + + // Assignment algorithm and bean id, with bean id pulled reflectively out of enclosing endpoint if possible + String[] names = beanFactory.getBeanNamesForType(AbstractEndpoint.class); + + String name = null; + String source = "endpoint"; + Object endpoint = null; + + for (String beanName : names) { + endpoint = beanFactory.getBean(beanName); + Object field = null; + try { + field = extractTarget(getField(endpoint, "source")); + } catch (Exception e) { + logger.trace("Could not get source from bean = " + beanName); + } + if (field == monitor.getMessageSource()) { + name = beanName; + break; + } + } + if (name != null && endpoint != null && name.startsWith("_org.springframework.integration")) { + name = name.substring("_org.springframework.integration".length() + 1); + source = "internal"; + } + if (name != null && endpoint != null && name.startsWith("org.springframework.integration")) { + Object target = endpoint; + if (endpoint instanceof Advised) { + TargetSource targetSource = ((Advised) endpoint).getTargetSource(); + if (targetSource != null) { + try { + target = targetSource.getTarget(); + } catch (Exception e) { + logger.debug("Could not get handler from bean = " + name); + } + } + } + Object field = getField(target, "outputChannel"); + if (field != null) { + if (!anonymousSourceCounters.containsKey(field)) { + anonymousSourceCounters.put(field, new AtomicLong()); + } + AtomicLong count = anonymousSourceCounters.get(field); + long total = count.incrementAndGet(); + String suffix = ""; + /* + * Short hack to makes sure object names are unique if more than one endpoint has the same input channel + */ + if (total > 1) { + suffix = "#" + total; + } + name = field + suffix; + source = "anonymous"; + } + } + + if (endpoint instanceof Lifecycle) { + // Wrap the monitor in a lifecycle so it exposes the start/stop operations + result = new LifecycleMessageSourceMetrics((Lifecycle) endpoint, monitor); + } + + if (name == null) { + name = monitor.getMessageSource().toString(); + source = "handler"; + } + + monitor.setSource(source); + monitor.setName(name); + + return result; + + } + private static Object getField(Object target, String name) { Assert.notNull(target, "Target object must not be null"); Field field = ReflectionUtils.findField(target.getClass(), name); diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMetrics.java similarity index 83% rename from spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMonitor.java rename to spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMetrics.java index c630add493..c50c6ba78c 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMonitor.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMetrics.java @@ -21,7 +21,7 @@ import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; /** - * A {@link MessageHandlerMonitor} that exposes in addition the {@link Lifecycle} interface. The lifecycle methods can + * A {@link MessageHandlerMetrics} that exposes in addition the {@link Lifecycle} interface. The lifecycle methods can * be used to stop and start polling endpoints, for instance, in a live system. * * @author Dave Syer @@ -30,13 +30,13 @@ import org.springframework.jmx.export.annotation.ManagedResource; * */ @ManagedResource -public class LifecycleMessageHandlerMonitor implements MessageHandlerMonitor, Lifecycle { +public class LifecycleMessageHandlerMetrics implements MessageHandlerMetrics, Lifecycle { private final Lifecycle lifecycle; - private final MessageHandlerMonitor delegate; + private final MessageHandlerMetrics delegate; - public LifecycleMessageHandlerMonitor(Lifecycle lifecycle, MessageHandlerMonitor delegate) { + public LifecycleMessageHandlerMetrics(Lifecycle lifecycle, MessageHandlerMetrics delegate) { this.lifecycle = lifecycle; this.delegate = delegate; } @@ -56,6 +56,10 @@ public class LifecycleMessageHandlerMonitor implements MessageHandlerMonitor, Li lifecycle.stop(); } + public void reset() { + delegate.reset(); + } + public int getErrorCount() { return delegate.getErrorCount(); } @@ -92,4 +96,8 @@ public class LifecycleMessageHandlerMonitor implements MessageHandlerMonitor, Li return delegate.getSource(); } + public int getActiveCount() { + return delegate.getActiveCount(); + } + } diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageSourceMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageSourceMetrics.java new file mode 100644 index 0000000000..5fcb8cefbf --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageSourceMetrics.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2010 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.monitor; + +import org.springframework.context.Lifecycle; +import org.springframework.jmx.export.annotation.ManagedAttribute; +import org.springframework.jmx.export.annotation.ManagedOperation; +import org.springframework.jmx.export.annotation.ManagedResource; + +/** + * A {@link MessageSourceMetrics} that exposes in addition the {@link Lifecycle} interface. The lifecycle methods can + * be used to stop and start polling endpoints, for instance, in a live system. + * + * @author Dave Syer + * + * @since 2.0 + * + */ +@ManagedResource +public class LifecycleMessageSourceMetrics implements MessageSourceMetrics, Lifecycle { + + private final Lifecycle lifecycle; + + private final MessageSourceMetrics delegate; + + public LifecycleMessageSourceMetrics(Lifecycle lifecycle, MessageSourceMetrics delegate) { + this.lifecycle = lifecycle; + this.delegate = delegate; + } + + @ManagedOperation + public void reset() { + delegate.reset(); + } + + @ManagedAttribute + public boolean isRunning() { + return lifecycle.isRunning(); + } + + @ManagedOperation + public void start() { + lifecycle.start(); + } + + @ManagedOperation + public void stop() { + lifecycle.stop(); + } + + public String getName() { + return delegate.getName(); + } + + public String getSource() { + return delegate.getSource(); + } + + /** + * @return + * @see org.springframework.integration.monitor.MessageSourceMetrics#getMessageCount() + */ + public int getMessageCount() { + return delegate.getMessageCount(); + } + +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageChannelMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageChannelMetrics.java similarity index 95% rename from spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageChannelMonitor.java rename to spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageChannelMetrics.java index bf5ceec4f5..51c04335d6 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageChannelMonitor.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageChannelMetrics.java @@ -16,6 +16,7 @@ package org.springframework.integration.monitor; import org.springframework.jmx.export.annotation.ManagedMetric; +import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.support.MetricType; /** @@ -27,7 +28,10 @@ import org.springframework.jmx.support.MetricType; * @since 2.0 * */ -public interface MessageChannelMonitor { +public interface MessageChannelMetrics { + + @ManagedOperation + void reset(); /** * @return the number of successful sends diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMetrics.java similarity index 81% rename from spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMonitor.java rename to spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMetrics.java index 4e06cfc904..30006df772 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMonitor.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMetrics.java @@ -16,6 +16,7 @@ package org.springframework.integration.monitor; import org.springframework.jmx.export.annotation.ManagedMetric; +import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.support.MetricType; /** @@ -23,41 +24,47 @@ import org.springframework.jmx.support.MetricType; * * @since 2.0 */ -public interface MessageHandlerMonitor { +public interface MessageHandlerMetrics { + + @ManagedOperation + void reset(); /** * @return the number of successful handler calls */ - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Handler Execution Count", description = "rate=1h") + @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Handler Execution Count") int getHandleCount(); /** * @return the number of failed handler calls */ - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Handler Error Count", description = "rate=1h") + @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Handler Error Count") int getErrorCount(); /** * @return the maximum handler duration (milliseconds) */ - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Mean Duration") + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Mean Duration (ms)") double getMeanDuration(); /** * @return the minimum handler duration (milliseconds) */ - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Min Duration") + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Min Duration (ms)") double getMinDuration(); /** * @return the standard deviation handler duration (milliseconds) */ - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Max Duration") + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Max Duration (ms)") double getMaxDuration(); - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Standard Deviation Duration") + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Standard Deviation Duration (ms)") double getStandardDeviationDuration(); + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Active Count") + int getActiveCount(); + /** * @return summary statistics about the handler duration (milliseconds) */ diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageSourceMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageSourceMetrics.java new file mode 100644 index 0000000000..b1984b37e4 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageSourceMetrics.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2010 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.monitor; + +import org.springframework.jmx.export.annotation.ManagedMetric; +import org.springframework.jmx.export.annotation.ManagedOperation; +import org.springframework.jmx.support.MetricType; + +/** + * @author Dave Syer + * + * @since 2.0 + */ +public interface MessageSourceMetrics { + + @ManagedOperation + void reset(); + + /** + * @return the number of successful handler calls + */ + @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Message Source Message Count") + int getMessageCount(); + + String getName(); + + String getSource(); + +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/PollableChannelMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/PollableChannelMetrics.java similarity index 88% rename from spring-integration-jmx/src/main/java/org/springframework/integration/monitor/PollableChannelMonitor.java rename to spring-integration-jmx/src/main/java/org/springframework/integration/monitor/PollableChannelMetrics.java index 9bcb444d4b..d6731dbef8 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/PollableChannelMonitor.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/PollableChannelMetrics.java @@ -20,6 +20,7 @@ import java.util.concurrent.atomic.AtomicInteger; import org.aopalliance.intercept.MethodInvocation; import org.springframework.integration.MessageChannel; import org.springframework.jmx.export.annotation.ManagedMetric; +import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.support.MetricType; /** @@ -28,7 +29,7 @@ import org.springframework.jmx.support.MetricType; * @since 2.0 * */ -public class PollableChannelMonitor extends DirectChannelMonitor { +public class PollableChannelMetrics extends DirectChannelMetrics { private final AtomicInteger receiveCount = new AtomicInteger(); @@ -37,7 +38,7 @@ public class PollableChannelMonitor extends DirectChannelMonitor { /** * @param name */ - public PollableChannelMonitor(String name) { + public PollableChannelMetrics(String name) { super(name); } @@ -67,6 +68,13 @@ public class PollableChannelMonitor extends DirectChannelMonitor { } } + @ManagedOperation + public synchronized void reset() { + super.reset(); + receiveErrorCount.set(0); + receiveCount.set(0); + } + @ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Receives") public int getReceiveCount() { return receiveCount.get(); diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/QueueChannelMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/QueueChannelMetrics.java similarity index 91% rename from spring-integration-jmx/src/main/java/org/springframework/integration/monitor/QueueChannelMonitor.java rename to spring-integration-jmx/src/main/java/org/springframework/integration/monitor/QueueChannelMetrics.java index edf528b12a..d3764b7f9d 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/QueueChannelMonitor.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/QueueChannelMetrics.java @@ -25,14 +25,14 @@ import org.springframework.jmx.support.MetricType; * @since 2.0 * */ -public class QueueChannelMonitor extends PollableChannelMonitor { +public class QueueChannelMetrics extends PollableChannelMetrics { private final QueueChannel channel; /** * @param name */ - public QueueChannelMonitor(QueueChannel channel, String name) { + public QueueChannelMetrics(QueueChannel channel, String name) { super(name); this.channel = channel; } diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMetrics.java similarity index 75% rename from spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMonitor.java rename to spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMetrics.java index ab6fa1c9a8..9dc052f9db 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMonitor.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMetrics.java @@ -17,6 +17,8 @@ package org.springframework.integration.monitor; import java.util.concurrent.atomic.AtomicInteger; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.integration.Message; @@ -24,9 +26,7 @@ import org.springframework.integration.MessageDeliveryException; import org.springframework.integration.MessageHandlingException; import org.springframework.integration.MessageRejectedException; import org.springframework.integration.core.MessageHandler; -import org.springframework.jmx.export.annotation.ManagedMetric; import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.jmx.support.MetricType; import org.springframework.util.StopWatch; /** @@ -36,14 +36,16 @@ import org.springframework.util.StopWatch; * */ @ManagedResource -public class SimpleMessageHandlerMonitor implements MessageHandler, MessageHandlerMonitor { +public class SimpleMessageHandlerMetrics implements MethodInterceptor, MessageHandlerMetrics { - private static final Log logger = LogFactory.getLog(SimpleMessageHandlerMonitor.class); + private static final Log logger = LogFactory.getLog(SimpleMessageHandlerMetrics.class); private static final int DEFAULT_MOVING_AVERAGE_WINDOW = 10; private final MessageHandler handler; + private final AtomicInteger activeCount = new AtomicInteger(); + private final AtomicInteger handleCount = new AtomicInteger(); private final AtomicInteger errorCount = new AtomicInteger(); @@ -55,7 +57,7 @@ public class SimpleMessageHandlerMonitor implements MessageHandler, MessageHandl private String source; - public SimpleMessageHandlerMonitor(MessageHandler handler) { + public SimpleMessageHandlerMetrics(MessageHandler handler) { this.handler = handler; } @@ -79,7 +81,17 @@ public class SimpleMessageHandlerMonitor implements MessageHandler, MessageHandl return handler; } - public void handleMessage(Message message) throws MessageRejectedException, MessageHandlingException, + public Object invoke(MethodInvocation invocation) throws Throwable { + String method = invocation.getMethod().getName(); + if ("handleMessage".equals(method)) { + Message message = (Message) invocation.getArguments()[0]; + handleMessage(message); + return null; + } + return invocation.proceed(); + } + + private void handleMessage(Message message) throws MessageRejectedException, MessageHandlingException, MessageDeliveryException { if (logger.isTraceEnabled()) { logger.trace("messageHandler(" + handler + ") message(" + message + ") :"); @@ -94,21 +106,29 @@ public class SimpleMessageHandlerMonitor implements MessageHandler, MessageHandl try { timer.start(); handleCount.incrementAndGet(); + activeCount.incrementAndGet(); handler.handleMessage(message); timer.stop(); - duration.append(timer.getTotalTimeSeconds()); + duration.append(timer.getTotalTimeMillis()); } catch (RuntimeException e) { errorCount.incrementAndGet(); throw e; } catch (Error e) { errorCount.incrementAndGet(); throw e; + } finally { + activeCount.decrementAndGet(); } } - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Handler Execution Count", description = "rate=1h") + public synchronized void reset() { + duration.reset(); + errorCount.set(0); + handleCount.set(0); + } + public int getHandleCount() { if (logger.isTraceEnabled()) { logger.trace("Getting Handle Count:" + this); @@ -116,31 +136,30 @@ public class SimpleMessageHandlerMonitor implements MessageHandler, MessageHandl return handleCount.get(); } - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Handler Error Count", description = "rate=1h") public int getErrorCount() { return errorCount.get(); } - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Mean Duration") public double getMeanDuration() { return duration.getMean(); } - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Min Duration") public double getMinDuration() { return duration.getMin(); } - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Max Duration") public double getMaxDuration() { return duration.getMax(); } - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Standard Deviation Duration") public double getStandardDeviationDuration() { return duration.getStandardDeviation(); } + public int getActiveCount() { + return activeCount.get(); + } + public Statistics getDuration() { return duration.getStatistics(); } diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageProducingHandlerMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageProducingHandlerMetrics.java similarity index 86% rename from spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageProducingHandlerMonitor.java rename to spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageProducingHandlerMetrics.java index baee4af7e2..aaa0bd632e 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageProducingHandlerMonitor.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageProducingHandlerMetrics.java @@ -26,9 +26,9 @@ import org.springframework.jmx.export.annotation.ManagedResource; * */ @ManagedResource -public class SimpleMessageProducingHandlerMonitor extends SimpleMessageHandlerMonitor implements MessageProducer { +public class SimpleMessageProducingHandlerMetrics extends SimpleMessageHandlerMetrics implements MessageProducer { - public SimpleMessageProducingHandlerMonitor(MessageHandler handler) { + public SimpleMessageProducingHandlerMetrics(MessageHandler handler) { super(handler); } diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMetrics.java new file mode 100644 index 0000000000..0edda2aa6e --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMetrics.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2010 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.monitor; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.integration.core.MessageSource; + +/** + * @author Dave Syer + * + * @since 2.0 + */ +public class SimpleMessageSourceMetrics implements MethodInterceptor, MessageSourceMetrics { + + private final AtomicInteger messageCount = new AtomicInteger(); + + private final MessageSource messageSource; + + private String source; + + private String name; + + public SimpleMessageSourceMetrics(MessageSource messageSource) { + this.messageSource = messageSource; + } + + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setSource(String source) { + this.source = source; + } + + public String getSource() { + return this.source; + } + + public MessageSource getMessageSource() { + return messageSource; + } + + public void reset() { + messageCount.set(0); + } + + public int getMessageCount() { + return messageCount.get(); + } + + public Object invoke(MethodInvocation invocation) throws Throwable { + String method = invocation.getMethod().getName(); + Object result = invocation.proceed(); + if ("receive".equals(method) && result!=null) { + messageCount.incrementAndGet(); + } + return result; + } + + @Override + public String toString() { + return String.format("MessageSourceMonitor: [name=%s, source=%s, count=%d]", name, source, messageCount.get()); + } + +} diff --git a/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-2.0.xsd b/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-2.0.xsd index 7ba105b519..bcff9d33df 100644 --- a/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-2.0.xsd +++ b/spring-integration-jmx/src/main/resources/org/springframework/integration/jmx/config/spring-integration-jmx-2.0.xsd @@ -96,7 +96,7 @@ - + Exports Message Channels and Endpoints as MBeans. @@ -105,7 +105,7 @@ - + The domain name for the MBeans exported by this Exporter. @@ -186,7 +186,7 @@ - + Defines the name of the MBeanServer bean to connect to. diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusOperationChannelTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusOperationChannelTests.java index e11150b383..4bafcf3187 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusOperationChannelTests.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusOperationChannelTests.java @@ -82,7 +82,7 @@ public class ControlBusOperationChannelTests { context.registerBeanDefinition("mbeanServer", serverDef); BeanDefinition exporterDef = new RootBeanDefinition(IntegrationMBeanExporter.class); exporterDef.getPropertyValues().addPropertyValue("server", new RuntimeBeanReference("mbeanServer")); - exporterDef.getPropertyValues().addPropertyValue("domain", domain); + exporterDef.getPropertyValues().addPropertyValue("defaultDomain", domain); context.registerBeanDefinition("exporter", exporterDef); BeanDefinition controlBusDef = new RootBeanDefinition(ControlBus.class); controlBusDef.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference("mbeanServer")); diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusTests.java index 2dcfb6c44a..ff28cdec6c 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusTests.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusTests.java @@ -43,9 +43,9 @@ import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.endpoint.PollingConsumer; import org.springframework.integration.handler.BridgeHandler; import org.springframework.integration.monitor.IntegrationMBeanExporter; -import org.springframework.integration.monitor.LifecycleMessageHandlerMonitor; -import org.springframework.integration.monitor.QueueChannelMonitor; -import org.springframework.integration.monitor.DirectChannelMonitor; +import org.springframework.integration.monitor.LifecycleMessageHandlerMetrics; +import org.springframework.integration.monitor.QueueChannelMetrics; +import org.springframework.integration.monitor.DirectChannelMetrics; import org.springframework.integration.scheduling.PollerMetadata; import org.springframework.jmx.support.MBeanServerFactoryBean; import org.springframework.jmx.support.ObjectNameManager; @@ -88,7 +88,7 @@ public class ControlBusTests { MBeanServer mbeanServer = context.getBean("mbeanServer", MBeanServer.class); ObjectInstance instance = mbeanServer.getObjectInstance(ObjectNameManager .getInstance("domain.test1:type=MessageChannel,name=directChannel")); - assertEquals(DirectChannelMonitor.class.getName(), instance.getClassName()); + assertEquals(DirectChannelMetrics.class.getName(), instance.getClassName()); } @Test @@ -101,7 +101,7 @@ public class ControlBusTests { ObjectInstance instance = mbeanServer .getObjectInstance(ObjectNameManager .getInstance("domain.test1b:type=MessageChannel,name=org.springframework.integration.generated#0,source=anonymous")); - assertEquals(DirectChannelMonitor.class.getName(), instance.getClassName()); + assertEquals(DirectChannelMetrics.class.getName(), instance.getClassName()); } @Test @@ -113,7 +113,7 @@ public class ControlBusTests { MBeanServer mbeanServer = context.getBean("mbeanServer", MBeanServer.class); ObjectInstance instance = mbeanServer.getObjectInstance(ObjectNameManager .getInstance("domain.test1a:type=MessageChannel,name=directChannel,foo=bar")); - assertEquals(DirectChannelMonitor.class.getName(), instance.getClassName()); + assertEquals(DirectChannelMetrics.class.getName(), instance.getClassName()); } @Test @@ -124,7 +124,7 @@ public class ControlBusTests { MBeanServer mbeanServer = context.getBean("mbeanServer", MBeanServer.class); ObjectInstance instance = mbeanServer.getObjectInstance(ObjectNameManager .getInstance("domain.test2:type=MessageChannel,name=queueChannel")); - assertEquals(QueueChannelMonitor.class.getName(), instance.getClassName()); + assertEquals(QueueChannelMetrics.class.getName(), instance.getClassName()); } @Test @@ -139,7 +139,7 @@ public class ControlBusTests { MBeanServer mbeanServer = context.getBean("mbeanServer", MBeanServer.class); ObjectInstance instance = mbeanServer.getObjectInstance(ObjectNameManager .getInstance("domain.test3:type=MessageHandler,name=eventDrivenConsumer,bean=endpoint")); - assertEquals(LifecycleMessageHandlerMonitor.class.getName(), instance.getClassName()); + assertEquals(LifecycleMessageHandlerMetrics.class.getName(), instance.getClassName()); } @Test @@ -158,7 +158,7 @@ public class ControlBusTests { MBeanServer mbeanServer = context.getBean("mbeanServer", MBeanServer.class); ObjectInstance instance = mbeanServer.getObjectInstance(ObjectNameManager .getInstance("domain.test4:type=MessageHandler,name=pollingConsumer,bean=endpoint")); - assertEquals(LifecycleMessageHandlerMonitor.class.getName(), instance.getClassName()); + assertEquals(LifecycleMessageHandlerMetrics.class.getName(), instance.getClassName()); } @Test @@ -188,7 +188,7 @@ public class ControlBusTests { private BeanDefinition registerControlBus(GenericApplicationContext context, String domain) { BeanDefinition exporterDef = new RootBeanDefinition(IntegrationMBeanExporter.class); exporterDef.getPropertyValues().addPropertyValue("server", new RuntimeBeanReference("mbeanServer")); - exporterDef.getPropertyValues().addPropertyValue("domain", domain); + exporterDef.getPropertyValues().addPropertyValue("defaultDomain", domain); context.registerBeanDefinition("exporter", exporterDef); BeanDefinition controlBusDef = new RootBeanDefinition(ControlBus.class); controlBusDef.getConstructorArgumentValues().addGenericArgumentValue(new RuntimeBeanReference("mbeanServer")); diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusXmlTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusXmlTests-context.xml index d67f90ed0b..ce73230e21 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusXmlTests-context.xml +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusXmlTests-context.xml @@ -29,7 +29,7 @@ - + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusXmlTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusXmlTests.java index af5487c67b..d726617b78 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusXmlTests.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusXmlTests.java @@ -26,9 +26,9 @@ import javax.management.ObjectInstance; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.integration.monitor.LifecycleMessageHandlerMonitor; -import org.springframework.integration.monitor.QueueChannelMonitor; -import org.springframework.integration.monitor.DirectChannelMonitor; +import org.springframework.integration.monitor.LifecycleMessageHandlerMetrics; +import org.springframework.integration.monitor.QueueChannelMetrics; +import org.springframework.integration.monitor.DirectChannelMetrics; import org.springframework.jmx.support.ObjectNameManager; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -51,21 +51,21 @@ public class ControlBusXmlTests { public void directChannelRegistered() throws Exception { ObjectInstance instance = mbeanServer.getObjectInstance( ObjectNameManager.getInstance(DOMAIN + ":type=MessageChannel,name=testDirectChannel")); - assertEquals(DirectChannelMonitor.class.getName(), instance.getClassName()); + assertEquals(DirectChannelMetrics.class.getName(), instance.getClassName()); } @Test public void queueChannelRegistered() throws Exception { ObjectInstance instance = mbeanServer.getObjectInstance( ObjectNameManager.getInstance(DOMAIN + ":type=MessageChannel,name=testQueueChannel")); - assertEquals(QueueChannelMonitor.class.getName(), instance.getClassName()); + assertEquals(QueueChannelMetrics.class.getName(), instance.getClassName()); } @Test public void eventDrivenConsumerRegistered() throws Exception { ObjectInstance instance = mbeanServer.getObjectInstance( ObjectNameManager.getInstance(DOMAIN + ":type=MessageHandler,name=testEventDrivenBridge,bean=endpoint")); - assertEquals(LifecycleMessageHandlerMonitor.class.getName(), instance.getClassName()); + assertEquals(LifecycleMessageHandlerMetrics.class.getName(), instance.getClassName()); } @Test @@ -73,14 +73,14 @@ public class ControlBusXmlTests { Set instances = mbeanServer.queryMBeans( ObjectNameManager.getInstance(DOMAIN + ":type=MessageHandler,bean=anonymous,*"), null); assertEquals(1, instances.size()); - assertEquals(LifecycleMessageHandlerMonitor.class.getName(), instances.iterator().next().getClassName()); + assertEquals(LifecycleMessageHandlerMetrics.class.getName(), instances.iterator().next().getClassName()); } @Test public void pollingConsumerRegistered() throws Exception { ObjectInstance instance = mbeanServer.getObjectInstance( ObjectNameManager.getInstance(DOMAIN + ":type=MessageHandler,name=testPollingBridge,bean=endpoint")); - assertEquals(LifecycleMessageHandlerMonitor.class.getName(), instance.getClassName()); + assertEquals(LifecycleMessageHandlerMetrics.class.getName(), instance.getClassName()); } } diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/AttributePollingChannelAdapterParserTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/AttributePollingChannelAdapterParserTests-context.xml index 5381afcfe2..a393f0ad92 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/AttributePollingChannelAdapterParserTests-context.xml +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/AttributePollingChannelAdapterParserTests-context.xml @@ -25,9 +25,7 @@ object-name="org.springframework.integration.jmx.config:type=TestBean,name=testBean1" attribute-name="FirstMessage" auto-startup="false"> - - - + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/ControlBusParserTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/ControlBusParserTests-context.xml index f79bf94597..998d5b86fe 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/ControlBusParserTests-context.xml +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/ControlBusParserTests-context.xml @@ -19,6 +19,6 @@ - + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanExporterParserTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanExporterParserTests-context.xml index c19acfc5d3..a0f3e5e671 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanExporterParserTests-context.xml +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanExporterParserTests-context.xml @@ -17,6 +17,6 @@ - + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanExporterParserTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanExporterParserTests.java index 6d76d6a468..01cd5bceb9 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanExporterParserTests.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanExporterParserTests.java @@ -40,7 +40,7 @@ public class MBeanExporterParserTests { private ApplicationContext context; @Test - public void test() throws InterruptedException { + public void testMBeanExporterExists() throws InterruptedException { IntegrationMBeanExporter exporter = this.context.getBean(IntegrationMBeanExporter.class); MBeanServer server = this.context.getBean("mbs", MBeanServer.class); assertEquals(server, exporter.getServer()); diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanRegistrationTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanRegistrationTests-context.xml new file mode 100644 index 0000000000..8d03cc4a5c --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanRegistrationTests-context.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanRegistrationTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanRegistrationTests.java new file mode 100644 index 0000000000..01668ebf68 --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanRegistrationTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2010 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.jmx.config; + +import static org.junit.Assert.assertEquals; + +import java.util.Set; + +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Dave Syer + * @since 2.0 + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class MBeanRegistrationTests { + + @Autowired + private MBeanServer server; + + @Test + public void testHandlerMBeanRegistration() throws Exception { + Set names = server.queryNames(new ObjectName("test.MBeanRegistration:type=MessageHandler,*"), null); + assertEquals(3, names.size()); + } + + @Test + public void testExporterMBeanRegistration() throws Exception { + // System.err.println(server.queryNames(new ObjectName("*:type=*MBeanExporter,*"), null)); + // System.err.println(Arrays.asList(server.getMBeanInfo(server.queryNames(new ObjectName("*:type=*Handler,*"), null).iterator().next()).getAttributes())); + Set names = server.queryNames(new ObjectName("test.MBeanRegistration:type=IntegrationMBeanExporter,name=integrationMbeanExporter,*"), null); + assertEquals(1, names.size()); + } + + public static class Source { + public String get() { + return "foo"; + } + } + +} diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests-context.xml new file mode 100644 index 0000000000..b9ae8c7926 --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests-context.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests.java new file mode 100644 index 0000000000..25faf51a4d --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2010 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.jmx.config; + +import static org.junit.Assert.assertEquals; + +import java.util.Set; + +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Dave Syer + * @since 2.0 + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class PollingAdapterMBeanTests { + + @Autowired + private MBeanServer server; + + @Test + public void testMessageSourceMBeanExists() throws Exception { + // System.err.println(server.queryNames(new ObjectName("*:type=MessageSource,*"), null)); + Set names = server.queryNames(new ObjectName("test.PollingAdapterMBean:type=MessageSource,*"), null); + assertEquals(1, names.size()); + } + + public static class Source { + public String get() { + return "foo"; + } + } + +} diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/RouterMBeanTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/RouterMBeanTests-context.xml new file mode 100644 index 0000000000..7947c69fc1 --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/RouterMBeanTests-context.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/RouterMBeanTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/RouterMBeanTests.java new file mode 100644 index 0000000000..cdeedd62e8 --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/RouterMBeanTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2010 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.jmx.config; + +import static org.junit.Assert.assertEquals; + +import java.util.Set; + +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Dave Syer + * @since 2.0 + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class RouterMBeanTests { + + @Autowired + private MBeanServer server; + + @Test + public void testRouterMBeanExists() throws Exception { + Set names = server.queryNames(new ObjectName("test.RouterMBean:type=MessageHandler,name=ptRouter,*"), null); + assertEquals(1, names.size()); + } + + @Test + public void testRouterMBeanOnlyRegisteredOnce() throws Exception { + // System.err.println(server.queryNames(new ObjectName("*:type=MessageHandler,*"), null)); + // The errorLogger and the router + assertEquals(2, server.queryNames(new ObjectName("test.RouterMBean:type=MessageHandler,*"), null).size()); + } + +} diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChainWithMessageProducingHandlersTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChainWithMessageProducingHandlersTests-context.xml index 3bc26ede32..481ec54291 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChainWithMessageProducingHandlersTests-context.xml +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChainWithMessageProducingHandlersTests-context.xml @@ -16,7 +16,7 @@ + p:server-ref="mbeanServer" p:defaultDomain="forum" /> diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChainWithMessageProducingHandlersTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChainWithMessageProducingHandlersTests.java index cb2fa19198..587f3db83e 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChainWithMessageProducingHandlersTests.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChainWithMessageProducingHandlersTests.java @@ -24,7 +24,7 @@ public class ChainWithMessageProducingHandlersTests { private ApplicationContext applicationContext; @Test - public void testSuccessfullApplicationContext(){ + public void testSuccessfulApplicationContext(){ // this is all we need to do. Until INT-1431 was solved initialization of this AC would fail. assertNotNull(applicationContext); } diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChannelIntegrationTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChannelIntegrationTests-context.xml index 564cca97a9..229aebceac 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChannelIntegrationTests-context.xml +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ChannelIntegrationTests-context.xml @@ -15,7 +15,7 @@ - + diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ExponentialMovingAverageRateTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ExponentialMovingAverageRateTests.java index 8b6b96d9c3..682d2bd75b 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ExponentialMovingAverageRateTests.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ExponentialMovingAverageRateTests.java @@ -1,21 +1,19 @@ /* * Copyright 2002-2010 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. + * + * 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.monitor; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.Ignore; @@ -27,8 +25,7 @@ import org.junit.Test; */ public class ExponentialMovingAverageRateTests { - private ExponentialMovingAverageRate history = new ExponentialMovingAverageRate( - 1., 10., 10); + private ExponentialMovingAverageRate history = new ExponentialMovingAverageRate(1., 10., 10); @Test public void testGetCount() { @@ -49,7 +46,7 @@ public class ExponentialMovingAverageRateTests { assertEquals(0, history.getMean(), 0.01); Thread.sleep(20L); history.increment(); - assertEquals(50, history.getMean(), 10); + assertTrue(history.getMean() > 10); } @Test @@ -59,9 +56,10 @@ public class ExponentialMovingAverageRateTests { history.increment(); Thread.sleep(20L); history.increment(); - assertEquals(50, history.getMean(), 10); + double before = history.getMean(); + assertTrue(before > 10); Thread.sleep(20L); - assertEquals(35, history.getMean(), 10); + assertTrue(history.getMean() < before); } @Test @@ -74,7 +72,20 @@ public class ExponentialMovingAverageRateTests { history.increment(); Thread.sleep(18L); // System.err.println(history); - assertTrue("Standard deviation should be non-zero: "+history, history.getStandardDeviation()>0); + assertTrue("Standard deviation should be non-zero: " + history, history.getStandardDeviation() > 0); + } + + @Test + @Ignore + public void testReset() throws Exception { + assertEquals(0, history.getStandardDeviation(), 0.01); + history.increment(); + Thread.sleep(30L); + history.increment(); + assertFalse(0==history.getStandardDeviation()); + history.reset(); + assertEquals(0, history.getStandardDeviation(), 0.01); + assertEquals("[[N=0, min=0.000000, max=0.000000, mean=0.000000, sigma=0.000000], timeSinceLast=0.000000]", history.toString()); } } diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ExponentialMovingAverageRatioTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ExponentialMovingAverageRatioTests.java index acbb8e7e67..2a20025a45 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ExponentialMovingAverageRatioTests.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ExponentialMovingAverageRatioTests.java @@ -16,6 +16,7 @@ package org.springframework.integration.monitor; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.Test; @@ -45,14 +46,14 @@ public class ExponentialMovingAverageRatioTests { @Test public void testGetEarlyMean() throws Exception { - assertEquals(0, history.getMean(), 0.01); + assertEquals(1, history.getMean(), 0.01); history.success(); assertEquals(1, history.getMean(), 0.01); } @Test public void testGetEarlyFailure() throws Exception { - assertEquals(0, history.getMean(), 0.01); + assertEquals(1, history.getMean(), 0.01); history.failure(); assertEquals(0, history.getMean(), 0.01); } @@ -66,7 +67,7 @@ public class ExponentialMovingAverageRatioTests { @Test public void testGetMean() throws Exception { - assertEquals(0, history.getMean(), 0.01); + assertEquals(1, history.getMean(), 0.01); history.success(); assertEquals(1, history.getMean(), 0.01); history.success(); @@ -77,7 +78,7 @@ public class ExponentialMovingAverageRatioTests { @Test public void testGetMeanFailuresHighRate() throws Exception { - assertEquals(0, history.getMean(), 0.01); + assertEquals(1, history.getMean(), 0.01); history.success(); assertEquals(average(1), history.getMean(), 0.01); history.failure(); @@ -88,7 +89,7 @@ public class ExponentialMovingAverageRatioTests { @Test public void testGetMeanFailuresLowRate() throws Exception { - assertEquals(0, history.getMean(), 0.01); + assertEquals(1, history.getMean(), 0.01); history.failure(); assertEquals(average(0), history.getMean(), 0.01); history.failure(); @@ -104,6 +105,17 @@ public class ExponentialMovingAverageRatioTests { assertEquals(0, history.getStandardDeviation(), 1); } + @Test + public void testReset() throws Exception { + assertEquals(0, history.getStandardDeviation(), 0.01); + history.success(); + history.failure(); + assertFalse(0==history.getStandardDeviation()); + history.reset(); + assertEquals(0, history.getStandardDeviation(), 0.01); + assertEquals("[[N=0, min=0.000000, max=0.000000, mean=1.000000, sigma=0.000000], timeSinceLast=0.000000]", history.toString()); + } + private double average(double... values) { int count = 0; double sum = 0; diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ExponentialMovingAverageTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ExponentialMovingAverageTests.java index eafff4aae1..282cad800c 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ExponentialMovingAverageTests.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ExponentialMovingAverageTests.java @@ -16,6 +16,7 @@ package org.springframework.integration.monitor; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import org.junit.Test; @@ -50,4 +51,15 @@ public class ExponentialMovingAverageTests { assertEquals(0, history.getStandardDeviation(), 0.01); } + @Test + public void testReset() throws Exception { + assertEquals(0, history.getStandardDeviation(), 0.01); + history.append(1); + history.append(2); + assertFalse(0==history.getStandardDeviation()); + history.reset(); + assertEquals(0, history.getStandardDeviation(), 0.01); + assertEquals("[N=0, min=0.000000, max=0.000000, mean=0.000000, sigma=0.000000]", history.toString()); + } + } diff --git a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/HandlerMonitoringIntegrationTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/HandlerMonitoringIntegrationTests.java index 25c968185c..8c4e52517f 100644 --- a/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/HandlerMonitoringIntegrationTests.java +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/HandlerMonitoringIntegrationTests.java @@ -15,6 +15,8 @@ package org.springframework.integration.monitor; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.util.Arrays; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.aspectj.lang.annotation.Aspect; @@ -64,7 +66,7 @@ public class HandlerMonitoringIntegrationTests { ClassPathXmlApplicationContext context = createContext("anonymous-handler.xml", "anonymous"); try { - assertTrue(messageHandlersMonitor.getHandlerNames().contains("errorLogger")); + assertTrue(Arrays.asList(messageHandlersMonitor.getHandlerNames()).contains("errorLogger")); } finally { context.close(); diff --git a/spring-integration-sftp/pom.xml b/spring-integration-sftp/pom.xml index 6c3c6a6df3..90a13932fb 100644 --- a/spring-integration-sftp/pom.xml +++ b/spring-integration-sftp/pom.xml @@ -5,6 +5,7 @@ org.springframework.integration spring-integration-parent 2.0.0.BUILD-SNAPSHOT + ../spring-integration-parent/pom.xml org.springframework.integration spring-integration-sftp @@ -30,37 +31,31 @@ cglib cglib-nodep - ${cglib.version} test org.easymock easymock - ${org.easymock.version} test org.easymock easymockclassextension - ${org.easymock.version} test junit junit - ${junit.version} test org.springframework spring-context-support - ${org.springframework.version} compile org.springframework spring-test - ${org.springframework.version} test diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpSendingMessageHandler.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpSendingMessageHandler.java index 14a26894da..3f28376cdb 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpSendingMessageHandler.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/SftpSendingMessageHandler.java @@ -13,24 +13,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.integration.sftp; -import com.jcraft.jsch.ChannelSftp; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; + import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; + import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; -import org.springframework.integration.*; +import org.springframework.integration.Message; +import org.springframework.integration.MessageDeliveryException; +import org.springframework.integration.MessageHeaders; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.file.DefaultFileNameGenerator; import org.springframework.integration.file.FileNameGenerator; import org.springframework.util.Assert; import org.springframework.util.FileCopyUtils; -import java.io.*; - +import com.jcraft.jsch.ChannelSftp; /** * Sending a message payload to a remote SFTP endpoint. For now, we assume that the payload of the inbound message is of @@ -38,69 +48,32 @@ import java.io.*; * name? * * @author Josh Long + * @since 2.0 */ public class SftpSendingMessageHandler implements MessageHandler, InitializingBean { - private SftpSessionPool pool; - private String remoteDirectory; + + private static final String TEMPORARY_FILE_SUFFIX = ".writing"; + + + private volatile SftpSessionPool pool; + + private volatile String remoteDirectory; + + private volatile FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator(); + + private volatile File temporaryBufferFolderFile; + + private volatile Resource temporaryBufferFolder = new FileSystemResource(SystemUtils.getJavaIoTmpDir()); + private volatile boolean afterPropertiesSetRan; + private volatile String charset = Charset.defaultCharset().name(); + + public SftpSendingMessageHandler(SftpSessionPool pool) { this.pool = pool; } - public void afterPropertiesSet() throws Exception { - Assert.state(this.pool != null, "the pool can't be null!"); - - temporaryBufferFolderFile = this.temporaryBufferFolder.getFile(); - - if (!afterPropertiesSetRan) { - if (StringUtils.isEmpty(this.remoteDirectory)) { - remoteDirectory = null; - } - - this.afterPropertiesSetRan = true; - } - } - - public String getRemoteDirectory() { - return remoteDirectory; - } - - /* Ugh this needs to be put in a convenient place accessible for all the file:, sftp:, and ftp:* adapters */ - - private File handleFileMessage(File sourceFile, File tempFile, File resultFile) - throws IOException { - if (sourceFile.renameTo(resultFile)) { - return resultFile; - } - - FileCopyUtils.copy(sourceFile, tempFile); - tempFile.renameTo(resultFile); - - return resultFile; - } - - private File handleByteArrayMessage(byte[] bytes, File tempFile, File resultFile) - throws IOException { - FileCopyUtils.copy(bytes, tempFile); - tempFile.renameTo(resultFile); - - return resultFile; - } - - private File handleStringMessage(String content, File tempFile, File resultFile, String charset) - throws IOException { - OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(tempFile), charset); - FileCopyUtils.copy(content, writer); - tempFile.renameTo(resultFile); - - return resultFile; - } - - private static final String TEMPORARY_FILE_SUFFIX = ".writing"; - private FileNameGenerator fileNameGenerator = new DefaultFileNameGenerator(); - private File temporaryBufferFolderFile; - private Resource temporaryBufferFolder = new FileSystemResource(SystemUtils.getJavaIoTmpDir()); public void setTemporaryBufferFolder(Resource temporaryBufferFolder) { this.temporaryBufferFolder = temporaryBufferFolder; @@ -110,6 +83,54 @@ public class SftpSendingMessageHandler implements MessageHandler, InitializingBe this.fileNameGenerator = fileNameGenerator; } + public void setRemoteDirectory(final String remoteDirectory) { + this.remoteDirectory = remoteDirectory; + } + + public String getRemoteDirectory() { + return remoteDirectory; + } + + public void setCharset(String charset) { + this.charset = charset; + } + + public void afterPropertiesSet() throws Exception { + Assert.state(this.pool != null, "the pool can't be null!"); + temporaryBufferFolderFile = this.temporaryBufferFolder.getFile(); + if (!afterPropertiesSetRan) { + if (StringUtils.isEmpty(this.remoteDirectory)) { + remoteDirectory = null; + } + this.afterPropertiesSetRan = true; + } + } + + + /* Ugh this needs to be put in a convenient place accessible for all the file:, sftp:, and ftp:* adapters */ + + private File handleFileMessage(File sourceFile, File tempFile, File resultFile) throws IOException { + if (sourceFile.renameTo(resultFile)) { + return resultFile; + } + FileCopyUtils.copy(sourceFile, tempFile); + tempFile.renameTo(resultFile); + return resultFile; + } + + private File handleByteArrayMessage(byte[] bytes, File tempFile, File resultFile) throws IOException { + FileCopyUtils.copy(bytes, tempFile); + tempFile.renameTo(resultFile); + return resultFile; + } + + private File handleStringMessage(String content, File tempFile, File resultFile, String charset) throws IOException { + OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(tempFile), charset); + FileCopyUtils.copy(content, writer); + tempFile.renameTo(resultFile); + return resultFile; + } + private File redeemForStorableFile(Message msg) throws MessageDeliveryException { try { Object payload = msg.getPayload(); @@ -117,101 +138,82 @@ public class SftpSendingMessageHandler implements MessageHandler, InitializingBe File tempFile = new File(temporaryBufferFolderFile, generateFileName + TEMPORARY_FILE_SUFFIX); File resultFile = new File(temporaryBufferFolderFile, generateFileName); File sendableFile; - if (payload instanceof String) + if (payload instanceof String) { sendableFile = this.handleStringMessage((String) payload, tempFile, resultFile, this.charset); - else if (payload instanceof File) + } + else if (payload instanceof File) { sendableFile = this.handleFileMessage((File) payload, tempFile, resultFile); - else if (payload instanceof byte[]) + } + else if (payload instanceof byte[]) { sendableFile = this.handleByteArrayMessage((byte[]) payload, tempFile, resultFile); - else sendableFile = null; + } + else { + sendableFile = null; + } return sendableFile; - } catch (Throwable th) { + } + catch (Throwable th) { throw new MessageDeliveryException(msg); } - } - private String charset; - public void setCharset(String charset) { - this.charset = charset; - } /* Ugh this needs to be put in a convenient place accessible for all the file:, sftp:, and ftp:* adapters */ - - public void handleMessage(final Message message) - throws MessageRejectedException, MessageHandlingException, MessageDeliveryException { + public void handleMessage(final Message message) { Assert.state(this.pool != null, "need a working pool"); File inboundFilePayload = this.redeemForStorableFile(message); try { - if ((inboundFilePayload != null) && inboundFilePayload.exists()) { sendFileToRemoteEndpoint(message, inboundFilePayload); } - } catch (Throwable thr) { + } + catch (Throwable thr) { // logger.debug("recieved an exception.", thr); throw new MessageDeliveryException(message, "couldn't deliver the message!", thr); - } finally { + } + finally { if (inboundFilePayload != null && inboundFilePayload.exists()) inboundFilePayload.delete(); - } } - public void setRemoteDirectory(final String remoteDirectory) { - this.remoteDirectory = remoteDirectory; - } - - private boolean sendFileToRemoteEndpoint(Message message, File file) - throws Throwable { + private boolean sendFileToRemoteEndpoint(Message message, File file) throws Throwable { assert this.pool != null : "need a working pool"; - SftpSession session = this.pool.getSession(); - if (session == null) { throw new RuntimeException("the session returned from the pool is null, can't possibly proceed."); } - session.start(); - ChannelSftp sftp = session.getChannel(); - InputStream fileInputStream = null; - try { fileInputStream = new FileInputStream(file); - String baseOfRemotePath = StringUtils.isEmpty(this.remoteDirectory) ? StringUtils.EMPTY : remoteDirectory; // the safe default - // logger.debug("going to send " + file.getAbsolutePath() + " to a remote sftp endpoint"); String dynRd = null; MessageHeaders messageHeaders = null; - if (message != null) { messageHeaders = message.getHeaders(); - if ((messageHeaders != null) && messageHeaders.containsKey(SftpConstants.SFTP_REMOTE_DIRECTORY_HEADER)) { dynRd = (String) messageHeaders.get(SftpConstants.SFTP_REMOTE_DIRECTORY_HEADER); - if (!StringUtils.isEmpty(dynRd)) { baseOfRemotePath = dynRd; } } } - if (!StringUtils.defaultString(baseOfRemotePath).endsWith("/")) { baseOfRemotePath += "/"; } - sftp.put(fileInputStream, baseOfRemotePath + file.getName()); - return true; - } finally { + } + finally { IOUtils.closeQuietly(fileInputStream); - if (pool != null) { pool.release(session); } } } + } diff --git a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpMessageSendingConsumerFactoryBean.java b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpMessageSendingConsumerFactoryBean.java index e964e4b026..56a092ace0 100644 --- a/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpMessageSendingConsumerFactoryBean.java +++ b/spring-integration-sftp/src/main/java/org/springframework/integration/sftp/config/SftpMessageSendingConsumerFactoryBean.java @@ -36,6 +36,7 @@ public class SftpMessageSendingConsumerFactoryBean implements FactoryBean - @@ -60,6 +59,7 @@ + diff --git a/spring-integration-twitter/pom.xml b/spring-integration-twitter/pom.xml index 5d2d8a39fa..18aa36ed62 100644 --- a/spring-integration-twitter/pom.xml +++ b/spring-integration-twitter/pom.xml @@ -5,6 +5,7 @@ org.springframework.integration spring-integration-parent 2.0.0.BUILD-SNAPSHOT + ../spring-integration-parent/pom.xml org.springframework.integration spring-integration-twitter @@ -32,36 +33,30 @@ cglib cglib-nodep - ${cglib.version} org.easymock easymock - ${org.easymock.version} test org.easymock easymockclassextension - ${org.easymock.version} test junit junit - ${junit.version} test org.springframework spring-context-support - ${org.springframework.version} compile org.springframework spring-test - ${org.springframework.version} test diff --git a/spring-integration-xml/.springBeans b/spring-integration-xml/.springBeans deleted file mode 100644 index 03a170852e..0000000000 --- a/spring-integration-xml/.springBeans +++ /dev/null @@ -1,14 +0,0 @@ - - - 1 - - - - - - - src/test/java/org/springframework/integration/xml/transformer/XsltTransformerTests-context.xml - - - - diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/AggregatedXmlMessageValidationException.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/AggregatedXmlMessageValidationException.java new file mode 100644 index 0000000000..011369040c --- /dev/null +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/AggregatedXmlMessageValidationException.java @@ -0,0 +1,29 @@ +/** + * + */ +package org.springframework.integration.xml; + +import java.util.Iterator; +import java.util.List; + +/** + * @author Oleg Zhurakousky + * @since 2.0 + */ +@SuppressWarnings("serial") +public class AggregatedXmlMessageValidationException extends RuntimeException { + + private final List exceptions; + + public AggregatedXmlMessageValidationException(List exceptions){ + this.exceptions = exceptions; + } + /** + * Will return iterator of exceptions aggregated by this Class. + * + * @return + */ + public Iterator exceptionIterator(){ + return exceptions.iterator(); + } +} diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/IntegrationXmlNamespaceHandler.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/IntegrationXmlNamespaceHandler.java index 072505af96..57ebcb5d0c 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/IntegrationXmlNamespaceHandler.java +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/IntegrationXmlNamespaceHandler.java @@ -34,7 +34,7 @@ public class IntegrationXmlNamespaceHandler extends AbstractIntegrationNamespace registerBeanDefinitionParser("xpath-selector", new XPathSelectorParser()); registerBeanDefinitionParser("xpath-expression", new XPathExpressionParser()); registerBeanDefinitionParser("xpath-splitter", new XPathMessageSplitterParser()); - registerBeanDefinitionParser("validating-router", new XmlPayloadValidatingRouterParser()); + registerBeanDefinitionParser("validating-filter", new XmlPayloadValidatingFilterParser()); } } diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathRouterParser.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathRouterParser.java index c7f4ca8ddc..22da832772 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathRouterParser.java +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathRouterParser.java @@ -16,66 +16,48 @@ package org.springframework.integration.xml.config; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; - import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractConsumerEndpointParser; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.config.xml.AbstractRouterParser; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; /** * Parser for the <xpath-router/> element. * * @author Jonas Partner * @author Mark Fisher + * @author Oleg Zhurakousky */ -public class XPathRouterParser extends AbstractConsumerEndpointParser { +public class XPathRouterParser extends AbstractRouterParser { private XPathExpressionParser xpathParser = new XPathExpressionParser(); - @Override - protected boolean shouldGenerateId() { - return false; - } - - @Override - protected boolean shouldGenerateIdAsFallback() { - return true; - } - - @Override - protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { + protected BeanDefinition doParseRouter(Element element, + ParserContext parserContext) { + BeanDefinitionBuilder xpathRouterBuilder = BeanDefinitionBuilder.genericBeanDefinition( + "org.springframework.integration.xml.router.XPathRouter"); NodeList xPathExpressionNodes = element.getElementsByTagNameNS( element.getNamespaceURI(), "xpath-expression"); - Assert.isTrue(xPathExpressionNodes.getLength() < 2, - "Only one xpath-expression child can be specified."); + Assert.isTrue(xPathExpressionNodes.getLength() < 2, "Only one xpath-expression child can be specified."); String xPathExpressionRef = element.getAttribute("xpath-expression-ref"); boolean xPathExpressionChildPresent = (xPathExpressionNodes.getLength() == 1); boolean xPathReferencePresent = StringUtils.hasText(xPathExpressionRef); Assert.isTrue(xPathExpressionChildPresent ^ xPathReferencePresent, "Exactly one of 'xpath-expression' or 'xpath-expression-ref' is required."); - boolean multiChannel = Boolean.parseBoolean(element.getAttribute("multi-channel")); - String classname = "org.springframework.integration.xml.router." + - ((multiChannel) ? "XPathMultiChannelRouter" : "XPathSingleChannelRouter"); - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(classname); if (xPathExpressionChildPresent) { BeanDefinition beanDefinition = this.xpathParser.parse( (Element) xPathExpressionNodes.item(0), parserContext); - builder.addConstructorArgValue(beanDefinition); + xpathRouterBuilder.addConstructorArgValue(beanDefinition); } else { - builder.addConstructorArgReference(xPathExpressionRef); + xpathRouterBuilder.addConstructorArgReference(xPathExpressionRef); } - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "resolution-required"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "ignore-channel-name-resolution-failures"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "channel-resolver"); - IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "default-output-channel"); - return builder; + return xpathRouterBuilder.getBeanDefinition(); } } diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParser.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParser.java new file mode 100644 index 0000000000..7ffa7deb52 --- /dev/null +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParser.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2010 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.xml.config; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractConsumerEndpointParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * @author Jonas Partner + * @author Oleg Zhurakousky + */ +public class XmlPayloadValidatingFilterParser extends AbstractConsumerEndpointParser { + private static String SELECTOR = + "org.springframework.integration.xml.selector.XmlValidatingMessageSelector"; + private static String FILTER = + "org.springframework.integration.config.FilterFactoryBean"; + + /** Constant that defines a W3C XML Schema. */ + public static final String SCHEMA_W3C_XML = "http://www.w3.org/2001/XMLSchema"; + + /** Constant that defines a RELAX NG Schema. */ + public static final String SCHEMA_RELAX_NG = "http://relaxng.org/ns/structure/1.0"; + + @Override + protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { + BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.genericBeanDefinition(FILTER); + + IntegrationNamespaceUtils.setReferenceIfAttributeDefined(filterBuilder, element, "discard-channel"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(filterBuilder, element, "throw-exception-on-rejection"); + + BeanDefinitionBuilder selectorBuilder = BeanDefinitionBuilder.genericBeanDefinition(SELECTOR); + String validator = element.getAttribute("xml-validator"); + String schemaLocation = element.getAttribute("schema-location"); + boolean validatorDefined = StringUtils.hasText(validator); + boolean schemaLocationDefined = StringUtils.hasText(schemaLocation); + selectorBuilder.addPropertyValue("throwExceptionOnRejection", element.getAttribute("throw-exception-on-rejection")); + + if (!(validatorDefined ^ schemaLocationDefined)) { + throw new BeanDefinitionStoreException("Exactly one of 'xml-validator' or 'schema-location' is allowed on the 'validating-filter' element"); + } + if (schemaLocationDefined){ + selectorBuilder.addConstructorArgValue(schemaLocation); + String schemaType = "xml-schema".equals(element.getAttribute("schema-type")) ? SCHEMA_W3C_XML : SCHEMA_RELAX_NG;; + selectorBuilder.addConstructorArgValue(schemaType); + } + else { + selectorBuilder.addConstructorArgReference(validator); + } + + filterBuilder.addPropertyValue("targetObject", selectorBuilder.getBeanDefinition()); + return filterBuilder; + } + + @Override + protected boolean shouldGenerateId() { + return false; + } + + @Override + protected boolean shouldGenerateIdAsFallback() { + return true; + } +} diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingRouterParser.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingRouterParser.java deleted file mode 100644 index 83f3e8f7e6..0000000000 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingRouterParser.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2002-2008 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.xml.config; - -import javax.xml.XMLConstants; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.integration.config.xml.AbstractConsumerEndpointParser; -import org.springframework.integration.xml.router.SchemaValidator; -import org.springframework.integration.xml.router.XmlPayloadValidatingRouter; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.w3c.dom.Element; - -/** - * @author Jonas Partner - */ -public class XmlPayloadValidatingRouterParser extends - AbstractConsumerEndpointParser { - - @Override - protected boolean shouldGenerateId() { - return false; - } - - @Override - protected boolean shouldGenerateIdAsFallback() { - return true; - } - - @Override - protected BeanDefinitionBuilder parseHandler(Element element, - ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder - .genericBeanDefinition(); - builder.getBeanDefinition().setBeanClass( - XmlPayloadValidatingRouter.class); - String channelResolver = element.getAttribute("channel-resolver"); - - String validChannelName = element.getAttribute("valid-channel"); - String invalidChannelName = element.getAttribute("invalid-channel"); - String schemaType = element.getAttribute("schema-type"); - String schemaLocation = element.getAttribute("schema-location"); - - Assert.state(schemaType.equals("xml-schema") - || schemaType.equals("relax-ng"), "Unrecognised schema type " - + schemaType); - - - Assert.state(StringUtils.hasText(invalidChannelName) - && StringUtils.hasText(validChannelName), - "valid-channel and invalid-channel must both be specified"); - - builder.addConstructorArgValue(validChannelName); - builder.addConstructorArgValue(invalidChannelName); - - - - BeanDefinition validatorBeanDefinition; - if (schemaType.equals("xml-schema")) { - validatorBeanDefinition = createValidator(XMLConstants.W3C_XML_SCHEMA_NS_URI, schemaLocation); - } else { - validatorBeanDefinition = createValidator(XMLConstants.RELAXNG_NS_URI, schemaLocation); - } - builder.addConstructorArgValue(validatorBeanDefinition); - - - if (StringUtils.hasText(channelResolver)) { - builder.addPropertyReference("channelResolver", channelResolver); - } - - return builder; - } - - protected BeanDefinition createValidator(String schemaType, String schemaLocation){ - BeanDefinitionBuilder xmlValidator = BeanDefinitionBuilder - .genericBeanDefinition(); - xmlValidator.getBeanDefinition().setBeanClass(SchemaValidator.class); - xmlValidator.addConstructorArgValue(schemaLocation); - xmlValidator.addConstructorArgValue(schemaType); - - return xmlValidator.getBeanDefinition(); - } - -} diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/SchemaValidator.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/SchemaValidator.java deleted file mode 100644 index e6fd201bbf..0000000000 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/SchemaValidator.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2002-2008 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.xml.router; - -import java.io.IOException; - -import javax.xml.transform.Source; - -import org.springframework.core.io.Resource; -import org.springframework.integration.MessagingException; -import org.springframework.xml.validation.XmlValidationException; -import org.springframework.xml.validation.XmlValidatorFactory; -import org.xml.sax.SAXParseException; - -public class SchemaValidator implements XmlValidator { - - private final org.springframework.xml.validation.XmlValidator xmlValidator; - - public SchemaValidator(Resource schemaResource, String schemaLanguage) - throws IOException { - super(); - this.xmlValidator = XmlValidatorFactory.createValidator(schemaResource, - schemaLanguage); - } - - public boolean isValid(Source source) { - try { - SAXParseException[] exceptions = xmlValidator.validate(source); - return exceptions.length < 1; - } catch (IOException ioE) { - throw new MessagingException( - "Exception applying schema validation", ioE); - } catch (XmlValidationException validationException){ - return false; - } - } - -} diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XPathMultiChannelRouter.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XPathMultiChannelRouter.java deleted file mode 100644 index 3da132a52a..0000000000 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XPathMultiChannelRouter.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2002-2010 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.xml.router; - -import java.util.List; -import java.util.Map; - -import org.springframework.integration.Message; -import org.springframework.integration.xml.XmlPayloadConverter; -import org.springframework.util.Assert; -import org.springframework.xml.xpath.NodeMapper; -import org.springframework.xml.xpath.XPathExpression; -import org.w3c.dom.DOMException; -import org.w3c.dom.Node; - -/** - * A router that evaluates the XPath expression using - * {@link XPathExpression#evaluateAsNodeList(Node)} which returns zero or more - * nodes in conjunction with an instance of {@link NodeMapper} to produce zero - * or more channel names. An instance of {@link XmlPayloadConverter} is used to - * extract the payload as a {@link Node}. - * - * @author Jonas Partner - */ -public class XPathMultiChannelRouter extends AbstractXPathRouter { - - private volatile NodeMapper nodeMapper = new TextContentNodeMapper(); - - - /** - * @see AbstractXPathRouter#AbstractXPathRouter(String, Map) - */ - public XPathMultiChannelRouter(String expression, Map namespaces) { - super(expression, namespaces); - } - - /** - * @see AbstractXPathRouter#AbstractXPathRouter(String, String, String) - */ - public XPathMultiChannelRouter(String expression, String prefix, String namespace) { - super(expression, prefix, namespace); - } - - /** - * @see AbstractXPathRouter#AbstractXPathRouter(String) - */ - public XPathMultiChannelRouter(String expression) { - super(expression); - } - - /** - * @see AbstractXPathRouter#AbstractXPathRouter(XPathExpression) - */ - public XPathMultiChannelRouter(XPathExpression expression) { - super(expression); - } - - - public void setNodeMapper(NodeMapper nodeMapper) { - Assert.notNull(nodeMapper, "NodeMapper must not be null"); - this.nodeMapper = nodeMapper; - } - - @SuppressWarnings("unchecked") - public List getChannelIndicatorList(Message message) { - Node node = getConverter().convertToNode(message.getPayload()); - return getXPathExpression().evaluate(node, this.nodeMapper); - } - - - private static class TextContentNodeMapper implements NodeMapper { - - public Object mapNode(Node node, int nodeNum) throws DOMException { - return node.getTextContent(); - } - - } - - - -} diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/AbstractXPathRouter.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XPathRouter.java similarity index 72% rename from spring-integration-xml/src/main/java/org/springframework/integration/xml/router/AbstractXPathRouter.java rename to spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XPathRouter.java index fad0bafb25..23bc2720bc 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/AbstractXPathRouter.java +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XPathRouter.java @@ -17,21 +17,29 @@ package org.springframework.integration.xml.router; import java.util.HashMap; +import java.util.List; import java.util.Map; -import org.springframework.integration.router.AbstractChannelNameResolvingMessageRouter; +import org.springframework.integration.Message; +import org.springframework.integration.router.AbstractMessageRouter; import org.springframework.integration.xml.DefaultXmlPayloadConverter; import org.springframework.integration.xml.XmlPayloadConverter; +import org.springframework.xml.xpath.NodeMapper; import org.springframework.xml.xpath.XPathExpression; import org.springframework.xml.xpath.XPathExpressionFactory; +import org.w3c.dom.DOMException; +import org.w3c.dom.Node; /** * Abstract base class for Message Routers that use * {@link XPathExpression} evaluation to determine channel names. * * @author Jonas Partner + * @author Oleg Zhurakousky */ -public abstract class AbstractXPathRouter extends AbstractChannelNameResolvingMessageRouter { +public class XPathRouter extends AbstractMessageRouter { + + private volatile NodeMapper nodeMapper = new TextContentNodeMapper(); private final XPathExpression xPathExpression; @@ -45,7 +53,7 @@ public abstract class AbstractXPathRouter extends AbstractChannelNameResolvingMe * @param expression * @param namespaces */ - public AbstractXPathRouter(String expression, Map namespaces) { + public XPathRouter(String expression, Map namespaces) { this.xPathExpression = XPathExpressionFactory.createXPathExpression(expression, namespaces); } @@ -57,7 +65,7 @@ public abstract class AbstractXPathRouter extends AbstractChannelNameResolvingMe * @param prefix * @param namespace */ - public AbstractXPathRouter(String expression, String prefix, String namespace) { + public XPathRouter(String expression, String prefix, String namespace) { Map namespaces = new HashMap(); namespaces.put(prefix, namespace); this.xPathExpression = XPathExpressionFactory.createXPathExpression(expression, namespaces); @@ -69,7 +77,7 @@ public abstract class AbstractXPathRouter extends AbstractChannelNameResolvingMe * * @param expression */ - public AbstractXPathRouter(String expression) { + public XPathRouter(String expression) { this.xPathExpression = XPathExpressionFactory.createXPathExpression(expression); } @@ -78,7 +86,7 @@ public abstract class AbstractXPathRouter extends AbstractChannelNameResolvingMe * * @param expression */ - public AbstractXPathRouter(XPathExpression expression) { + public XPathRouter(XPathExpression expression) { this.xPathExpression = expression; } @@ -103,4 +111,20 @@ public abstract class AbstractXPathRouter extends AbstractChannelNameResolvingMe public String getComponentType(){ return "xml:xpath-router"; } + + @Override + @SuppressWarnings("unchecked") + public List getChannelIndicatorList(Message message) { + Node node = getConverter().convertToNode(message.getPayload()); + return getXPathExpression().evaluate(node, this.nodeMapper); + } + + + private static class TextContentNodeMapper implements NodeMapper { + + public Object mapNode(Node node, int nodeNum) throws DOMException { + return node.getTextContent(); + } + + } } diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XPathSingleChannelRouter.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XPathSingleChannelRouter.java deleted file mode 100644 index e5298a314a..0000000000 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XPathSingleChannelRouter.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2002-2008 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.xml.router; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.springframework.integration.Message; -import org.springframework.integration.MessagingException; -import org.springframework.integration.xml.DefaultXmlPayloadConverter; -import org.springframework.integration.xml.XmlPayloadConverter; -import org.springframework.xml.xpath.XPathExpression; -import org.w3c.dom.Node; - -/** - * Router that evaluates the payload using {@link XPathExpression#evaluateAsString(Node)} - * to extract a channel name. The payload is extracted as a node using the - * provided {@link XmlPayloadConverter} with {@link DefaultXmlPayloadConverter} - * being the default. - * - *

The provided {@link XPathExpression} must evaluate to a non-empty String. - * - * @author Jonas Partner - */ -public class XPathSingleChannelRouter extends AbstractXPathRouter { - - /** - * @see AbstractXPathRouter#AbstractXPathRouter(String, Map) - */ - public XPathSingleChannelRouter(String expression, Map namespaces) { - super(expression, namespaces); - } - - /** - * @see AbstractXPathRouter#AbstractXPathRouter(String, String, String) - */ - public XPathSingleChannelRouter(String expression, String prefix, String namespace) { - super(expression, prefix, namespace); - } - - /** - * @see AbstractXPathRouter#AbstractXPathRouter(String) - */ - public XPathSingleChannelRouter(String expression) { - super(expression); - } - - /** - * @see AbstractXPathRouter#AbstractXPathRouter(XPathExpression) - */ - public XPathSingleChannelRouter(XPathExpression expression) { - super(expression); - } - - - /** - * Evaluates the payload using {@link XPathExpression#evaluateAsString(Node)} - * - * @throws MessagingException if the {@link XPathExpression} evaluates to - * an empty string - */ - - @Override - protected List getChannelIndicatorList(Message message) { - List channels = new ArrayList(); - Node node = getConverter().convertToNode(message.getPayload()); - String result = getXPathExpression().evaluateAsString(node); - if (result == null || "".equals(result)) { - return null; - } else { - channels.add(result); - } - - return channels; - - } - -} diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XmlPayloadValidatingRouter.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XmlPayloadValidatingRouter.java deleted file mode 100644 index b4b661b4b6..0000000000 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XmlPayloadValidatingRouter.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2002-2008 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.xml.router; - -import org.springframework.integration.Message; -import org.springframework.integration.router.AbstractSingleChannelNameRouter; -import org.springframework.integration.xml.DefaultXmlPayloadConverter; -import org.springframework.integration.xml.XmlPayloadConverter; - -public class XmlPayloadValidatingRouter extends AbstractSingleChannelNameRouter{ - - private final String validMessageChannelName; - - private final String invalidMessageChannelName; - - private final XmlValidator xmlValidator; - - private volatile XmlPayloadConverter converter = new DefaultXmlPayloadConverter(); - - - public XmlPayloadValidatingRouter(String validMessageChannelName, - String invalidMessageChannelName, XmlValidator xmlValidator) { - super(); - this.validMessageChannelName = validMessageChannelName; - this.invalidMessageChannelName = invalidMessageChannelName; - this.xmlValidator = xmlValidator; - } - - /** - * Converter used to convert payloads prior to validation - * - * @param converter - */ - public void setConverter(XmlPayloadConverter converter) { - this.converter = converter; - } - - - @Override - protected String determineTargetChannelName(Message message) { - return xmlValidator.isValid(converter.convertToSource(message.getPayload())) ? validMessageChannelName : invalidMessageChannelName; - } - - - - -} diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelector.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelector.java new file mode 100644 index 0000000000..0d0b3e9900 --- /dev/null +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelector.java @@ -0,0 +1,83 @@ +/* + * Copyright 2002-2010 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.xml.selector; + +import org.springframework.core.io.Resource; +import org.springframework.integration.Message; +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.core.MessageSelector; +import org.springframework.integration.xml.AggregatedXmlMessageValidationException; +import org.springframework.integration.xml.DefaultXmlPayloadConverter; +import org.springframework.integration.xml.XmlPayloadConverter; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.xml.validation.XmlValidator; +import org.springframework.xml.validation.XmlValidatorFactory; +import org.xml.sax.SAXParseException; +/** + * + * @author Oleg Zhurakousky + * @since 2.0 + * + */ +public class XmlValidatingMessageSelector implements MessageSelector { + + private final XmlValidator xmlValidator; + private volatile boolean throwExceptionOnRejection; + + private volatile XmlPayloadConverter converter = new DefaultXmlPayloadConverter(); + + + public XmlValidatingMessageSelector(XmlValidator xmlValidator) throws Exception{ + Assert.notNull(xmlValidator, "XmlValidator can not be 'null'"); + this.xmlValidator = xmlValidator; + } + + public XmlValidatingMessageSelector(Resource schema, String schemaType) throws Exception{ + Assert.notNull(schema, "You must provide XML schema location to perform validation"); + this.xmlValidator = XmlValidatorFactory.createValidator(schema, schemaType); + } + + public void setThrowExceptionOnRejection(boolean throwExceptionOnRejection) { + this.throwExceptionOnRejection = throwExceptionOnRejection; + } + + /** + * Converter used to convert payloads prior to validation + * + * @param converter + */ + public void setConverter(XmlPayloadConverter converter) { + this.converter = converter; + } + + @SuppressWarnings("unchecked") + public boolean accept(Message message) { + SAXParseException[] validationExceptions = null; + try { + validationExceptions = xmlValidator.validate(converter.convertToSource(message.getPayload())); + } catch (Exception e) { + throw new MessageHandlingException(message, e); + } + boolean validationSuccess = ObjectUtils.isEmpty(validationExceptions); + if (!validationSuccess && throwExceptionOnRejection){ + throw new AggregatedXmlMessageValidationException(CollectionUtils.arrayToList(validationExceptions)); + } + return validationSuccess; + } +} diff --git a/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.0.xsd b/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.0.xsd index 70b791a813..b8b0836c3b 100644 --- a/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.0.xsd +++ b/spring-integration-xml/src/main/resources/org/springframework/integration/xml/config/spring-integration-xml-2.0.xsd @@ -352,8 +352,23 @@ + + + + + + + + + + + + + + - + @@ -372,7 +387,6 @@ - @@ -419,7 +433,7 @@ - + @@ -451,7 +465,7 @@ - + @@ -495,17 +509,17 @@ - + - Defines a validating router. + Defines an XML validating filter. - + @@ -515,18 +529,32 @@ - + + + Allows you to plug-in custom 'org.springframework.xml.validation.XmlValidator' strategy + - + - - - + + + + + Allows you to point to a Message Channel where you want discarded messages to be sent. + + + + + + + + + @@ -535,6 +563,7 @@ + @@ -542,7 +571,7 @@ - + diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathRouterParserTests.java b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathRouterParserTests.java index 80c3dfe2c7..7f972da868 100644 --- a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathRouterParserTests.java +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathRouterParserTests.java @@ -16,6 +16,8 @@ package org.springframework.integration.xml.config; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; import static org.junit.Assert.assertEquals; import org.junit.After; @@ -25,11 +27,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.integration.MessageChannel; import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.core.PollableChannel; import org.springframework.integration.endpoint.EventDrivenConsumer; import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.router.AbstractMessageRouter; import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.test.util.TestUtils; import org.springframework.integration.xml.util.XmlTestUtil; import org.springframework.test.context.ContextConfiguration; import org.w3c.dom.Document; @@ -37,6 +43,7 @@ import org.w3c.dom.Document; /** * @author Jonas Partner * @author Mark Fisher + * @author Oleg Zhurakousky */ @ContextConfiguration public class XPathRouterParserTests { @@ -195,5 +202,68 @@ public class XPathRouterParserTests { inputChannel.send(MessageBuilder.withPayload("").build()); assertEquals("Wrong count of messages on default output channel",1, defaultOutput.getQueueSize()); } + @Test + public void testWithDynamicChanges() throws Exception { + ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("XPathRouterTests-context.xml", this.getClass()); + + MessageChannel inputChannel = ac.getBean("xpathRouterEmptyChannel", MessageChannel.class); + PollableChannel channelA = ac.getBean("channelA", PollableChannel.class); + PollableChannel channelB = ac.getBean("channelB", PollableChannel.class); + Document doc = XmlTestUtil.getDocumentForString("channelA"); + GenericMessage docMessage = new GenericMessage(doc); + inputChannel.send(docMessage); + assertNotNull(channelA.receive(10)); + assertNull(channelB.receive(10)); + + EventDrivenConsumer routerEndpoint = ac.getBean("xpathRouterEmpty", EventDrivenConsumer.class); + AbstractMessageRouter xpathRouter = (AbstractMessageRouter) TestUtils.getPropertyValue(routerEndpoint, "handler"); + xpathRouter.setChannelMapping("channelA", "channelB"); + inputChannel.send(docMessage); + assertNotNull(channelB.receive(10)); + assertNull(channelA.receive(10)); + } + @Test + public void testWithDynamicChangesWithExistingMappings() throws Exception { + ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("XPathRouterTests-context.xml", this.getClass()); + + MessageChannel inputChannel = ac.getBean("xpathRouterWithMappingChannel", MessageChannel.class); + PollableChannel channelA = ac.getBean("channelA", PollableChannel.class); + PollableChannel channelB = ac.getBean("channelB", PollableChannel.class); + Document doc = XmlTestUtil.getDocumentForString("channelA"); + GenericMessage docMessage = new GenericMessage(doc); + inputChannel.send(docMessage); + assertNull(channelA.receive(10)); + assertNotNull(channelB.receive(10)); + + EventDrivenConsumer routerEndpoint = ac.getBean("xpathRouterWithMapping", EventDrivenConsumer.class); + AbstractMessageRouter xpathRouter = (AbstractMessageRouter) TestUtils.getPropertyValue(routerEndpoint, "handler"); + xpathRouter.removeChannelMapping("channelA"); + inputChannel.send(docMessage); + assertNotNull(channelA.receive(10)); + assertNull(channelB.receive(10)); + } + + @Test + public void testWithDynamicChangesWithExistingMappingsAndMultiChannel() throws Exception { + ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("XPathRouterTests-context.xml", this.getClass()); + + MessageChannel inputChannel = ac.getBean("multiChannelRouterChannel", MessageChannel.class); + PollableChannel channelA = ac.getBean("channelA", PollableChannel.class); + PollableChannel channelB = ac.getBean("channelB", PollableChannel.class); + Document doc = XmlTestUtil.getDocumentForString("channelAchannelB"); + GenericMessage docMessage = new GenericMessage(doc); + inputChannel.send(docMessage); + assertNotNull(channelA.receive(10)); + assertNotNull(channelA.receive(10)); + assertNull(channelB.receive(10)); + + EventDrivenConsumer routerEndpoint = ac.getBean("xpathRouterWithMappingMultiChannel", EventDrivenConsumer.class); + AbstractMessageRouter xpathRouter = (AbstractMessageRouter) TestUtils.getPropertyValue(routerEndpoint, "handler"); + xpathRouter.removeChannelMapping("channelA"); + xpathRouter.removeChannelMapping("channelB"); + inputChannel.send(docMessage); + assertNotNull(channelA.receive(10)); + assertNotNull(channelB.receive(10)); + } } diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathRouterTests-context.xml b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathRouterTests-context.xml new file mode 100644 index 0000000000..a6635ee746 --- /dev/null +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathRouterTests-context.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests-context.xml b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests-context.xml new file mode 100644 index 0000000000..aaa7df5eb3 --- /dev/null +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests-context.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests.java b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests.java new file mode 100644 index 0000000000..42779cecfa --- /dev/null +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2010 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.xml.config; + +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; + +import org.junit.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.MessageRejectedException; +import org.springframework.integration.core.PollableChannel; +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.xml.util.XmlTestUtil; +import org.springframework.test.context.ContextConfiguration; +import org.w3c.dom.Document; + +/** + * @author Jonas Partner + * @author Oleg Zhurakousky + */ +@ContextConfiguration +public class XmlPayloadValidatingFilterParserTests { + + @Test + public void testValidMessage() throws Exception { + ApplicationContext ac = new ClassPathXmlApplicationContext("XmlPayloadValidatingFilterParserTests-context.xml", this.getClass()); + Document doc = XmlTestUtil.getDocumentForString("hello"); + GenericMessage docMessage = new GenericMessage(doc); + PollableChannel validChannel = ac.getBean("validOutputChannel", PollableChannel.class); + MessageChannel inputChannel = ac.getBean("inputChannelA", MessageChannel.class); + inputChannel.send(docMessage); + assertNotNull(validChannel.receive(100)); + } + @Test + public void testInvalidMessageWithDiscardChannel() throws Exception { + ApplicationContext ac = new ClassPathXmlApplicationContext("XmlPayloadValidatingFilterParserTests-context.xml", this.getClass()); + Document doc = XmlTestUtil.getDocumentForString(""); + GenericMessage docMessage = new GenericMessage(doc); + PollableChannel validChannel = ac.getBean("validOutputChannel", PollableChannel.class); + PollableChannel invalidChannel = ac.getBean("invalidOutputChannel", PollableChannel.class); + MessageChannel inputChannel = ac.getBean("inputChannelA", MessageChannel.class); + inputChannel.send(docMessage); + assertNotNull(invalidChannel.receive(100)); + assertNull(validChannel.receive(100)); + } + @Test(expected=MessageRejectedException.class) + public void testInvalidMessageWithThrowException() throws Exception { + ApplicationContext ac = new ClassPathXmlApplicationContext("XmlPayloadValidatingFilterParserTests-context.xml", this.getClass()); + Document doc = XmlTestUtil.getDocumentForString(""); + GenericMessage docMessage = new GenericMessage(doc); + PollableChannel validChannel = ac.getBean("validOutputChannel", PollableChannel.class); + PollableChannel invalidChannel = ac.getBean("invalidOutputChannel", PollableChannel.class); + MessageChannel inputChannel = ac.getBean("inputChannelB", MessageChannel.class); + inputChannel.send(docMessage); + assertNotNull(invalidChannel.receive(100)); + assertNull(validChannel.receive(100)); + } + @Test + public void testValidMessageWithValidator() throws Exception { + ApplicationContext ac = new ClassPathXmlApplicationContext("XmlPayloadValidatingFilterParserTests-context.xml", this.getClass()); + Document doc = XmlTestUtil.getDocumentForString("hello"); + GenericMessage docMessage = new GenericMessage(doc); + PollableChannel validChannel = ac.getBean("validOutputChannel", PollableChannel.class); + MessageChannel inputChannel = ac.getBean("inputChannelC", MessageChannel.class); + inputChannel.send(docMessage); + assertNotNull(validChannel.receive(100)); + } + @Test + public void testInvalidMessageWithValidatorAndDiscardChannel() throws Exception { + ApplicationContext ac = new ClassPathXmlApplicationContext("XmlPayloadValidatingFilterParserTests-context.xml", this.getClass()); + Document doc = XmlTestUtil.getDocumentForString(""); + GenericMessage docMessage = new GenericMessage(doc); + PollableChannel validChannel = ac.getBean("validOutputChannel", PollableChannel.class); + PollableChannel invalidChannel = ac.getBean("invalidOutputChannel", PollableChannel.class); + MessageChannel inputChannel = ac.getBean("inputChannelC", MessageChannel.class); + inputChannel.send(docMessage); + assertNotNull(invalidChannel.receive(100)); + } +} diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingRouterParserTests.java b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingRouterParserTests.java deleted file mode 100644 index dac5ed6fdc..0000000000 --- a/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingRouterParserTests.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2002-2010 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.xml.config; - -import static org.junit.Assert.assertEquals; - -import org.junit.After; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.config.AutowireCapableBeanFactory; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.integration.MessageChannel; -import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.endpoint.EventDrivenConsumer; -import org.springframework.integration.message.GenericMessage; -import org.springframework.integration.xml.util.XmlTestUtil; -import org.springframework.test.context.ContextConfiguration; -import org.w3c.dom.Document; - -/** - * @author Jonas Partner - */ -@ContextConfiguration -public class XmlPayloadValidatingRouterParserTests { - - String channelConfig = " "; - - @Autowired @Qualifier("test-input") - MessageChannel inputChannel; - - @Autowired @Qualifier("validOutputChannel") - QueueChannel validOutputChannel; - - @Autowired @Qualifier("invalidOutputChannel") - QueueChannel invalidOutputChannel; - - - ConfigurableApplicationContext appContext; - - public EventDrivenConsumer buildContext(String routerDef){ - appContext = TestXmlApplicationContextHelper.getTestAppContext( channelConfig + routerDef); - appContext.getAutowireCapableBeanFactory().autowireBeanProperties(this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false); - EventDrivenConsumer consumer = (EventDrivenConsumer) appContext.getBean("router"); - consumer.start(); - return consumer; - } - - @After - public void tearDown(){ - if(appContext != null){ - appContext.close(); - } - } - - @Test - public void testValidMessage() throws Exception { - Document doc = XmlTestUtil.getDocumentForString("hello"); - GenericMessage docMessage = new GenericMessage(doc); - buildContext(""); - inputChannel.send(docMessage); - assertEquals("Wrong number of messages", 1, validOutputChannel.getQueueSize()); - } - - @Test - public void testInvalidMessage() throws Exception { - Document doc = XmlTestUtil.getDocumentForString(""); - GenericMessage docMessage = new GenericMessage(doc); - buildContext(""); - inputChannel.send(docMessage); - assertEquals("Wrong number of messages", 1, invalidOutputChannel.getQueueSize()); - } - -} diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/router/SchemaValidatorTests.java b/spring-integration-xml/src/test/java/org/springframework/integration/xml/router/SchemaValidatorTests.java deleted file mode 100644 index 92642fab79..0000000000 --- a/spring-integration-xml/src/test/java/org/springframework/integration/xml/router/SchemaValidatorTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2002-2008 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.xml.router; - -import static org.junit.Assert.*; - -import javax.xml.XMLConstants; -import javax.xml.transform.Source; - -import org.junit.Test; -import org.springframework.core.io.ClassPathResource; -import org.springframework.integration.xml.util.XmlTestUtil; -import org.springframework.xml.transform.StringSource; - -public class SchemaValidatorTests { - - - - - - @Test - public void testValidMessageWithXsd() throws Exception{ - SchemaValidator validator = new SchemaValidator(new ClassPathResource("validationTestsSchema.xsd", SchemaValidator.class), XMLConstants.W3C_XML_SCHEMA_NS_URI); - Source source = XmlTestUtil.getDomSourceForString("hello"); - assertTrue("Document expected to be valid " ,validator.isValid(source)) ; - } - - @Test - public void testInvalidMessageWithXsd() throws Exception{ - SchemaValidator validator = new SchemaValidator(new ClassPathResource("validationTestsSchema.xsd", SchemaValidator.class), XMLConstants.W3C_XML_SCHEMA_NS_URI); - Source source = XmlTestUtil.getDomSourceForString("hello"); - assertFalse("Document not expected to be valid " ,validator.isValid(source)) ; - } - - @Test - public void testInvalidXml() throws Exception { - SchemaValidator validator = new SchemaValidator(new ClassPathResource("validationTestsSchema.xsd", SchemaValidator.class), XMLConstants.W3C_XML_SCHEMA_NS_URI); - Source source =new StringSource("something else"); - assertFalse("Document not expected to be valid " ,validator.isValid(source)) ; - } - - -} diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/router/XPathMultiChannelRouterTests.java b/spring-integration-xml/src/test/java/org/springframework/integration/xml/router/XPathRouterTests.java similarity index 59% rename from spring-integration-xml/src/test/java/org/springframework/integration/xml/router/XPathMultiChannelRouterTests.java rename to spring-integration-xml/src/test/java/org/springframework/integration/xml/router/XPathRouterTests.java index a01c8ed642..ee71c63ad5 100644 --- a/spring-integration-xml/src/test/java/org/springframework/integration/xml/router/XPathMultiChannelRouterTests.java +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/router/XPathRouterTests.java @@ -18,6 +18,8 @@ package org.springframework.integration.xml.router; import static org.junit.Assert.assertEquals; +import java.util.List; + import org.junit.Test; import org.w3c.dom.Document; import org.w3c.dom.Node; @@ -31,14 +33,14 @@ import org.springframework.xml.xpath.XPathExpressionFactory; /** * @author Jonas Partner */ -public class XPathMultiChannelRouterTests { +public class XPathRouterTests { @Test @SuppressWarnings("unchecked") public void simpleSingleAttribute() throws Exception { Document doc = XmlTestUtil.getDocumentForString(""); XPathExpression expression = XPathExpressionFactory.createXPathExpression("/doc/@type"); - XPathMultiChannelRouter router = new XPathMultiChannelRouter(expression); + XPathRouter router = new XPathRouter(expression); Object[] channelNames = router.getChannelIndicatorList(new GenericMessage(doc)).toArray(); assertEquals("Wrong number of channels returned", 1, channelNames.length); assertEquals("Wrong channel name", "one", channelNames[0]); @@ -49,7 +51,7 @@ public class XPathMultiChannelRouterTests { public void multipleNodeValues() throws Exception { Document doc = XmlTestUtil.getDocumentForString("bOnebTwo"); XPathExpression expression = XPathExpressionFactory.createXPathExpression("/doc/book"); - XPathMultiChannelRouter router = new XPathMultiChannelRouter(expression); + XPathRouter router = new XPathRouter(expression); Object[] channelNames = router.getChannelIndicatorList(new GenericMessage(doc)).toArray(); assertEquals("Wrong number of channels returned", 2, channelNames.length); assertEquals("Wrong channel name", "bOne", channelNames[0]); @@ -60,7 +62,7 @@ public class XPathMultiChannelRouterTests { @SuppressWarnings("unchecked") public void multipleNodeValuesAsString() throws Exception { XPathExpression expression = XPathExpressionFactory.createXPathExpression("/doc/book"); - XPathMultiChannelRouter router = new XPathMultiChannelRouter(expression); + XPathRouter router = new XPathRouter(expression); Object[] channelNames = router.getChannelIndicatorList(new GenericMessage("bOnebTwo")).toArray(); assertEquals("Wrong number of channels returned", 2, channelNames.length); assertEquals("Wrong channel name", "bOne", channelNames[0]); @@ -70,17 +72,59 @@ public class XPathMultiChannelRouterTests { @Test(expected = MessagingException.class) public void nonNodePayload() throws Exception { XPathExpression expression = XPathExpressionFactory.createXPathExpression("/doc/@type"); - XPathMultiChannelRouter router = new XPathMultiChannelRouter(expression); + XPathRouter router = new XPathRouter(expression); router.getChannelIndicatorList(new GenericMessage("test")); } @Test public void nodePayload() throws Exception { - XPathMultiChannelRouter router = new XPathMultiChannelRouter("./three/text()"); + XPathRouter router = new XPathRouter("./three/text()"); Document testDocument = XmlTestUtil.getDocumentForString("bobdave"); Object[] channelNames = router.getChannelIndicatorList(new GenericMessage(testDocument.getElementsByTagName("two").item(0))).toArray(); assertEquals("bob",channelNames[0]); assertEquals("dave",channelNames[1]); } + + @Test + public void testSimpleDocType() throws Exception { + Document doc = XmlTestUtil.getDocumentForString(""); + XPathExpression expression = XPathExpressionFactory.createXPathExpression("/doc/@type"); + XPathRouter router = new XPathRouter(expression); + Object channelName = router.getChannelIndicatorList(new GenericMessage(doc)).toArray()[0]; + assertEquals("Wrong channel name", "one", channelName); + } + + @Test + public void testSimpleStringDoc() throws Exception { + XPathExpression expression = XPathExpressionFactory.createXPathExpression("/doc/@type"); + XPathRouter router = new XPathRouter(expression); + Object channelName = router.getChannelIndicatorList(new GenericMessage("")).toArray()[0]; + assertEquals("Wrong channel name", "one", channelName); + } + + @Test(expected = MessagingException.class) + public void testNonNodePayload() throws Exception { + XPathExpression expression = XPathExpressionFactory.createXPathExpression("/doc/@type"); + XPathRouter router = new XPathRouter(expression); + router.getChannelIndicatorList(new GenericMessage("test")); + } + + @Test + public void testNodePayload() throws Exception { + XPathRouter router = new XPathRouter("./three/text()"); + Document testDocument = XmlTestUtil.getDocumentForString("bob"); + Object[] channelNames = router.getChannelIndicatorList(new GenericMessage(testDocument + .getElementsByTagName("two").item(0))).toArray(); + assertEquals("bob", channelNames[0]); + } + + @Test + public void testEvaluationReturnsEmptyString() throws Exception { + Document doc = XmlTestUtil.getDocumentForString(""); + XPathExpression expression = XPathExpressionFactory.createXPathExpression("/somethingelse/@type"); + XPathRouter router = new XPathRouter(expression); + List channelNames = router.getChannelIndicatorList(new GenericMessage(doc)); + assertEquals(0, channelNames.size()); + } } diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/router/XPathSingleChannelRouterTests.java b/spring-integration-xml/src/test/java/org/springframework/integration/xml/router/XPathSingleChannelRouterTests.java deleted file mode 100644 index a53b7fb5fc..0000000000 --- a/spring-integration-xml/src/test/java/org/springframework/integration/xml/router/XPathSingleChannelRouterTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2002-2010 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.xml.router; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import org.w3c.dom.Document; -import org.w3c.dom.Node; - -import org.springframework.integration.MessagingException; -import org.springframework.integration.message.GenericMessage; -import org.springframework.integration.xml.util.XmlTestUtil; -import org.springframework.xml.xpath.XPathExpression; -import org.springframework.xml.xpath.XPathExpressionFactory; - -/** - * @author Jonas Partner - */ -public class XPathSingleChannelRouterTests { - - @Test - public void testSimpleDocType() throws Exception { - Document doc = XmlTestUtil.getDocumentForString(""); - XPathExpression expression = XPathExpressionFactory.createXPathExpression("/doc/@type"); - XPathSingleChannelRouter router = new XPathSingleChannelRouter(expression); - Object channelName = router.getChannelIndicatorList(new GenericMessage(doc)).toArray()[0]; - assertEquals("Wrong channel name", "one", channelName); - } - - @Test - public void testSimpleStringDoc() throws Exception { - XPathExpression expression = XPathExpressionFactory.createXPathExpression("/doc/@type"); - XPathSingleChannelRouter router = new XPathSingleChannelRouter(expression); - Object channelName = router.getChannelIndicatorList(new GenericMessage("")).toArray()[0]; - assertEquals("Wrong channel name", "one", channelName); - } - - @Test(expected = MessagingException.class) - public void testNonNodePayload() throws Exception { - XPathExpression expression = XPathExpressionFactory.createXPathExpression("/doc/@type"); - XPathSingleChannelRouter router = new XPathSingleChannelRouter(expression); - router.getChannelIndicatorList(new GenericMessage("test")); - } - - @Test - public void testNodePayload() throws Exception { - XPathSingleChannelRouter router = new XPathSingleChannelRouter("./three/text()"); - Document testDocument = XmlTestUtil.getDocumentForString("bob"); - Object[] channelNames = router.getChannelIndicatorList(new GenericMessage(testDocument - .getElementsByTagName("two").item(0))).toArray(); - assertEquals("bob", channelNames[0]); - } - - @Test - public void testEvaluationReturnsEmptyString() throws Exception { - Document doc = XmlTestUtil.getDocumentForString(""); - XPathExpression expression = XPathExpressionFactory.createXPathExpression("/somethingelse/@type"); - XPathSingleChannelRouter router = new XPathSingleChannelRouter(expression); - Object channelNames = router.getChannelIndicatorList(new GenericMessage(doc)); - assertEquals("Wrong channel name", null, channelNames); - } - -} diff --git a/spring-integration-xml/src/test/java/org/springframework/integration/xml/router/XmlPayloadValidatingRouterTests.java b/spring-integration-xml/src/test/java/org/springframework/integration/xml/router/XmlPayloadValidatingRouterTests.java deleted file mode 100644 index 48e3b526a6..0000000000 --- a/spring-integration-xml/src/test/java/org/springframework/integration/xml/router/XmlPayloadValidatingRouterTests.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.springframework.integration.xml.router; - -import static org.junit.Assert.*; - -import javax.xml.transform.Source; -import javax.xml.transform.sax.SAXSource; - -import org.junit.Before; -/* - * Copyright 2002-2008 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. - */ - -import org.junit.Test; -import org.springframework.integration.Message; -import org.springframework.integration.support.MessageBuilder; - -public class XmlPayloadValidatingRouterTests { - - String validChannelName = "VALID"; - - String invalidChannelName = "INVALID"; - - Source testSource; - - Message testMessage; - - @Before - public void setUp(){ - testSource = new SAXSource(); - testMessage = MessageBuilder.withPayload(testSource).build(); - } - - @Test - public void testValidMessage(){ - StubValidator validator = new StubValidator(true); - XmlPayloadValidatingRouter router = new XmlPayloadValidatingRouter(validChannelName, invalidChannelName, validator); - String returnedChannelName = router.determineTargetChannelName(testMessage); - assertEquals("Wrong channel name", validChannelName, returnedChannelName); - assertEquals("Source not passed to validator ", testSource, validator.passedIn); - } - - @Test - public void testInvalidMessage(){ - StubValidator validator = new StubValidator(false); - XmlPayloadValidatingRouter router = new XmlPayloadValidatingRouter(validChannelName, invalidChannelName, validator); - String returnedChannelName = router.determineTargetChannelName(testMessage); - assertEquals("Wrong channel name", invalidChannelName, returnedChannelName); - assertEquals("Source not passed to validator ", testSource, validator.passedIn); - } - - - static class StubValidator implements XmlValidator { - - private final boolean validationResult; - - Source passedIn; - - public StubValidator(boolean validationResult) { - this.validationResult = validationResult; - } - - public boolean isValid(Source source) { - passedIn = source; - return validationResult; - } - - } - -} diff --git a/spring-integration-xmpp/pom.xml b/spring-integration-xmpp/pom.xml index bfc09700fe..73b31061eb 100644 --- a/spring-integration-xmpp/pom.xml +++ b/spring-integration-xmpp/pom.xml @@ -5,6 +5,7 @@ org.springframework.integration spring-integration-parent 2.0.0.BUILD-SNAPSHOT + ../spring-integration-parent/pom.xml spring-integration-xmpp jar @@ -29,37 +30,31 @@ cglib cglib-nodep - ${cglib.version} test org.easymock easymock - ${org.easymock.version} test org.easymock easymockclassextension - ${org.easymock.version} test junit junit - ${junit.version} test org.springframework spring-context-support - ${org.springframework.version} compile org.springframework spring-test - ${org.springframework.version} test diff --git a/src/docbkx/filter.xml b/src/docbkx/filter.xml index 7da324e52e..d0da4b7915 100644 --- a/src/docbkx/filter.xml +++ b/src/docbkx/filter.xml @@ -97,6 +97,36 @@ ]]> + If the Expression itself needs to be dynamic, then an 'expression' sub-element may be used. That provides a level of + indirection for resolving the Expression by its key from an ExpressionSource. That is a strategy interface that you + can implement directly, or you can rely upon a version available in Spring Integration that loads Expressions from + a "resource bundle" and can check for modifications after a given number of seconds. All of this is demonstrated in + the following configuration sample where the Expression could be reloaded within one minute if the underlying file + had been modified. If the ExpressionSource bean is named "expressionSource", then it is not necessary to provide the + "source" attribute on the <expression> element, but in this case it's shown for completeness. + + + + + + + + + +]]> + + Then, the 'config/integration/expressions.properties' file (or any more specific version with a locale extension + to be resolved in the typical way that resource-bundles are loaded) would contain a key/value pair: + + 100 +]]> + + All of the examples that use "expression" as an attribute or sub-element can also be applied within + transformer, router, splitter, service-activator, and header-enricher elements. Of course, the semantics/role + of the given component type would affect the interpretation of the evaluation result in the same way that the + return or a method-invocation would be interpreted. For example, an expression can return Strings that are + to be treated as Message Channel names by a router component. \ No newline at end of file diff --git a/src/docbkx/gateway.xml b/src/docbkx/gateway.xml index bd7df3720e..382fb2416c 100644 --- a/src/docbkx/gateway.xml +++ b/src/docbkx/gateway.xml @@ -168,5 +168,81 @@ For a more detailed example, please refer to the async-gateway +
+ Gateway behavior when no response is coming + + As it was explained earlier, Gateway provides a convenient way of interacting with Messaging system via POJO method + invocations, but realizing that a typical method invocation, which is generally expected to always return (even with Exception), + might not always map one-to-one to message exchanges (e.g., reply message might not be coming which is equivalent to + method not returning), it is important to go over several scenarios especially in the Sync Gateway case and understand + what the default behavior of the Gateway and how to deal with these scenarios to make Sync Gateway behavior more + predictable regardless of the outcome of the message flow that was initialed from such Gateway. + + + There are certain attributes that could be configured to make Sync Gateway behavior more predictable, + but some of them might not always work as you might have expected. One of them is reply-timeout. + So, lets look at the reply-timeout attribute and see how it can/can't influence the behavior + of the Sync Gateway in various scenarios. We will look at single-theraded scenario + (all components downstream are connected via Direct Channel) and multi-theraded scenarios + (e.g., somewhere downstream you may have Pollable or Executor Channel which breaks single-thread boundary) + + + Long running process downstream + + + Sync Gateway - single-threaded. + If a component downstream is still running (e.g., infinite loop or a very slow service), then setting reply-timeout + has no effect and Gateway method call will not return until such downstream service exits (e.g., return or exception). + Sync Gateway - multi-threaded. + If a component downstream is still running (e.g., infinite loop or a very slow service), in a multi-threaded message + flow setting reply-timeout will have an effect by allowing gateway method invocation to + return once the timeout has been reached, since GatewayProxyFactoryBean  will simply + poll on the reply channel waiting for a message untill the timeout expires. However it could result in the 'null' return + from the Gateway method if the timeout has been reached before the actual reply was produced. It is also important to understand that + the reply message (if produced) will be sent to a reply channel after Gateway method invocation might have returned, so you must be aware of that + and design your flow with this in mind. + + + Downstream component returns 'null' + + + Sync Gateway - single-threaded. + If a component downstream returns 'null' and no reply-timeout has been configured, the Gateway + method call will hang indefinitely unless: a) reply-timeout has been configured or b) + requires-reply attribute has been set on the downstream component (e.g., service-activator) + that might return 'null'. In this case, the exception will be thrown and propagated to the Gateway. + Sync Gateway - multi-threaded. Behavior is the same as above. + + + Downstream component return signature is 'void' while Gateway method signature is non-void + + + Sync Gateway - single-threaded. + If a component downstream returns 'void' and no reply-timeout has been configured, + the Gateway method call will hang indefinitely unless reply-timeout has been configured  + Sync Gateway - multi-threaded Behavior is the same as above. + + + Downstream component results in Runtime Exception (regardless of the method signature) + + + Sync Gateway - single-threaded. + If a component downstream throws a Runtime Exception, such exception will be propagated via Error Message back to + the gateway and re-thrown. + Sync Gateway - multi-threaded Behavior is the same as above. + + + + It is also important to understand that by default reply-timout is unbounded which means that + if not explicitly set there are several scenarios (described above) where your Gateway method invocation might + hang indefinitely, so make sure you analyze your flow and if there is even a remote possibility of one of these + scenarios to occur in your flow, set the reply-timout to a 'safe' value at least for the sake + of bringing method invocation to a close. But also, realize that there are some scenarios (see the very first one) + where reply-timout will not help which means it is also important to analyze your message + flow and decide when to use Sync Gateway vs Async Gateway where Gateway method invocation is always guaranteed + to return while giving you a more granular control over the results of the invocation via Java Futures. + + +
\ No newline at end of file