From 3cfbbd9f792482556163c02b82a9d0f93be5ce73 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 5 Oct 2010 13:53:24 -0700 Subject: [PATCH 01/58] Remove unnecessary instanceof check and @Suppress --- .../router/AbstractChannelNameResolvingMessageRouter.java | 1 - 1 file changed, 1 deletion(-) 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 index 259c32aa84..93ccd27120 100644 --- 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 @@ -119,7 +119,6 @@ public abstract class AbstractChannelNameResolvingMessageRouter extends Abstract return channels; } - @SuppressWarnings("unchecked") private void addToCollection(Collection channels, Collection channelIndicators, Message message) { if (channelIndicators == null) { return; From 8cf6e474d233bc43d1e65b6ea61565a4f27e9d1d Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 21 Sep 2010 09:31:29 +0200 Subject: [PATCH 02/58] INT-1452: fix names and counts attributes Add global counts to mbean exporter --- .../AbstractMessageHandlerFactoryBean.java | 2 +- .../monitor/IntegrationMBeanExporter.java | 49 +++++++++++++------ .../LifecycleMessageHandlerMonitor.java | 4 ++ .../monitor/MessageHandlerMonitor.java | 3 ++ .../monitor/SimpleMessageHandlerMonitor.java | 10 ++++ .../HandlerMonitoringIntegrationTests.java | 4 +- 6 files changed, 54 insertions(+), 18 deletions(-) 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..beeabf137c 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 @@ -125,7 +125,7 @@ abstract class AbstractMessageHandlerFactoryBean implements FactoryBean) { this.handler = this.createMessageProcessingHandler((MessageProcessor) this.targetObject); } else { 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..d64d367fc6 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; @@ -89,10 +88,6 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP public static final String DEFAULT_DOMAIN = "spring.application"; - private Set channelKeys = new HashSet(); - - private Set handlerKeys = new HashSet(); - private final AnnotationJmxAttributeSource attributeSource = new AnnotationJmxAttributeSource(); private ListableBeanFactory beanFactory; @@ -165,11 +160,13 @@ 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 + 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 { + } + else { monitor = new SimpleMessageHandlerMonitor((MessageHandler) bean); - } + } handlers.add(monitor); return monitor; } @@ -285,23 +282,43 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } @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 (MessageHandlerMonitor monitor : handlers) { + count += monitor.getActiveCount(); + } + return count; + } + + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Queued Message Count") + public int getQueuedMessageCount() { + int count = 0; + for (MessageChannelMonitor monitor : channels) { + if (monitor instanceof QueueChannelMonitor) { + count += ((QueueChannelMonitor) 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") 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/LifecycleMessageHandlerMonitor.java index c630add493..cce1885892 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/LifecycleMessageHandlerMonitor.java @@ -92,4 +92,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/MessageHandlerMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMonitor.java index 4e06cfc904..8bdfd013d9 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/MessageHandlerMonitor.java @@ -58,6 +58,9 @@ public interface MessageHandlerMonitor { @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Standard Deviation Duration") double getStandardDeviationDuration(); + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Active Status") + int getActiveCount(); + /** * @return summary statistics about the handler duration (milliseconds) */ 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/SimpleMessageHandlerMonitor.java index ab6fa1c9a8..67d99257df 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/SimpleMessageHandlerMonitor.java @@ -44,6 +44,8 @@ public class SimpleMessageHandlerMonitor implements MessageHandler, MessageHandl private final MessageHandler handler; + private final AtomicInteger activeCount = new AtomicInteger(); + private final AtomicInteger handleCount = new AtomicInteger(); private final AtomicInteger errorCount = new AtomicInteger(); @@ -94,6 +96,7 @@ public class SimpleMessageHandlerMonitor implements MessageHandler, MessageHandl try { timer.start(); handleCount.incrementAndGet(); + activeCount.incrementAndGet(); handler.handleMessage(message); @@ -105,6 +108,8 @@ public class SimpleMessageHandlerMonitor implements MessageHandler, MessageHandl } catch (Error e) { errorCount.incrementAndGet(); throw e; + } finally { + activeCount.decrementAndGet(); } } @@ -141,6 +146,11 @@ public class SimpleMessageHandlerMonitor implements MessageHandler, MessageHandl return duration.getStandardDeviation(); } + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Active Count") + public int getActiveCount() { + return activeCount.get(); + } + public Statistics getDuration() { return duration.getStatistics(); } 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(); From 07043a8875a46bf3026dc742004c489c70e640e7 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 5 Oct 2010 13:12:58 -0700 Subject: [PATCH 03/58] INT-1489: inspect router to see if it is Advised - Switch to proxy approach for handler monitor - Add router mbean integration test --- .../integration/config/RouterFactoryBean.java | 89 +++++++++++++------ .../config/xml/PayloadTypeRouterParser.java | 4 +- .../monitor/IntegrationMBeanExporter.java | 9 +- .../monitor/SimpleMessageHandlerMonitor.java | 16 +++- .../jmx/config/MBeanExporterParserTests.java | 2 +- .../jmx/config/RouterMBeanTests-context.xml | 30 +++++++ .../jmx/config/RouterMBeanTests.java | 47 ++++++++++ 7 files changed, 162 insertions(+), 35 deletions(-) create mode 100644 spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/RouterMBeanTests-context.xml create mode 100644 spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/RouterMBeanTests.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java index 4abfdb3346..ddc902ac7b 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java @@ -1,21 +1,20 @@ /* * 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.config; +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.Advised; import org.springframework.integration.MessageChannel; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.router.AbstractChannelNameResolvingMessageRouter; @@ -48,7 +47,6 @@ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean { private volatile Boolean ignoreSendFailures; - public void setChannelResolver(ChannelResolver channelResolver) { this.channelResolver = channelResolver; } @@ -79,9 +77,49 @@ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean { @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); + 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 @@ -90,21 +128,13 @@ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean { } 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) { + if (this.channelResolver != null && router instanceof AbstractChannelNameResolvingMessageRouter) { ((AbstractChannelNameResolvingMessageRouter) router).setChannelResolver(this.channelResolver); } if (this.defaultOutputChannel != null) { @@ -114,10 +144,11 @@ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean { router.setTimeout(timeout.longValue()); } if (this.ignoreChannelNameResolutionFailures != null) { - Assert.isTrue(router instanceof AbstractChannelNameResolvingMessageRouter, + Assert.isTrue(router instanceof AbstractChannelNameResolvingMessageRouter, "The 'ignoreChannelNameResolutionFailures' property can only be set on routers that extend " - + AbstractChannelNameResolvingMessageRouter.class.getName()); - ((AbstractChannelNameResolvingMessageRouter) router).setIgnoreChannelNameResolutionFailures(ignoreChannelNameResolutionFailures); + + AbstractChannelNameResolvingMessageRouter.class.getName()); + ((AbstractChannelNameResolvingMessageRouter) router) + .setIgnoreChannelNameResolutionFailures(ignoreChannelNameResolutionFailures); } if (this.applySequence != null) { router.setApplySequence(this.applySequence); 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..5a87301842 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 @@ -20,6 +20,7 @@ import java.util.List; import org.w3c.dom.Element; +import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -39,14 +40,13 @@ import org.springframework.util.xml.DomUtils; public class PayloadTypeRouterParser extends AbstractRouterParser { @Override - @SuppressWarnings("unchecked") protected BeanDefinition parseRouter(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(); + ManagedMap channelMap = new ManagedMap(); for (Element childElement : childElements) { String typeName = childElement.getAttribute("type"); ClassLoader classLoader = parserContext.getReaderContext().getBeanClassLoader(); 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 d64d367fc6..d704b7a52c 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 @@ -167,8 +167,9 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP else { monitor = new SimpleMessageHandlerMonitor((MessageHandler) bean); } + Object advised = applyHandlerInterceptor(bean, monitor, beanClassLoader); handlers.add(monitor); - return monitor; + return advised; } if (bean instanceof MessageChannel) { DirectChannelMonitor monitor; @@ -404,6 +405,12 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP return applyAdvice(bean, channelsAdvice, beanClassLoader); } + private Object applyHandlerInterceptor(Object bean, SimpleMessageHandlerMonitor interceptor, ClassLoader beanClassLoader) { + NameMatchMethodPointcutAdvisor handlerAdvice = new NameMatchMethodPointcutAdvisor(interceptor); + handlerAdvice.addMethodName("handleMessage"); + return applyAdvice(bean, handlerAdvice, beanClassLoader); + } + private Object extractTarget(Object bean) { if (!(bean instanceof Advised)) { return bean; 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/SimpleMessageHandlerMonitor.java index 67d99257df..0247a47289 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/SimpleMessageHandlerMonitor.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; @@ -36,7 +38,7 @@ import org.springframework.util.StopWatch; * */ @ManagedResource -public class SimpleMessageHandlerMonitor implements MessageHandler, MessageHandlerMonitor { +public class SimpleMessageHandlerMonitor implements MethodInterceptor, MessageHandlerMonitor { private static final Log logger = LogFactory.getLog(SimpleMessageHandlerMonitor.class); @@ -81,7 +83,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 + ") :"); 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/RouterMBeanTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/RouterMBeanTests-context.xml new file mode 100644 index 0000000000..31190c0e19 --- /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..6f79b88245 --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/RouterMBeanTests.java @@ -0,0 +1,47 @@ +/* + * 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 javax.management.MBeanServer; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.integration.monitor.IntegrationMBeanExporter; +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 ApplicationContext context; + + @Test + public void testMBeanExporterExists() throws InterruptedException { + IntegrationMBeanExporter exporter = this.context.getBean(IntegrationMBeanExporter.class); + MBeanServer server = this.context.getBean("mbs", MBeanServer.class); + assertEquals(server, exporter.getServer()); + exporter.destroy(); + } + +} From 76d17bd187ecdaa35a50b0f5e28ed3202ff3fd2f Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 5 Oct 2010 15:47:18 -0700 Subject: [PATCH 04/58] Expose MessageSources and their polling endpoints to JMX --- .../monitor/IntegrationMBeanExporter.java | 139 +++++++++++++++++- .../LifecycleMessageSourceMonitor.java | 75 ++++++++++ .../monitor/MessageSourceMonitor.java | 36 +++++ .../monitor/SimpleMessageSourceMonitor.java | 79 ++++++++++ .../PollingAdapterMBeanTests-context.xml | 26 ++++ .../jmx/config/PollingAdapterMBeanTests.java | 54 +++++++ 6 files changed, 401 insertions(+), 8 deletions(-) create mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageSourceMonitor.java create mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageSourceMonitor.java create mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMonitor.java create mode 100644 spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests-context.xml create mode 100644 spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests.java 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 d704b7a52c..015da2e62c 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 @@ -41,6 +41,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; @@ -92,16 +93,22 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP private ListableBeanFactory beanFactory; - private Map anonymousCounters = new HashMap(); + private Map anonymousHandlerCounters = new HashMap(); + + private Map anonymousSourceCounters = new HashMap(); private Set handlers = new HashSet(); + private Set sources = new HashSet(); + 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(); private ClassLoader beanClassLoader; @@ -170,6 +177,11 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP Object advised = applyHandlerInterceptor(bean, monitor, beanClassLoader); handlers.add(monitor); return advised; + } else if (bean instanceof MessageSource) { + SimpleMessageSourceMonitor monitor = new SimpleMessageSourceMonitor((MessageSource) bean); + Object advised = applySourceInterceptor(bean, monitor, beanClassLoader); + sources.add(monitor); + return advised; } if (bean instanceof MessageChannel) { DirectChannelMonitor monitor; @@ -268,6 +280,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP protected void doStart() { registerChannels(); registerHandlers(); + registerSources(); logger.info("Summary on start: " + objectNamesByName); } @@ -384,7 +397,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP private void registerHandlers() { for (SimpleMessageHandlerMonitor source : handlers) { - MessageHandlerMonitor monitor = enhanceMonitor(source); + MessageHandlerMonitor monitor = enhanceHandlerMonitor(source); String name = monitor.getName(); // Only register once... if (!handlersByName.containsKey(name)) { @@ -398,6 +411,22 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } } + private void registerSources() { + for (SimpleMessageSourceMonitor source : sources) { + MessageSourceMonitor 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, DirectChannelMonitor interceptor, ClassLoader beanClassLoader) { NameMatchMethodPointcutAdvisor channelsAdvice = new NameMatchMethodPointcutAdvisor(interceptor); channelsAdvice.addMethodName("send"); @@ -411,6 +440,12 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP return applyAdvice(bean, handlerAdvice, beanClassLoader); } + private Object applySourceInterceptor(Object bean, SimpleMessageSourceMonitor 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; @@ -458,6 +493,12 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP handler.getSource()); } + private String getSourceBeanKey(MessageSourceMonitor 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 ""; @@ -469,7 +510,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP return builder.toString(); } - private MessageHandlerMonitor enhanceMonitor(SimpleMessageHandlerMonitor monitor) { + private MessageHandlerMonitor enhanceHandlerMonitor(SimpleMessageHandlerMonitor monitor) { MessageHandlerMonitor result = monitor; @@ -488,12 +529,12 @@ 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.debug("Could not get handler from bean = " + beanName); } - if (field == monitor) { + if (field == monitor.getMessageHandler()) { name = beanName; break; } @@ -517,10 +558,10 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } 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 = ""; /* @@ -551,6 +592,88 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } + private MessageSourceMonitor enhanceSourceMonitor(SimpleMessageSourceMonitor monitor) { + + MessageSourceMonitor 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.debug("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 LifecycleMessageSourceMonitor((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/LifecycleMessageSourceMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageSourceMonitor.java new file mode 100644 index 0000000000..fc69442bb1 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageSourceMonitor.java @@ -0,0 +1,75 @@ +/* + * 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 MessageSourceMonitor} 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 LifecycleMessageSourceMonitor implements MessageSourceMonitor, Lifecycle { + + private final Lifecycle lifecycle; + + private final MessageSourceMonitor delegate; + + public LifecycleMessageSourceMonitor(Lifecycle lifecycle, MessageSourceMonitor delegate) { + this.lifecycle = lifecycle; + this.delegate = delegate; + } + + @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.MessageSourceMonitor#getMessageCount() + */ + public int getMessageCount() { + return delegate.getMessageCount(); + } + +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageSourceMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageSourceMonitor.java new file mode 100644 index 0000000000..0b988a9968 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageSourceMonitor.java @@ -0,0 +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.monitor; + +import org.springframework.jmx.export.annotation.ManagedMetric; +import org.springframework.jmx.support.MetricType; + +/** + * @author Dave Syer + * + * @since 2.0 + */ +public interface MessageSourceMonitor { + + /** + * @return the number of successful handler calls + */ + @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Message Source Message Count", description = "rate=1h") + int getMessageCount(); + + String getName(); + + String getSource(); + +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMonitor.java new file mode 100644 index 0000000000..c54508eda1 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMonitor.java @@ -0,0 +1,79 @@ +/* + * 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 SimpleMessageSourceMonitor implements MethodInterceptor, MessageSourceMonitor { + + private final AtomicInteger messageCount = new AtomicInteger(); + + private final MessageSource messageSource; + + private String source; + + private String name; + + public SimpleMessageSourceMonitor(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 int getMessageCount() { + return messageCount.get(); + } + + public Object invoke(MethodInvocation invocation) throws Throwable { + String method = invocation.getMethod().getName(); + if ("receive".equals(method)) { + messageCount.incrementAndGet(); + } + return invocation.proceed(); + } + + @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/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..14b1f093d0 --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests-context.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + 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..1a08698dce --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests.java @@ -0,0 +1,54 @@ +/* + * 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 javax.management.MBeanServer; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.integration.monitor.IntegrationMBeanExporter; +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 ApplicationContext context; + + @Test + public void testMBeanExporterExists() throws InterruptedException { + IntegrationMBeanExporter exporter = this.context.getBean(IntegrationMBeanExporter.class); + MBeanServer server = this.context.getBean("mbs", MBeanServer.class); + assertEquals(server, exporter.getServer()); + exporter.destroy(); + } + + public static class Source { + public String get() { + System.err.println("*** " + System.currentTimeMillis()); + return "foo"; + } + } + +} From cbf0242bd97d41b1801a6a82125d924802a7046c Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 6 Oct 2010 07:10:12 -0700 Subject: [PATCH 05/58] INT-1495: harmonise schema --- .../jmx/config/AttributePollingChannelAdapterParser.java | 2 +- .../integration/jmx/config/JmxNamespaceHandler.java | 2 +- .../integration/jmx/config/MBeanExporterParser.java | 2 +- .../config/NotificationListeningChannelAdapterParser.java | 2 +- .../jmx/config/OperationInvokingChannelAdapterParser.java | 2 +- .../jmx/config/OperationInvokingOutboundGatewayParser.java | 2 +- .../integration/jmx/config/spring-integration-jmx-2.0.xsd | 4 ++-- .../AttributePollingChannelAdapterParserTests-context.xml | 4 +--- .../jmx/config/ControlBusParserTests-context.xml | 2 +- .../jmx/config/MBeanExporterParserTests-context.xml | 2 +- .../jmx/config/PollingAdapterMBeanTests-context.xml | 6 +++++- .../integration/jmx/config/PollingAdapterMBeanTests.java | 1 - .../integration/jmx/config/RouterMBeanTests-context.xml | 4 ++-- 13 files changed, 18 insertions(+), 17 deletions(-) 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..1085e33529 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 @@ -51,7 +51,7 @@ public class MBeanExporterParser extends AbstractSingleBeanDefinitionParser { } 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/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..241d3dbf3b 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. @@ -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/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..9e6413ee02 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..96e186add0 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/PollingAdapterMBeanTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/PollingAdapterMBeanTests-context.xml index 14b1f093d0..80c02eca68 100644 --- 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 @@ -13,14 +13,18 @@ http://www.springframework.org/schema/integration/jmx/spring-integration-jmx.xsd"> + + - + + + 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 index 1a08698dce..47cfb3c5cf 100644 --- 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 @@ -46,7 +46,6 @@ public class PollingAdapterMBeanTests { public static class Source { public String get() { - System.err.println("*** " + System.currentTimeMillis()); 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 index 31190c0e19..a61e80e45d 100644 --- 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 @@ -17,14 +17,14 @@ - + - From 1d44c8af2c5d8921d3023298d99a6ff55212d6a6 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 6 Oct 2010 07:27:55 -0700 Subject: [PATCH 06/58] INT-1449: tweak test case --- .../jmx/config/PollingAdapterMBeanTests-context.xml | 4 ++-- .../integration/jmx/config/PollingAdapterMBeanTests.java | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) 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 index 80c02eca68..49f81b6930 100644 --- 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 @@ -14,7 +14,7 @@ - + @@ -25,6 +25,6 @@ - + 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 index 47cfb3c5cf..0455801f96 100644 --- 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 @@ -15,7 +15,10 @@ 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; @@ -37,10 +40,12 @@ public class PollingAdapterMBeanTests { private ApplicationContext context; @Test - public void testMBeanExporterExists() throws InterruptedException { + public void testMBeanExporterExists() throws Exception { IntegrationMBeanExporter exporter = this.context.getBean(IntegrationMBeanExporter.class); MBeanServer server = this.context.getBean("mbs", MBeanServer.class); assertEquals(server, exporter.getServer()); + Set names = server.queryNames(new ObjectName("spring.application:type=MessageSource,*"), null); + assertEquals(1, names.size()); exporter.destroy(); } From 84415d6f53758f827f39fa79ab491c5b9d3732ce Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 6 Oct 2010 10:25:58 -0700 Subject: [PATCH 07/58] INT-1449: ensure mbeans only registered once for factory bean product --- .../monitor/IntegrationMBeanExporter.java | 70 ++++++++++--------- .../jmx/config/PollingAdapterMBeanTests.java | 13 ++-- .../jmx/config/RouterMBeanTests-context.xml | 2 +- .../jmx/config/RouterMBeanTests.java | 22 +++--- 4 files changed, 57 insertions(+), 50 deletions(-) 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 015da2e62c..3c8c5dca25 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 @@ -21,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; @@ -165,13 +167,24 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + + if (bean instanceof Advised) { + for (Advisor advisor : ((Advised) bean).getAdvisors()) { + Advice advice = advisor.getAdvice(); + if (advice instanceof MessageHandlerMonitor || advice instanceof MessageSourceMonitor + || advice instanceof MessageChannelMonitor) { + // Already advised - so probably a factory bean product + return bean; + } + } + } + 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 + if (bean instanceof MessageProducer) { + // We need to maintain semantics of the handler also being a producer monitor = new SimpleMessageProducingHandlerMonitor((MessageHandler) bean); - } - else { + } else { monitor = new SimpleMessageHandlerMonitor((MessageHandler) bean); } Object advised = applyHandlerInterceptor(bean, monitor, beanClassLoader); @@ -183,25 +196,26 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP sources.add(monitor); return advised; } + if (bean instanceof MessageChannel) { DirectChannelMonitor monitor; if (bean instanceof PollableChannel) { Object target = extractTarget(bean); if (target instanceof QueueChannel) { monitor = new QueueChannelMonitor((QueueChannel) target, beanName); - } - else { + } else { monitor = new PollableChannelMonitor(beanName); } - } - else { + } else { monitor = new DirectChannelMonitor(beanName); } Object advised = applyChannelInterceptor(bean, monitor, beanClassLoader); channels.add(monitor); return advised; } + return bean; + } public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { @@ -225,8 +239,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP this.lifecycleLock.lock(); try { return this.running; - } - finally { + } finally { this.lifecycleLock.unlock(); } } @@ -241,8 +254,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP logger.info("started " + this); } } - } - finally { + } finally { this.lifecycleLock.unlock(); } } @@ -257,8 +269,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP logger.info("stopped " + this); } } - } - finally { + } finally { this.lifecycleLock.unlock(); } } @@ -268,8 +279,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP try { this.stop(); callback.run(); - } - finally { + } finally { this.lifecycleLock.unlock(); } } @@ -434,13 +444,15 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP return applyAdvice(bean, channelsAdvice, beanClassLoader); } - private Object applyHandlerInterceptor(Object bean, SimpleMessageHandlerMonitor interceptor, ClassLoader beanClassLoader) { + private Object applyHandlerInterceptor(Object bean, SimpleMessageHandlerMonitor interceptor, + ClassLoader beanClassLoader) { NameMatchMethodPointcutAdvisor handlerAdvice = new NameMatchMethodPointcutAdvisor(interceptor); handlerAdvice.addMethodName("handleMessage"); return applyAdvice(bean, handlerAdvice, beanClassLoader); } - private Object applySourceInterceptor(Object bean, SimpleMessageSourceMonitor interceptor, ClassLoader beanClassLoader) { + private Object applySourceInterceptor(Object bean, SimpleMessageSourceMonitor interceptor, + ClassLoader beanClassLoader) { NameMatchMethodPointcutAdvisor sourceAdvice = new NameMatchMethodPointcutAdvisor(interceptor); sourceAdvice.addMethodName("receive"); return applyAdvice(bean, sourceAdvice, beanClassLoader); @@ -456,8 +468,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; } @@ -469,8 +480,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); @@ -530,9 +540,8 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP Object field = null; try { field = extractTarget(getField(endpoint, "handler")); - } - catch (Exception e) { - logger.debug("Could not get handler from bean = " + beanName); + } catch (Exception e) { + logger.trace("Could not get handler from bean = " + beanName); } if (field == monitor.getMessageHandler()) { name = beanName; @@ -550,8 +559,7 @@ 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); } } @@ -612,9 +620,8 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP Object field = null; try { field = extractTarget(getField(endpoint, "source")); - } - catch (Exception e) { - logger.debug("Could not get source from bean = " + beanName); + } catch (Exception e) { + logger.trace("Could not get source from bean = " + beanName); } if (field == monitor.getMessageSource()) { name = beanName; @@ -632,8 +639,7 @@ 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); } } 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 index 0455801f96..314a6a1088 100644 --- 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 @@ -23,8 +23,6 @@ import javax.management.ObjectName; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.integration.monitor.IntegrationMBeanExporter; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -37,16 +35,13 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; public class PollingAdapterMBeanTests { @Autowired - private ApplicationContext context; - + private MBeanServer server; + @Test - public void testMBeanExporterExists() throws Exception { - IntegrationMBeanExporter exporter = this.context.getBean(IntegrationMBeanExporter.class); - MBeanServer server = this.context.getBean("mbs", MBeanServer.class); - assertEquals(server, exporter.getServer()); + public void testMessageSourceMBeanExists() throws Exception { + // System.err.println(server.queryNames(new ObjectName("*:type=MessageSource,*"), null)); Set names = server.queryNames(new ObjectName("spring.application:type=MessageSource,*"), null); assertEquals(1, names.size()); - exporter.destroy(); } public static class Source { 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 index a61e80e45d..eca2df4493 100644 --- 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 @@ -25,6 +25,6 @@ + domain="tests.RouterMBean" /> 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 index 6f79b88245..c51fd6e47d 100644 --- 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 @@ -15,13 +15,14 @@ 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.context.ApplicationContext; -import org.springframework.integration.monitor.IntegrationMBeanExporter; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -34,14 +35,19 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; public class RouterMBeanTests { @Autowired - private ApplicationContext context; + private MBeanServer server; + + @Test + public void testRouterMBeanExists() throws Exception { + Set names = server.queryNames(new ObjectName("*:type=MessageHandler,name=ptRouter,*"), null); + assertEquals(1, names.size()); + } @Test - public void testMBeanExporterExists() throws InterruptedException { - IntegrationMBeanExporter exporter = this.context.getBean(IntegrationMBeanExporter.class); - MBeanServer server = this.context.getBean("mbs", MBeanServer.class); - assertEquals(server, exporter.getServer()); - exporter.destroy(); + 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("*:type=MessageHandler,*"), null).size()); } } From 71f171b1632ea49e669a9c94e3be8e7558a4b3ff Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 6 Oct 2010 10:56:08 -0700 Subject: [PATCH 08/58] INT-1495: change domain= to default-deomain= --- .../jmx/config/MBeanExporterParser.java | 2 +- .../monitor/IntegrationMBeanExporter.java | 2 +- .../jmx/config/spring-integration-jmx-2.0.xsd | 2 +- .../ControlBusOperationChannelTests.java | 2 +- .../integration/control/ControlBusTests.java | 2 +- .../control/ControlBusXmlTests-context.xml | 2 +- .../config/ControlBusParserTests-context.xml | 2 +- .../MBeanExporterParserTests-context.xml | 2 +- .../jmx/config/RouterMBeanTests-context.xml | 2 +- .../jmx/config/RouterMBeanTests.java | 4 +-- ...hMessageProducingHandlersTests-context.xml | 2 +- ...hainWithMessageProducingHandlersTests.java | 2 +- .../ChannelIntegrationTests-context.xml | 2 +- .../ExponentialMovingAverageRateTests.java | 33 +++++++++---------- 14 files changed, 29 insertions(+), 32 deletions(-) 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 1085e33529..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,7 +46,7 @@ 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); } 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 3c8c5dca25..d98780dcea 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 @@ -156,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; } 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 241d3dbf3b..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 @@ -105,7 +105,7 @@ - + The domain name for the MBeans exported by this Exporter. 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..41ed89793c 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 @@ -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/jmx/config/ControlBusParserTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/ControlBusParserTests-context.xml index 9e6413ee02..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 96e186add0..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/RouterMBeanTests-context.xml b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/RouterMBeanTests-context.xml index eca2df4493..7947c69fc1 100644 --- 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 @@ -25,6 +25,6 @@ + default-domain="test.RouterMBean" /> 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 index c51fd6e47d..cdeedd62e8 100644 --- 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 @@ -39,7 +39,7 @@ public class RouterMBeanTests { @Test public void testRouterMBeanExists() throws Exception { - Set names = server.queryNames(new ObjectName("*:type=MessageHandler,name=ptRouter,*"), null); + Set names = server.queryNames(new ObjectName("test.RouterMBean:type=MessageHandler,name=ptRouter,*"), null); assertEquals(1, names.size()); } @@ -47,7 +47,7 @@ public class RouterMBeanTests { 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("*:type=MessageHandler,*"), null).size()); + 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..8a701a3d10 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,17 +1,14 @@ /* * 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; @@ -27,8 +24,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 +45,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 +55,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 +71,7 @@ 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); } } From a21172fdab4d0dd63e14406eeb098d0be75e88cb Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 6 Oct 2010 11:00:59 -0700 Subject: [PATCH 09/58] INT-1496: start optimistically with success rate 100% --- .../monitor/ExponentialMovingAverageRatio.java | 3 ++- .../monitor/ExponentialMovingAverageRatioTests.java | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) 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..6240b6cb96 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 @@ -92,7 +92,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/test/java/org/springframework/integration/monitor/ExponentialMovingAverageRatioTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/monitor/ExponentialMovingAverageRatioTests.java index acbb8e7e67..09feb9eb72 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 @@ -45,14 +45,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 +66,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 +77,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 +88,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(); From be2b1343a0bceda40a5c84eadaedcc9afc9bbd3c Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 6 Oct 2010 11:10:50 -0700 Subject: [PATCH 10/58] INT-1492: add test --- .../config/MBeanRegistrationTests-context.xml | 29 +++++++++ .../jmx/config/MBeanRegistrationTests.java | 59 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanRegistrationTests-context.xml create mode 100644 spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanRegistrationTests.java 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..0ed79c09bb --- /dev/null +++ b/spring-integration-jmx/src/test/java/org/springframework/integration/jmx/config/MBeanRegistrationTests.java @@ -0,0 +1,59 @@ +/* + * 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)); + Set names = server.queryNames(new ObjectName("test.MBeanRegistration:type=*MBeanExporter,name=integrationMbeanExporter,*"), null); + assertEquals(1, names.size()); + } + + public static class Source { + public String get() { + return "foo"; + } + } + +} From e2c1616d864e0ef1e53fe0f9909162daa0e17bb2 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 6 Oct 2010 11:27:09 -0700 Subject: [PATCH 11/58] INT-1449: fix broken message count in message source monitor --- .../integration/monitor/SimpleMessageSourceMonitor.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMonitor.java index c54508eda1..863b00caba 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMonitor.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMonitor.java @@ -65,10 +65,11 @@ public class SimpleMessageSourceMonitor implements MethodInterceptor, MessageSou public Object invoke(MethodInvocation invocation) throws Throwable { String method = invocation.getMethod().getName(); - if ("receive".equals(method)) { + Object result = invocation.proceed(); + if ("receive".equals(method) && result!=null) { messageCount.incrementAndGet(); } - return invocation.proceed(); + return result; } @Override From 71c7872270a8c7c65ff21f0480630ae02b9d919c Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 6 Oct 2010 11:35:18 -0700 Subject: [PATCH 12/58] INT-1495: change default domain to org.springframework.integration --- .../integration/monitor/IntegrationMBeanExporter.java | 2 +- .../jmx/config/PollingAdapterMBeanTests-context.xml | 4 ++-- .../integration/jmx/config/PollingAdapterMBeanTests.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) 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 d98780dcea..5d0b915710 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 @@ -89,7 +89,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP private static final Log logger = LogFactory.getLog(IntegrationMBeanExporter.class); - public static final String DEFAULT_DOMAIN = "spring.application"; + public static final String DEFAULT_DOMAIN = "org.springframework.integration"; private final AnnotationJmxAttributeSource attributeSource = new AnnotationJmxAttributeSource(); 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 index 49f81b6930..b9ae8c7926 100644 --- 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 @@ -14,7 +14,7 @@ - + @@ -25,6 +25,6 @@ - + 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 index 314a6a1088..25faf51a4d 100644 --- 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 @@ -40,7 +40,7 @@ public class PollingAdapterMBeanTests { @Test public void testMessageSourceMBeanExists() throws Exception { // System.err.println(server.queryNames(new ObjectName("*:type=MessageSource,*"), null)); - Set names = server.queryNames(new ObjectName("spring.application:type=MessageSource,*"), null); + Set names = server.queryNames(new ObjectName("test.PollingAdapterMBean:type=MessageSource,*"), null); assertEquals(1, names.size()); } From 3ddc163217453ba0c33574bdb3094f5d3b5ee747 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 7 Oct 2010 16:49:19 -0700 Subject: [PATCH 13/58] INT-1502: implement doStop() --- .../integration/monitor/IntegrationMBeanExporter.java | 4 ++++ 1 file changed, 4 insertions(+) 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 5d0b915710..d5df469ba5 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 @@ -285,6 +285,10 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } protected void doStop() { + unregisterBeans(); + channelsByName.clear(); + handlersByName.clear(); + sourcesByName.clear(); } protected void doStart() { From 48c1c6b98a8d9046ddc0e161aad6ddc918d8b4ae Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 7 Oct 2010 18:53:34 -0700 Subject: [PATCH 14/58] Fix Java 6/5 incompatiblity in test --- .../integration/jmx/config/MBeanRegistrationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 0ed79c09bb..924916c5cc 100644 --- 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 @@ -46,7 +46,7 @@ public class MBeanRegistrationTests { @Test public void testExporterMBeanRegistration() throws Exception { // System.err.println(server.queryNames(new ObjectName("*:type=*MBeanExporter,*"), null)); - Set names = server.queryNames(new ObjectName("test.MBeanRegistration:type=*MBeanExporter,name=integrationMbeanExporter,*"), null); + Set names = server.queryNames(new ObjectName("test.MBeanRegistration:type=IntegrationMBeanExporter,name=integrationMbeanExporter,*"), null); assertEquals(1, names.size()); } From 517aad3d6987f8472db40abcc40916dffaa193eb Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 8 Oct 2010 06:28:12 -0700 Subject: [PATCH 15/58] INT-1503: *Monitor->*Metrics --- .../monitor/DirectChannelMonitor.java | 200 ------------------ .../monitor/IntegrationMBeanExporter.java | 80 +++---- .../LifecycleMessageHandlerMonitor.java | 99 --------- .../LifecycleMessageSourceMonitor.java | 75 ------- .../monitor/MessageChannelMonitor.java | 107 ---------- .../monitor/MessageHandlerMonitor.java | 73 ------- .../monitor/MessageSourceMonitor.java | 36 ---- .../monitor/PollableChannelMonitor.java | 86 -------- .../monitor/QueueChannelMonitor.java | 50 ----- .../monitor/SimpleMessageHandlerMonitor.java | 175 --------------- .../SimpleMessageProducingHandlerMonitor.java | 38 ---- .../monitor/SimpleMessageSourceMonitor.java | 80 ------- .../integration/control/ControlBusTests.java | 18 +- .../control/ControlBusXmlTests.java | 16 +- 14 files changed, 57 insertions(+), 1076 deletions(-) delete mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMonitor.java delete mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMonitor.java delete mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageSourceMonitor.java delete mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageChannelMonitor.java delete mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMonitor.java delete mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageSourceMonitor.java delete mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/PollableChannelMonitor.java delete mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/QueueChannelMonitor.java delete mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMonitor.java delete mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageProducingHandlerMonitor.java delete mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMonitor.java 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/DirectChannelMonitor.java deleted file mode 100644 index eb4f549a66..0000000000 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMonitor.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2009-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.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; - -/** - * Registers all message channels, and accumulates statistics about their performance. The statistics are then published - * locally for other components to consume and publish remotely. - * - * @author Dave Syer - * @author Helena Edelson - */ -@ManagedResource -public class DirectChannelMonitor implements MethodInterceptor, MessageChannelMonitor { - - protected final Log logger = LogFactory.getLog(getClass()); - - public static final long ONE_SECOND_SECONDS = 1; - - public static final long ONE_MINUTE_SECONDS = 60; - - public static final int DEFAULT_MOVING_AVERAGE_WINDOW = 10; - - private ExponentialMovingAverage sendDuration = new ExponentialMovingAverage( - DEFAULT_MOVING_AVERAGE_WINDOW); - - private final ExponentialMovingAverageRate sendErrorRate = new ExponentialMovingAverageRate( - ONE_SECOND_SECONDS, ONE_MINUTE_SECONDS, DEFAULT_MOVING_AVERAGE_WINDOW); - - private final ExponentialMovingAverageRatio sendSuccessRatio = new ExponentialMovingAverageRatio( - ONE_MINUTE_SECONDS, DEFAULT_MOVING_AVERAGE_WINDOW); - - private final ExponentialMovingAverageRate sendRate = new ExponentialMovingAverageRate( - ONE_SECOND_SECONDS, ONE_MINUTE_SECONDS, DEFAULT_MOVING_AVERAGE_WINDOW); - - private final AtomicInteger sendCount = new AtomicInteger(); - - private final AtomicInteger sendErrorCount = new AtomicInteger(); - - private final String name; - - public DirectChannelMonitor(String name) { - this.name = name; - } - - public void destroy() { - if (logger.isDebugEnabled()) { - logger.debug(sendDuration); - } - } - - public String getName() { - return name; - } - - public Object invoke(MethodInvocation invocation) throws Throwable { - String method = invocation.getMethod().getName(); - MessageChannel channel = (MessageChannel) invocation.getThis(); - return doInvoke(invocation, method, channel); - } - - protected Object doInvoke(MethodInvocation invocation, String method, MessageChannel channel) throws Throwable { - if ("send".equals(method)) { - Message message = (Message) invocation.getArguments()[0]; - return monitorSend(invocation, channel, message); - } - return invocation.proceed(); - } - - private Object monitorSend(MethodInvocation invocation, MessageChannel channel, Message message) - throws Throwable { - - if (logger.isTraceEnabled()) { - logger.trace("Recording send on channel(" + channel + ") : message(" + message + ")"); - } - - final StopWatch timer = new StopWatch(channel + ".send:execution"); - - try { - timer.start(); - - sendCount.incrementAndGet(); - sendRate.increment(); - - Object result = invocation.proceed(); - - timer.stop(); - if ((Boolean)result) { - sendSuccessRatio.success(); - sendDuration.append(timer.getTotalTimeSeconds()); - } else { - sendSuccessRatio.failure(); - sendErrorCount.incrementAndGet(); - sendErrorRate.increment(); - } - return result; - - } - catch (Throwable e) { - sendSuccessRatio.failure(); - sendErrorCount.incrementAndGet(); - sendErrorRate.increment(); - throw e; - } - finally { - if (logger.isTraceEnabled()) { - logger.trace(timer); - } - } - } - - @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(); - } - - public Statistics getSendDuration() { - return sendDuration.getStatistics(); - } - - public Statistics getSendRate() { - return sendRate.getStatistics(); - } - - public Statistics getErrorRate() { - return sendErrorRate.getStatistics(); - } - - @Override - public String toString() { - return String.format("MessageChannelMonitor: [name=%s, sends=%d]", name, sendCount.get()); - } -} 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 d5df469ba5..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 @@ -99,17 +99,17 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP private Map anonymousSourceCounters = new HashMap(); - private Set handlers = new HashSet(); + private Set handlers = new HashSet(); - private Set sources = new HashSet(); + private Set sources = new HashSet(); - private Set channels = new HashSet(); + private Set channels = new HashSet(); - private Map channelsByName = new HashMap(); + private Map channelsByName = new HashMap(); - private Map handlersByName = new HashMap(); + private Map handlersByName = new HashMap(); - private Map sourcesByName = new HashMap(); + private Map sourcesByName = new HashMap(); private Map objectNamesByName = new HashMap(); @@ -171,8 +171,8 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP if (bean instanceof Advised) { for (Advisor advisor : ((Advised) bean).getAdvisors()) { Advice advice = advisor.getAdvice(); - if (advice instanceof MessageHandlerMonitor || advice instanceof MessageSourceMonitor - || advice instanceof MessageChannelMonitor) { + if (advice instanceof MessageHandlerMetrics || advice instanceof MessageSourceMetrics + || advice instanceof MessageChannelMetrics) { // Already advised - so probably a factory bean product return bean; } @@ -180,34 +180,34 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } if (bean instanceof MessageHandler) { - SimpleMessageHandlerMonitor monitor = null; + SimpleMessageHandlerMetrics monitor = null; if (bean instanceof MessageProducer) { // We need to maintain semantics of the handler also being a producer - monitor = new SimpleMessageProducingHandlerMonitor((MessageHandler) bean); + monitor = new SimpleMessageProducingHandlerMetrics((MessageHandler) bean); } else { - monitor = new SimpleMessageHandlerMonitor((MessageHandler) bean); + monitor = new SimpleMessageHandlerMetrics((MessageHandler) bean); } Object advised = applyHandlerInterceptor(bean, monitor, beanClassLoader); handlers.add(monitor); return advised; } else if (bean instanceof MessageSource) { - SimpleMessageSourceMonitor monitor = new SimpleMessageSourceMonitor((MessageSource) bean); + 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 PollableChannelMonitor(beanName); + monitor = new PollableChannelMetrics(beanName); } } else { - monitor = new DirectChannelMonitor(beanName); + monitor = new DirectChannelMetrics(beanName); } Object advised = applyChannelInterceptor(bean, monitor, beanClassLoader); channels.add(monitor); @@ -301,10 +301,10 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP @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); } } @@ -327,7 +327,7 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Active Handler Count") public int getActiveHandlerCount() { int count = 0; - for (MessageHandlerMonitor monitor : handlers) { + for (MessageHandlerMetrics monitor : handlers) { count += monitor.getActiveCount(); } return count; @@ -336,9 +336,9 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Queued Message Count") public int getQueuedMessageCount() { int count = 0; - for (MessageChannelMonitor monitor : channels) { - if (monitor instanceof QueueChannelMonitor) { - count += ((QueueChannelMonitor) monitor).getQueueSize(); + for (MessageChannelMetrics monitor : channels) { + if (monitor instanceof QueueChannelMetrics) { + count += ((QueueChannelMetrics) monitor).getQueueSize(); } } return count; @@ -369,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 + ")"); @@ -394,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)) { @@ -410,8 +410,8 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } private void registerHandlers() { - for (SimpleMessageHandlerMonitor source : handlers) { - MessageHandlerMonitor monitor = enhanceHandlerMonitor(source); + for (SimpleMessageHandlerMetrics source : handlers) { + MessageHandlerMetrics monitor = enhanceHandlerMonitor(source); String name = monitor.getName(); // Only register once... if (!handlersByName.containsKey(name)) { @@ -426,8 +426,8 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } private void registerSources() { - for (SimpleMessageSourceMonitor source : sources) { - MessageSourceMonitor monitor = enhanceSourceMonitor(source); + for (SimpleMessageSourceMetrics source : sources) { + MessageSourceMetrics monitor = enhanceSourceMonitor(source); String name = monitor.getName(); // Only register once... if (!sourcesByName.containsKey(name)) { @@ -441,21 +441,21 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } } - private Object applyChannelInterceptor(Object bean, DirectChannelMonitor interceptor, ClassLoader beanClassLoader) { + 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, SimpleMessageHandlerMonitor interceptor, + 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, SimpleMessageSourceMonitor interceptor, + private Object applySourceInterceptor(Object bean, SimpleMessageSourceMetrics interceptor, ClassLoader beanClassLoader) { NameMatchMethodPointcutAdvisor sourceAdvice = new NameMatchMethodPointcutAdvisor(interceptor); sourceAdvice.addMethodName("receive"); @@ -501,13 +501,13 @@ 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(MessageSourceMonitor handler) { + 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()); @@ -524,9 +524,9 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP return builder.toString(); } - private MessageHandlerMonitor enhanceHandlerMonitor(SimpleMessageHandlerMonitor monitor) { + private MessageHandlerMetrics enhanceHandlerMonitor(SimpleMessageHandlerMetrics monitor) { - MessageHandlerMonitor result = monitor; + MessageHandlerMetrics result = monitor; if (monitor.getName() != null && monitor.getSource() != null) { return monitor; @@ -589,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) { @@ -604,9 +604,9 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP } - private MessageSourceMonitor enhanceSourceMonitor(SimpleMessageSourceMonitor monitor) { + private MessageSourceMetrics enhanceSourceMonitor(SimpleMessageSourceMetrics monitor) { - MessageSourceMonitor result = monitor; + MessageSourceMetrics result = monitor; if (monitor.getName() != null && monitor.getSource() != null) { return monitor; @@ -669,7 +669,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 LifecycleMessageSourceMonitor((Lifecycle) endpoint, monitor); + result = new LifecycleMessageSourceMetrics((Lifecycle) endpoint, monitor); } if (name == null) { 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/LifecycleMessageHandlerMonitor.java deleted file mode 100644 index cce1885892..0000000000 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMonitor.java +++ /dev/null @@ -1,99 +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.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 MessageHandlerMonitor} 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 LifecycleMessageHandlerMonitor implements MessageHandlerMonitor, Lifecycle { - - private final Lifecycle lifecycle; - - private final MessageHandlerMonitor delegate; - - public LifecycleMessageHandlerMonitor(Lifecycle lifecycle, MessageHandlerMonitor delegate) { - this.lifecycle = lifecycle; - this.delegate = delegate; - } - - @ManagedAttribute - public boolean isRunning() { - return lifecycle.isRunning(); - } - - @ManagedOperation - public void start() { - lifecycle.start(); - } - - @ManagedOperation - public void stop() { - lifecycle.stop(); - } - - public int getErrorCount() { - return delegate.getErrorCount(); - } - - public int getHandleCount() { - return delegate.getHandleCount(); - } - - public double getMaxDuration() { - return delegate.getMaxDuration(); - } - - public double getMeanDuration() { - return delegate.getMeanDuration(); - } - - public double getMinDuration() { - return delegate.getMinDuration(); - } - - public double getStandardDeviationDuration() { - return delegate.getStandardDeviationDuration(); - } - - public Statistics getDuration() { - return delegate.getDuration(); - } - - public String getName() { - return delegate.getName(); - } - - public String getSource() { - return delegate.getSource(); - } - - public int getActiveCount() { - return delegate.getActiveCount(); - } - -} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageSourceMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageSourceMonitor.java deleted file mode 100644 index fc69442bb1..0000000000 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageSourceMonitor.java +++ /dev/null @@ -1,75 +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.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 MessageSourceMonitor} 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 LifecycleMessageSourceMonitor implements MessageSourceMonitor, Lifecycle { - - private final Lifecycle lifecycle; - - private final MessageSourceMonitor delegate; - - public LifecycleMessageSourceMonitor(Lifecycle lifecycle, MessageSourceMonitor delegate) { - this.lifecycle = lifecycle; - this.delegate = delegate; - } - - @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.MessageSourceMonitor#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/MessageChannelMonitor.java deleted file mode 100644 index bf5ceec4f5..0000000000 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageChannelMonitor.java +++ /dev/null @@ -1,107 +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.monitor; - -import org.springframework.jmx.export.annotation.ManagedMetric; -import org.springframework.jmx.support.MetricType; - -/** - * Interface for all message channel monitors containing accessors for various useful metrics that are generic for all - * channel types. - * - * @author Dave Syer - * - * @since 2.0 - * - */ -public interface MessageChannelMonitor { - - /** - * @return the number of successful sends - */ - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Sends") - int getSendCount(); - - /** - * @return the number of failed sends (either throwing an exception or rejected by the channel) - */ - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Send Errors") - int getSendErrorCount(); - - /** - * @return the time in seconds since the last send - */ - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Time Since Last Send in Seconds") - double getTimeSinceLastSend(); - - /** - * @return the mean send rate (per second) - */ - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Rate per Second") - double getMeanSendRate(); - - /** - * @return the mean error rate (per second). Errors comprise all failed sends. - */ - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Error Rate per Second") - double getMeanErrorRate(); - - /** - * @return the mean ratio of failed to successful sends in approximately the last minute - */ - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Mean Channel Error Ratio per Minute") - double getMeanErrorRatio(); - - /** - * @return the mean send duration (milliseconds) - */ - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Mean Duration") - double getMeanSendDuration(); - - /** - * @return the minimum send duration (milliseconds) since startup - */ - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Min Duration") - double getMinSendDuration(); - - /** - * @return the maximum send duration (milliseconds) since startup - */ - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Max Duration") - double getMaxSendDuration(); - - /** - * @return the standard deviation send duration (milliseconds) - */ - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Standard Deviation Duration") - double getStandardDeviationSendDuration(); - - /** - * @return summary statistics about the send duration (milliseconds) - */ - Statistics getSendDuration(); - - /** - * @return summary statistics about the send rates (per second) - */ - Statistics getSendRate(); - - /** - * @return summary statistics about the error rates (per second) - */ - Statistics getErrorRate(); - -} \ No newline at end of file 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/MessageHandlerMonitor.java deleted file mode 100644 index 8bdfd013d9..0000000000 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMonitor.java +++ /dev/null @@ -1,73 +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.monitor; - -import org.springframework.jmx.export.annotation.ManagedMetric; -import org.springframework.jmx.support.MetricType; - -/** - * @author Dave Syer - * - * @since 2.0 - */ -public interface MessageHandlerMonitor { - - /** - * @return the number of successful handler calls - */ - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Handler Execution Count", description = "rate=1h") - int getHandleCount(); - - /** - * @return the number of failed handler calls - */ - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Handler Error Count", description = "rate=1h") - int getErrorCount(); - - /** - * @return the maximum handler duration (milliseconds) - */ - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Mean Duration") - double getMeanDuration(); - - /** - * @return the minimum handler duration (milliseconds) - */ - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Min Duration") - double getMinDuration(); - - /** - * @return the standard deviation handler duration (milliseconds) - */ - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Max Duration") - double getMaxDuration(); - - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Standard Deviation Duration") - double getStandardDeviationDuration(); - - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Active Status") - int getActiveCount(); - - /** - * @return summary statistics about the handler duration (milliseconds) - */ - Statistics getDuration(); - - String getName(); - - String getSource(); - -} \ No newline at end of file diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageSourceMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageSourceMonitor.java deleted file mode 100644 index 0b988a9968..0000000000 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageSourceMonitor.java +++ /dev/null @@ -1,36 +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.monitor; - -import org.springframework.jmx.export.annotation.ManagedMetric; -import org.springframework.jmx.support.MetricType; - -/** - * @author Dave Syer - * - * @since 2.0 - */ -public interface MessageSourceMonitor { - - /** - * @return the number of successful handler calls - */ - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Message Source Message Count", description = "rate=1h") - 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/PollableChannelMonitor.java deleted file mode 100644 index 9bcb444d4b..0000000000 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/PollableChannelMonitor.java +++ /dev/null @@ -1,86 +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.monitor; - -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.support.MetricType; - -/** - * @author Dave Syer - * - * @since 2.0 - * - */ -public class PollableChannelMonitor extends DirectChannelMonitor { - - private final AtomicInteger receiveCount = new AtomicInteger(); - - private final AtomicInteger receiveErrorCount = new AtomicInteger(); - - /** - * @param name - */ - public PollableChannelMonitor(String name) { - super(name); - } - - @Override - protected Object doInvoke(MethodInvocation invocation, String method, MessageChannel channel) throws Throwable { - if ("receive".equals(method)) { - return monitorReceive(invocation, channel); - } - return super.doInvoke(invocation, method, channel); - } - - private Object monitorReceive(MethodInvocation invocation, MessageChannel channel) throws Throwable { - if (logger.isTraceEnabled()) { - logger.trace("Recording receive on channel(" + channel + ") "); - } - try { - Object object = invocation.proceed(); - if (object!=null) { - receiveCount.incrementAndGet(); - } - return object; - - } - catch (Throwable e) { - receiveErrorCount.incrementAndGet(); - throw e; - } - } - - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Receives") - public int getReceiveCount() { - return receiveCount.get(); - } - - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Receive Errors") - public int getReceiveErrorCount() { - return receiveErrorCount.get(); - } - - @Override - public String toString() { - return String.format("MessageChannelMonitor: [name=%s, sends=%d, receives=%d]", getName(), getSendCount(), - 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/QueueChannelMonitor.java deleted file mode 100644 index edf528b12a..0000000000 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/QueueChannelMonitor.java +++ /dev/null @@ -1,50 +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.monitor; - -import org.springframework.integration.channel.QueueChannel; -import org.springframework.jmx.export.annotation.ManagedMetric; -import org.springframework.jmx.support.MetricType; - -/** - * @author Dave Syer - * - * @since 2.0 - * - */ -public class QueueChannelMonitor extends PollableChannelMonitor { - - private final QueueChannel channel; - - /** - * @param name - */ - public QueueChannelMonitor(QueueChannel channel, String name) { - super(name); - this.channel = channel; - } - - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "QueueChannel Queue Size") - public int getQueueSize() { - return channel.getQueueSize(); - } - - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "QueueChannel Remaining Capacity") - public int getRemainingCapacity() { - return channel.getRemainingCapacity(); - } - -} 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/SimpleMessageHandlerMonitor.java deleted file mode 100644 index 0247a47289..0000000000 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMonitor.java +++ /dev/null @@ -1,175 +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.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; -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; - -/** - * @author Dave Syer - * - * @since 2.0 - * - */ -@ManagedResource -public class SimpleMessageHandlerMonitor implements MethodInterceptor, MessageHandlerMonitor { - - private static final Log logger = LogFactory.getLog(SimpleMessageHandlerMonitor.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(); - - private final ExponentialMovingAverage duration = new ExponentialMovingAverage( - DEFAULT_MOVING_AVERAGE_WINDOW); - - private String name; - - private String source; - - public SimpleMessageHandlerMonitor(MessageHandler handler) { - this.handler = handler; - } - - 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 MessageHandler getMessageHandler() { - return handler; - } - - 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 + ") :"); - } - - String name = this.name; - if (name == null) { - name = handler.toString(); - } - StopWatch timer = new StopWatch(name + ".handle:execution"); - - try { - timer.start(); - handleCount.incrementAndGet(); - activeCount.incrementAndGet(); - - handler.handleMessage(message); - - timer.stop(); - duration.append(timer.getTotalTimeSeconds()); - } 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 int getHandleCount() { - if (logger.isTraceEnabled()) { - logger.trace("Getting Handle Count:" + this); - } - 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(); - } - - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Active Count") - public int getActiveCount() { - return activeCount.get(); - } - - public Statistics getDuration() { - return duration.getStatistics(); - } - - @Override - public String toString() { - return String.format("MessageHandlerMonitor: [name=%s, source=%s, duration=%s]", name, source, duration); - } - -} \ No newline at end of file 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/SimpleMessageProducingHandlerMonitor.java deleted file mode 100644 index baee4af7e2..0000000000 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageProducingHandlerMonitor.java +++ /dev/null @@ -1,38 +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.monitor; - -import org.springframework.integration.MessageChannel; -import org.springframework.integration.core.MessageHandler; -import org.springframework.integration.core.MessageProducer; -import org.springframework.jmx.export.annotation.ManagedResource; - -/** - * @author Oleg Zhurakousky - * @since 2.0 - * - */ -@ManagedResource -public class SimpleMessageProducingHandlerMonitor extends SimpleMessageHandlerMonitor implements MessageProducer { - - public SimpleMessageProducingHandlerMonitor(MessageHandler handler) { - super(handler); - } - - public void setOutputChannel(MessageChannel outputChannel) { - ((MessageProducer)this.getMessageHandler()).setOutputChannel(outputChannel); - } -} \ No newline at end of file diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMonitor.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMonitor.java deleted file mode 100644 index 863b00caba..0000000000 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMonitor.java +++ /dev/null @@ -1,80 +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.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 SimpleMessageSourceMonitor implements MethodInterceptor, MessageSourceMonitor { - - private final AtomicInteger messageCount = new AtomicInteger(); - - private final MessageSource messageSource; - - private String source; - - private String name; - - public SimpleMessageSourceMonitor(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 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/test/java/org/springframework/integration/control/ControlBusTests.java b/spring-integration-jmx/src/test/java/org/springframework/integration/control/ControlBusTests.java index 41ed89793c..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 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()); } } From 72c372ee43c0e5e3518506ed76d6e62ee26b7aa2 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Fri, 8 Oct 2010 10:31:17 -0400 Subject: [PATCH 16/58] INT-1499 DelayHandler now implements MessageProducer --- .../org/springframework/integration/handler/DelayHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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()); From f89862cf1d4b9d075181980bde70e65b5d913cc9 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Fri, 8 Oct 2010 10:45:32 -0400 Subject: [PATCH 17/58] INT-1491 added filter name to rejection Exception description --- .../org/springframework/integration/filter/MessageFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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..0e4ed71c07 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 @@ -107,7 +107,7 @@ public class MessageFilter extends AbstractReplyProducingMessageHandler { this.getMessagingTemplate().send(this.discardChannel, message); } if (this.throwExceptionOnRejection) { - throw new MessageRejectedException(message); + throw new MessageRejectedException(message, "MessageFilter '" + this.getComponentName() + "' rejected Message"); } return null; } From b55a54729e00287d16dff380525e44d0b78ff89e Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Fri, 8 Oct 2010 11:11:31 -0400 Subject: [PATCH 18/58] INT-1504 added support for 'error-handler' attribute on HTTP outbound adapters (for the ResponseErrorHandler on the underlying RestTemplate) --- .../HttpOutboundChannelAdapterParser.java | 1 + .../config/HttpOutboundGatewayParser.java | 1 + .../config/spring-integration-http-2.0.xsd | 30 +++++++++++++++++++ ...boundChannelAdapterParserTests-context.xml | 3 ++ ...HttpOutboundChannelAdapterParserTests.java | 16 ++++++++++ ...HttpOutboundGatewayParserTests-context.xml | 3 ++ .../HttpOutboundGatewayParserTests.java | 16 ++++++++++ 7 files changed, 70 insertions(+) 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 @@ + + + + + + + + + + @@ -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 { + } + } + } From 7cfd0f4a55436845aaf6eca4469f80afcc80bbc6 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 8 Oct 2010 08:15:55 -0700 Subject: [PATCH 19/58] INT-1503: *Monitor->*Metrics --- .../monitor/DirectChannelMetrics.java | 200 ++++++++++++++++++ .../LifecycleMessageHandlerMetrics.java | 99 +++++++++ .../LifecycleMessageSourceMetrics.java | 75 +++++++ .../monitor/MessageChannelMetrics.java | 107 ++++++++++ .../monitor/MessageHandlerMetrics.java | 73 +++++++ .../monitor/MessageSourceMetrics.java | 36 ++++ .../monitor/PollableChannelMetrics.java | 86 ++++++++ .../monitor/QueueChannelMetrics.java | 50 +++++ .../monitor/SimpleMessageHandlerMetrics.java | 175 +++++++++++++++ .../SimpleMessageProducingHandlerMetrics.java | 38 ++++ .../monitor/SimpleMessageSourceMetrics.java | 80 +++++++ 11 files changed, 1019 insertions(+) create mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMetrics.java create mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMetrics.java create mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageSourceMetrics.java create mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageChannelMetrics.java create mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMetrics.java create mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageSourceMetrics.java create mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/PollableChannelMetrics.java create mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/QueueChannelMetrics.java create mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMetrics.java create mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageProducingHandlerMetrics.java create mode 100644 spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMetrics.java diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMetrics.java new file mode 100644 index 0000000000..b614dfb0dd --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMetrics.java @@ -0,0 +1,200 @@ +/* + * Copyright 2009-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.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; + +/** + * Registers all message channels, and accumulates statistics about their performance. The statistics are then published + * locally for other components to consume and publish remotely. + * + * @author Dave Syer + * @author Helena Edelson + */ +@ManagedResource +public class DirectChannelMetrics implements MethodInterceptor, MessageChannelMetrics { + + protected final Log logger = LogFactory.getLog(getClass()); + + public static final long ONE_SECOND_SECONDS = 1; + + public static final long ONE_MINUTE_SECONDS = 60; + + public static final int DEFAULT_MOVING_AVERAGE_WINDOW = 10; + + private ExponentialMovingAverage sendDuration = new ExponentialMovingAverage( + DEFAULT_MOVING_AVERAGE_WINDOW); + + private final ExponentialMovingAverageRate sendErrorRate = new ExponentialMovingAverageRate( + ONE_SECOND_SECONDS, ONE_MINUTE_SECONDS, DEFAULT_MOVING_AVERAGE_WINDOW); + + private final ExponentialMovingAverageRatio sendSuccessRatio = new ExponentialMovingAverageRatio( + ONE_MINUTE_SECONDS, DEFAULT_MOVING_AVERAGE_WINDOW); + + private final ExponentialMovingAverageRate sendRate = new ExponentialMovingAverageRate( + ONE_SECOND_SECONDS, ONE_MINUTE_SECONDS, DEFAULT_MOVING_AVERAGE_WINDOW); + + private final AtomicInteger sendCount = new AtomicInteger(); + + private final AtomicInteger sendErrorCount = new AtomicInteger(); + + private final String name; + + public DirectChannelMetrics(String name) { + this.name = name; + } + + public void destroy() { + if (logger.isDebugEnabled()) { + logger.debug(sendDuration); + } + } + + public String getName() { + return name; + } + + public Object invoke(MethodInvocation invocation) throws Throwable { + String method = invocation.getMethod().getName(); + MessageChannel channel = (MessageChannel) invocation.getThis(); + return doInvoke(invocation, method, channel); + } + + protected Object doInvoke(MethodInvocation invocation, String method, MessageChannel channel) throws Throwable { + if ("send".equals(method)) { + Message message = (Message) invocation.getArguments()[0]; + return monitorSend(invocation, channel, message); + } + return invocation.proceed(); + } + + private Object monitorSend(MethodInvocation invocation, MessageChannel channel, Message message) + throws Throwable { + + if (logger.isTraceEnabled()) { + logger.trace("Recording send on channel(" + channel + ") : message(" + message + ")"); + } + + final StopWatch timer = new StopWatch(channel + ".send:execution"); + + try { + timer.start(); + + sendCount.incrementAndGet(); + sendRate.increment(); + + Object result = invocation.proceed(); + + timer.stop(); + if ((Boolean)result) { + sendSuccessRatio.success(); + sendDuration.append(timer.getTotalTimeSeconds()); + } else { + sendSuccessRatio.failure(); + sendErrorCount.incrementAndGet(); + sendErrorRate.increment(); + } + return result; + + } + catch (Throwable e) { + sendSuccessRatio.failure(); + sendErrorCount.incrementAndGet(); + sendErrorRate.increment(); + throw e; + } + finally { + if (logger.isTraceEnabled()) { + logger.trace(timer); + } + } + } + + @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(); + } + + public Statistics getSendDuration() { + return sendDuration.getStatistics(); + } + + public Statistics getSendRate() { + return sendRate.getStatistics(); + } + + public Statistics getErrorRate() { + return sendErrorRate.getStatistics(); + } + + @Override + public String toString() { + return String.format("MessageChannelMonitor: [name=%s, sends=%d]", name, sendCount.get()); + } +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMetrics.java new file mode 100644 index 0000000000..5ae504fb5b --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMetrics.java @@ -0,0 +1,99 @@ +/* + * 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 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 + * + * @since 2.0 + * + */ +@ManagedResource +public class LifecycleMessageHandlerMetrics implements MessageHandlerMetrics, Lifecycle { + + private final Lifecycle lifecycle; + + private final MessageHandlerMetrics delegate; + + public LifecycleMessageHandlerMetrics(Lifecycle lifecycle, MessageHandlerMetrics delegate) { + this.lifecycle = lifecycle; + this.delegate = delegate; + } + + @ManagedAttribute + public boolean isRunning() { + return lifecycle.isRunning(); + } + + @ManagedOperation + public void start() { + lifecycle.start(); + } + + @ManagedOperation + public void stop() { + lifecycle.stop(); + } + + public int getErrorCount() { + return delegate.getErrorCount(); + } + + public int getHandleCount() { + return delegate.getHandleCount(); + } + + public double getMaxDuration() { + return delegate.getMaxDuration(); + } + + public double getMeanDuration() { + return delegate.getMeanDuration(); + } + + public double getMinDuration() { + return delegate.getMinDuration(); + } + + public double getStandardDeviationDuration() { + return delegate.getStandardDeviationDuration(); + } + + public Statistics getDuration() { + return delegate.getDuration(); + } + + public String getName() { + return delegate.getName(); + } + + public String getSource() { + 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..0e7f2a967a --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageSourceMetrics.java @@ -0,0 +1,75 @@ +/* + * 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; + } + + @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/MessageChannelMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageChannelMetrics.java new file mode 100644 index 0000000000..5c294d58d2 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageChannelMetrics.java @@ -0,0 +1,107 @@ +/* + * 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.support.MetricType; + +/** + * Interface for all message channel monitors containing accessors for various useful metrics that are generic for all + * channel types. + * + * @author Dave Syer + * + * @since 2.0 + * + */ +public interface MessageChannelMetrics { + + /** + * @return the number of successful sends + */ + @ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Sends") + int getSendCount(); + + /** + * @return the number of failed sends (either throwing an exception or rejected by the channel) + */ + @ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Send Errors") + int getSendErrorCount(); + + /** + * @return the time in seconds since the last send + */ + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Time Since Last Send in Seconds") + double getTimeSinceLastSend(); + + /** + * @return the mean send rate (per second) + */ + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Rate per Second") + double getMeanSendRate(); + + /** + * @return the mean error rate (per second). Errors comprise all failed sends. + */ + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Error Rate per Second") + double getMeanErrorRate(); + + /** + * @return the mean ratio of failed to successful sends in approximately the last minute + */ + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Mean Channel Error Ratio per Minute") + double getMeanErrorRatio(); + + /** + * @return the mean send duration (milliseconds) + */ + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Mean Duration") + double getMeanSendDuration(); + + /** + * @return the minimum send duration (milliseconds) since startup + */ + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Min Duration") + double getMinSendDuration(); + + /** + * @return the maximum send duration (milliseconds) since startup + */ + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Max Duration") + double getMaxSendDuration(); + + /** + * @return the standard deviation send duration (milliseconds) + */ + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Channel Send Standard Deviation Duration") + double getStandardDeviationSendDuration(); + + /** + * @return summary statistics about the send duration (milliseconds) + */ + Statistics getSendDuration(); + + /** + * @return summary statistics about the send rates (per second) + */ + Statistics getSendRate(); + + /** + * @return summary statistics about the error rates (per second) + */ + Statistics getErrorRate(); + +} \ No newline at end of file diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMetrics.java new file mode 100644 index 0000000000..c9bb3f8845 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMetrics.java @@ -0,0 +1,73 @@ +/* + * 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.support.MetricType; + +/** + * @author Dave Syer + * + * @since 2.0 + */ +public interface MessageHandlerMetrics { + + /** + * @return the number of successful handler calls + */ + @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Handler Execution Count", description = "rate=1h") + int getHandleCount(); + + /** + * @return the number of failed handler calls + */ + @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Handler Error Count", description = "rate=1h") + int getErrorCount(); + + /** + * @return the maximum handler duration (milliseconds) + */ + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Mean Duration") + double getMeanDuration(); + + /** + * @return the minimum handler duration (milliseconds) + */ + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Min Duration") + double getMinDuration(); + + /** + * @return the standard deviation handler duration (milliseconds) + */ + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Max Duration") + double getMaxDuration(); + + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Standard Deviation Duration") + double getStandardDeviationDuration(); + + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Active Status") + int getActiveCount(); + + /** + * @return summary statistics about the handler duration (milliseconds) + */ + Statistics getDuration(); + + String getName(); + + String getSource(); + +} \ No newline at end of file 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..931c7c3909 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageSourceMetrics.java @@ -0,0 +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.monitor; + +import org.springframework.jmx.export.annotation.ManagedMetric; +import org.springframework.jmx.support.MetricType; + +/** + * @author Dave Syer + * + * @since 2.0 + */ +public interface MessageSourceMetrics { + + /** + * @return the number of successful handler calls + */ + @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Message Source Message Count", description = "rate=1h") + int getMessageCount(); + + String getName(); + + String getSource(); + +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/PollableChannelMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/PollableChannelMetrics.java new file mode 100644 index 0000000000..81e48abd63 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/PollableChannelMetrics.java @@ -0,0 +1,86 @@ +/* + * 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.MethodInvocation; +import org.springframework.integration.MessageChannel; +import org.springframework.jmx.export.annotation.ManagedMetric; +import org.springframework.jmx.support.MetricType; + +/** + * @author Dave Syer + * + * @since 2.0 + * + */ +public class PollableChannelMetrics extends DirectChannelMetrics { + + private final AtomicInteger receiveCount = new AtomicInteger(); + + private final AtomicInteger receiveErrorCount = new AtomicInteger(); + + /** + * @param name + */ + public PollableChannelMetrics(String name) { + super(name); + } + + @Override + protected Object doInvoke(MethodInvocation invocation, String method, MessageChannel channel) throws Throwable { + if ("receive".equals(method)) { + return monitorReceive(invocation, channel); + } + return super.doInvoke(invocation, method, channel); + } + + private Object monitorReceive(MethodInvocation invocation, MessageChannel channel) throws Throwable { + if (logger.isTraceEnabled()) { + logger.trace("Recording receive on channel(" + channel + ") "); + } + try { + Object object = invocation.proceed(); + if (object!=null) { + receiveCount.incrementAndGet(); + } + return object; + + } + catch (Throwable e) { + receiveErrorCount.incrementAndGet(); + throw e; + } + } + + @ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Receives") + public int getReceiveCount() { + return receiveCount.get(); + } + + @ManagedMetric(metricType = MetricType.COUNTER, displayName = "MessageChannel Receive Errors") + public int getReceiveErrorCount() { + return receiveErrorCount.get(); + } + + @Override + public String toString() { + return String.format("MessageChannelMonitor: [name=%s, sends=%d, receives=%d]", getName(), getSendCount(), + receiveCount.get()); + } + +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/QueueChannelMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/QueueChannelMetrics.java new file mode 100644 index 0000000000..d3764b7f9d --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/QueueChannelMetrics.java @@ -0,0 +1,50 @@ +/* + * 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.integration.channel.QueueChannel; +import org.springframework.jmx.export.annotation.ManagedMetric; +import org.springframework.jmx.support.MetricType; + +/** + * @author Dave Syer + * + * @since 2.0 + * + */ +public class QueueChannelMetrics extends PollableChannelMetrics { + + private final QueueChannel channel; + + /** + * @param name + */ + public QueueChannelMetrics(QueueChannel channel, String name) { + super(name); + this.channel = channel; + } + + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "QueueChannel Queue Size") + public int getQueueSize() { + return channel.getQueueSize(); + } + + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "QueueChannel Remaining Capacity") + public int getRemainingCapacity() { + return channel.getRemainingCapacity(); + } + +} diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMetrics.java new file mode 100644 index 0000000000..2971034754 --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMetrics.java @@ -0,0 +1,175 @@ +/* + * 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.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +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.jmx.export.annotation.ManagedMetric; +import org.springframework.jmx.export.annotation.ManagedResource; +import org.springframework.jmx.support.MetricType; +import org.springframework.util.StopWatch; + +/** + * @author Dave Syer + * + * @since 2.0 + * + */ +@ManagedResource +public class SimpleMessageHandlerMetrics implements MethodInterceptor, MessageHandlerMetrics { + + 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(); + + private final ExponentialMovingAverage duration = new ExponentialMovingAverage( + DEFAULT_MOVING_AVERAGE_WINDOW); + + private String name; + + private String source; + + public SimpleMessageHandlerMetrics(MessageHandler handler) { + this.handler = handler; + } + + 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 MessageHandler getMessageHandler() { + return handler; + } + + 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 + ") :"); + } + + String name = this.name; + if (name == null) { + name = handler.toString(); + } + StopWatch timer = new StopWatch(name + ".handle:execution"); + + try { + timer.start(); + handleCount.incrementAndGet(); + activeCount.incrementAndGet(); + + handler.handleMessage(message); + + timer.stop(); + duration.append(timer.getTotalTimeSeconds()); + } 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 int getHandleCount() { + if (logger.isTraceEnabled()) { + logger.trace("Getting Handle Count:" + this); + } + 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(); + } + + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Active Count") + public int getActiveCount() { + return activeCount.get(); + } + + public Statistics getDuration() { + return duration.getStatistics(); + } + + @Override + public String toString() { + return String.format("MessageHandlerMonitor: [name=%s, source=%s, duration=%s]", name, source, duration); + } + +} \ No newline at end of file diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageProducingHandlerMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageProducingHandlerMetrics.java new file mode 100644 index 0000000000..aaa0bd632e --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageProducingHandlerMetrics.java @@ -0,0 +1,38 @@ +/* + * 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.integration.MessageChannel; +import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.core.MessageProducer; +import org.springframework.jmx.export.annotation.ManagedResource; + +/** + * @author Oleg Zhurakousky + * @since 2.0 + * + */ +@ManagedResource +public class SimpleMessageProducingHandlerMetrics extends SimpleMessageHandlerMetrics implements MessageProducer { + + public SimpleMessageProducingHandlerMetrics(MessageHandler handler) { + super(handler); + } + + public void setOutputChannel(MessageChannel outputChannel) { + ((MessageProducer)this.getMessageHandler()).setOutputChannel(outputChannel); + } +} \ No newline at end of file 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..312fbb3c8f --- /dev/null +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageSourceMetrics.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 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 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()); + } + +} From b3c79358e150a6dab59e4700417e525293d07d0b Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Fri, 8 Oct 2010 14:42:11 -0400 Subject: [PATCH 20/58] INT-1505 added support for a 'payload-expression' attribute on the inbound ApplicationEvent Channel Adapter --- .../com.springsource.sts.config.flow.prefs | 4 +-- ...ApplicationEventInboundChannelAdapter.java | 36 +++++++++++++++---- .../EventInboundChannelAdapterParser.java | 5 +-- .../config/spring-integration-event-2.0.xsd | 10 +++++- ...cationEventInboundChannelAdapterTests.java | 24 +++++++++++-- ...boundChannelAdapterParserTests-context.xml | 8 ++++- ...EventInboundChannelAdapterParserTests.java | 19 +++++++--- 7 files changed, 87 insertions(+), 19 deletions(-) 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 { From 4c53843dbe8f1af3be71519e03160fbcf0cd3bf1 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Fri, 8 Oct 2010 14:51:34 -0400 Subject: [PATCH 21/58] INT-1505 updated template.mf, added expression package --- spring-integration-event/template.mf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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" From 986d1ad1b2f1c84f0042f9c2bbb1ef3a0d9f7c0f Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Mon, 11 Oct 2010 21:30:22 -0400 Subject: [PATCH 22/58] INT-1382 added DynamicExpression, ExpressionSource strategy, and ResourceBundle-based implementation --- .../expression/DynamicExpression.java | 147 +++++ .../expression/ExpressionSource.java | 33 + ...oadableResourceBundleExpressionSource.java | 572 ++++++++++++++++++ .../expression/DynamicExpressionTests.java | 70 +++ .../expression/expressions.properties | 1 + 5 files changed, 823 insertions(+) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/expression/DynamicExpression.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionSource.java create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/expression/ReloadableResourceBundleExpressionSource.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/expression/DynamicExpressionTests.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/expression/expressions.properties 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..2857c6fcc8 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/DynamicExpression.java @@ -0,0 +1,147 @@ +/* + * 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.getValue(context, rootObject); + } + + public T getValue(EvaluationContext context, Class desiredResultType) throws EvaluationException { + return this.getValue(context, desiredResultType); + } + + public T getValue(EvaluationContext context, Object rootObject, Class desiredResultType) throws EvaluationException { + return this.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.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(); + return this.expressionSource.getExpression(this.key, locale); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionSource.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionSource.java new file mode 100644 index 0000000000..fca593da59 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/expression/ExpressionSource.java @@ -0,0 +1,33 @@ +/* + * 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.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/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 From 3f4ce448a056d1479290db035b610d2e22c641f2 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Mon, 11 Oct 2010 21:47:10 -0400 Subject: [PATCH 23/58] INT-1482 added default charset value for FtpSendingMessageHandler --- .../ftp/FtpSendingMessageHandler.java | 137 ++++++++---------- 1 file changed, 64 insertions(+), 73 deletions(-) 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..e9131a966f 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,19 @@ 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 afterPropertiesSet() throws Exception { Assert.notNull(ftpClientPool, "'ftpClientPool' must not be null"); Assert.notNull(temporaryBufferFolder, @@ -82,83 +102,56 @@ public class FtpSendingMessageHandler implements MessageHandler, 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 +160,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 +178,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 +189,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; } + } From f3be8bebdef63726269e9b915ddb88f19c676f99 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Mon, 11 Oct 2010 21:56:14 -0400 Subject: [PATCH 24/58] INT-1482 added default charset value for FtpSendingMessageHandler and SftpSendingMessageHandler --- .../ftp/FtpSendingMessageHandler.java | 9 +- .../sftp/SftpSendingMessageHandler.java | 198 +++++++++--------- 2 files changed, 105 insertions(+), 102 deletions(-) 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 e9131a966f..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 @@ -81,6 +81,10 @@ public class FtpSendingMessageHandler implements MessageHandler, InitializingBea 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, @@ -90,15 +94,12 @@ public class FtpSendingMessageHandler implements MessageHandler, InitializingBea /* 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; } 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); } } } + } From a4f05cef0e5b632d03684fb2a6b6e7394b22ed88 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Mon, 11 Oct 2010 22:05:54 -0400 Subject: [PATCH 25/58] INT-1482 charset value is now being set for bothFtpSendingMessageHandler and SftpSendingMessageHandler --- .../ftp/FtpSendingMessageHandlerFactoryBean.java | 5 +++-- .../sftp/config/SftpMessageSendingConsumerFactoryBean.java | 6 ++++++ .../integration/sftp/config/SftpNamespaceHandler.java | 2 +- .../integration/sftp/config/spring-integration-sftp-2.0.xsd | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) 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 AbstractFactoryBean - @@ -60,6 +59,7 @@ + From 20eb2922ee66393aae0818bf1896692ff22772cc Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 12 Oct 2010 05:42:10 -0400 Subject: [PATCH 26/58] INT-1493, ensured that default 'maxMessagesPerPoll' for SPCA is 1 and -1 for PC, added tests validating that both SPCA and PC stops --- ...ourcePollingChannelAdapterFactoryBean.java | 3 + .../endpoint/AbstractPollingEndpoint.java | 2 +- .../endpoint/PollingLifecycleTests.java | 116 ++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/endpoint/PollingLifecycleTests.java 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..74b34789d6 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,9 @@ public class SourcePollingChannelAdapterFactoryBean implements FactoryBean("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)); + consumer.stop(); + for (int i = 0; i < 10; i++) { + channel.send(new GenericMessage("foo")); + } + Mockito.verify(handler, times(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.setMaxMessagesPerPoll(-1); // should be overriden in FB + 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(); + } +} From 4263202e717e42b1d69ef5d4778b68c83d251ad7 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 12 Oct 2010 06:54:21 -0400 Subject: [PATCH 27/58] INT-1441 added null check for PropertyEditor and replaced canConvert with try/catch convert catching ConversionFailedException as per SPR-7548, no additional tests were added as this bug woudl affect the tests we already have --- .../util/BeanFactoryTypeConverter.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) 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..d7820b9bdf 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. @@ -22,11 +22,17 @@ 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; - +/** + * + * @author Dave Syer + * @author Oleg Zhurakousky + * + */ public class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware { private SimpleTypeConverter delegate = new SimpleTypeConverter(); @@ -89,13 +95,19 @@ public class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware if (targetType.getType() == Void.class || targetType.getType() == Void.TYPE) { return null; } - if (conversionService.canConvert(sourceType, targetType)) { + try { return conversionService.convert(value, sourceType, targetType); + } catch (ConversionFailedException e) { + throw e; + } catch (Exception ex){ + // ignore because we have a fallback strategy, see SPR-7548 for more details } 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()); } From 46e5ef3e1410668c9cd95b4bbade2955d2f67295 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 12 Oct 2010 07:57:48 -0400 Subject: [PATCH 28/58] INT-1506 changed ID from xsd:string to xsd:ID and made it optional in XML module --- .../xml/config/spring-integration-xml-2.0.xsd | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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..85290c7a1b 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 @@ -353,7 +353,7 @@ - + @@ -419,7 +419,7 @@ - + @@ -451,7 +451,7 @@ - + @@ -505,7 +505,7 @@ - + @@ -542,7 +542,7 @@ - + From 5f27825974ac9dbb6216a7d37543c11d6dc0edeb Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 12 Oct 2010 08:19:14 -0400 Subject: [PATCH 29/58] INT-1493 poslishing, added comment to SPCAFB about why 1 should be a default maxMessagesPerPoll, change assertion for BF in APE only when it is absolutely required --- .../config/SourcePollingChannelAdapterFactoryBean.java | 2 ++ .../integration/endpoint/AbstractPollingEndpoint.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) 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 74b34789d6..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 @@ -126,6 +126,8 @@ public class SourcePollingChannelAdapterFactoryBean implements FactoryBean Date: Tue, 12 Oct 2010 08:57:39 -0400 Subject: [PATCH 30/58] INT-1493, polished assertion to make sure that it asserts on atMost 1 invocation of the poller since there is still a natural race condition between stop() and poller loop --- .../integration/endpoint/PollingLifecycleTests.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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 index dd9c07252a..e070cc0e8a 100644 --- 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 @@ -19,11 +19,13 @@ 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; @@ -77,11 +79,16 @@ public class PollingLifecycleTests { 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")); } - Mockito.verify(handler, times(1)).handleMessage(Mockito.any(Message.class)); + 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 @@ -91,7 +98,6 @@ public class PollingLifecycleTests { SourcePollingChannelAdapterFactoryBean adapterFactory = new SourcePollingChannelAdapterFactoryBean(); PollerMetadata pollerMetadata = new PollerMetadata(); - pollerMetadata.setMaxMessagesPerPoll(-1); // should be overriden in FB pollerMetadata.setTrigger(new PeriodicTrigger(2000)); adapterFactory.setPollerMetadata(pollerMetadata); MessageSource source = spy(new MessageSource() { From fee0aac0ed8089c858631024584843caf22356e9 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 12 Oct 2010 17:55:48 -0400 Subject: [PATCH 31/58] INT-1509, removed depndency on TaskExecutor in favor of Executor --- .../integration/endpoint/AbstractPollingEndpoint.java | 6 +++--- .../integration/scheduling/PollerMetadata.java | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) 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 a330c4b56d..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,7 +43,7 @@ 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; @@ -84,7 +84,7 @@ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implement return; } Assert.notNull(this.pollerMetadata.getTrigger(), "Trigger is required"); - TaskExecutor providedExecutor = this.pollerMetadata.getTaskExecutor(); + Executor providedExecutor = this.pollerMetadata.getTaskExecutor(); if (providedExecutor != null) { this.taskExecutor = providedExecutor; } 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; } } From b4d9366dfb998fea01897d80a21d205bc0e1eb1f Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 12 Oct 2010 15:18:02 -0700 Subject: [PATCH 32/58] INT-1508: add reset() --- .../monitor/DirectChannelMetrics.java | 11 +++++++++ .../monitor/ExponentialMovingAverage.java | 23 +++++++++++++------ .../monitor/ExponentialMovingAverageRate.java | 19 +++++++++++---- .../ExponentialMovingAverageRatio.java | 13 ++++++++--- .../LifecycleMessageHandlerMetrics.java | 5 ++++ .../LifecycleMessageSourceMetrics.java | 5 ++++ .../monitor/MessageChannelMetrics.java | 4 ++++ .../monitor/MessageHandlerMetrics.java | 4 ++++ .../monitor/MessageSourceMetrics.java | 6 ++++- .../monitor/PollableChannelMetrics.java | 8 +++++++ .../monitor/SimpleMessageHandlerMetrics.java | 8 +++++++ .../monitor/SimpleMessageSourceMetrics.java | 9 ++++++++ .../ExponentialMovingAverageRateTests.java | 14 +++++++++++ .../ExponentialMovingAverageRatioTests.java | 12 ++++++++++ .../ExponentialMovingAverageTests.java | 12 ++++++++++ 15 files changed, 137 insertions(+), 16 deletions(-) diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMetrics.java index b614dfb0dd..17b0a9a773 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMetrics.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMetrics.java @@ -21,6 +21,7 @@ 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.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.jmx.support.MetricType; import org.springframework.util.StopWatch; @@ -130,6 +131,16 @@ public class DirectChannelMetrics implements MethodInterceptor, MessageChannelMe } } } + + @ManagedOperation + 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() { 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 6240b6cb96..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); diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMetrics.java index 5ae504fb5b..2364e75b3a 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMetrics.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMetrics.java @@ -56,6 +56,11 @@ public class LifecycleMessageHandlerMetrics implements MessageHandlerMetrics, Li lifecycle.stop(); } + @ManagedOperation + public void reset() { + delegate.reset(); + } + public int getErrorCount() { return delegate.getErrorCount(); } 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 index 0e7f2a967a..5fcb8cefbf 100644 --- 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 @@ -41,6 +41,11 @@ public class LifecycleMessageSourceMetrics implements MessageSourceMetrics, Life this.delegate = delegate; } + @ManagedOperation + public void reset() { + delegate.reset(); + } + @ManagedAttribute public boolean isRunning() { return lifecycle.isRunning(); diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageChannelMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageChannelMetrics.java index 5c294d58d2..51c04335d6 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageChannelMetrics.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; /** @@ -29,6 +30,9 @@ import org.springframework.jmx.support.MetricType; */ 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/MessageHandlerMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMetrics.java index c9bb3f8845..141147c23a 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMetrics.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; /** @@ -25,6 +26,9 @@ import org.springframework.jmx.support.MetricType; */ public interface MessageHandlerMetrics { + @ManagedOperation + void reset(); + /** * @return the number of successful handler calls */ 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 index 931c7c3909..b1984b37e4 100644 --- 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 @@ -14,6 +14,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,10 +24,13 @@ import org.springframework.jmx.support.MetricType; */ public interface MessageSourceMetrics { + @ManagedOperation + void reset(); + /** * @return the number of successful handler calls */ - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Message Source Message Count", description = "rate=1h") + @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Message Source Message Count") int getMessageCount(); String getName(); diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/PollableChannelMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/PollableChannelMetrics.java index 81e48abd63..d6731dbef8 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/PollableChannelMetrics.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; /** @@ -67,6 +68,13 @@ public class PollableChannelMetrics extends DirectChannelMetrics { } } + @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/SimpleMessageHandlerMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMetrics.java index 2971034754..1c3e3f34d7 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMetrics.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMetrics.java @@ -27,6 +27,7 @@ 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.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.jmx.support.MetricType; import org.springframework.util.StopWatch; @@ -125,6 +126,13 @@ public class SimpleMessageHandlerMetrics implements MethodInterceptor, MessageHa } } + @ManagedOperation + public synchronized void reset() { + duration.reset(); + errorCount.set(0); + handleCount.set(0); + } + @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Handler Execution Count", description = "rate=1h") public int getHandleCount() { if (logger.isTraceEnabled()) { 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 index 312fbb3c8f..edcd1a84ad 100644 --- 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 @@ -18,6 +18,9 @@ import java.util.concurrent.atomic.AtomicInteger; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.integration.core.MessageSource; +import org.springframework.jmx.export.annotation.ManagedMetric; +import org.springframework.jmx.export.annotation.ManagedOperation; +import org.springframework.jmx.support.MetricType; /** * @author Dave Syer @@ -59,6 +62,12 @@ public class SimpleMessageSourceMetrics implements MethodInterceptor, MessageSou return messageSource; } + @ManagedOperation + public void reset() { + messageCount.set(0); + } + + @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Message Source Message Count") public int getMessageCount() { return messageCount.get(); } 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 8a701a3d10..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 @@ -13,6 +13,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.Ignore; @@ -74,4 +75,17 @@ public class ExponentialMovingAverageRateTests { 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 09feb9eb72..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; @@ -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()); + } + } From 8737d46c6e8c737b7a76c787809b37c9c43a41ae Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Tue, 12 Oct 2010 19:11:52 -0400 Subject: [PATCH 33/58] INT-1382 added support for 'expression' sub-elements in the core schema --- ...pressionEvaluatingCorrelationStrategy.java | 18 +- ...essionEvaluatingMessageGroupProcessor.java | 28 +- .../AbstractMessageHandlerFactoryBean.java | 17 +- .../integration/config/FilterFactoryBean.java | 3 +- .../integration/config/RouterFactoryBean.java | 3 +- .../config/ServiceActivatorFactoryBean.java | 3 +- .../config/SplitterFactoryBean.java | 21 +- .../config/TransformerFactoryBean.java | 3 +- ...tractDelegatingConsumerEndpointParser.java | 35 +- .../expression/DynamicExpression.java | 12 +- .../filter/ExpressionEvaluatingSelector.java | 13 +- .../ExpressionEvaluatingMessageProcessor.java | 19 +- .../router/ExpressionEvaluatingRouter.java | 3 +- .../ExpressionEvaluatingSplitter.java | 3 +- .../ExpressionEvaluatingTransformer.java | 3 +- .../transformer/HeaderEnricher.java | 13 +- .../config/xml/spring-integration-2.0.xsd | 29 +- ...ionEvaluatingCorrelationStrategyTests.java | 27 +- ...pressionFilterIntegrationTests-context.xml | 26 ++ ...namicExpressionFilterIntegrationTests.java | 63 ++++ .../integration/filter/expressions.properties | 1 + ...essionEvaluatingMessageProcessorTests.java | 44 ++- .../http/DefaultInboundRequestMapper.java | 355 ------------------ .../DefaultInboundRequestMapperTests.java | 127 ------- 24 files changed, 317 insertions(+), 552 deletions(-) create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/filter/DynamicExpressionFilterIntegrationTests-context.xml create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/filter/DynamicExpressionFilterIntegrationTests.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/filter/expressions.properties delete mode 100644 spring-integration-http/src/main/java/org/springframework/integration/http/DefaultInboundRequestMapper.java delete mode 100644 spring-integration-http/src/test/java/org/springframework/integration/http/DefaultInboundRequestMapperTests.java 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/config/AbstractMessageHandlerFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMessageHandlerFactoryBean.java index beeabf137c..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 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/SplitterFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/SplitterFactoryBean.java index dbae530ef5..31dc216dcb 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/SplitterFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/SplitterFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 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,6 +16,7 @@ package org.springframework.integration.config; +import org.springframework.expression.Expression; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.splitter.AbstractMessageSplitter; import org.springframework.integration.splitter.DefaultMessageSplitter; @@ -31,12 +32,22 @@ import org.springframework.util.StringUtils; public class SplitterFactoryBean extends AbstractMessageHandlerFactoryBean { private volatile Long sendTimeout; + private volatile boolean requiresReply; + public void setSendTimeout(Long sendTimeout) { this.sendTimeout = sendTimeout; } + public boolean isRequiresReply() { + return requiresReply; + } + + public void setRequiresReply(boolean requiresReply) { + this.requiresReply = requiresReply; + } + @Override MessageHandler createMethodInvokingHandler(Object targetObject, String targetMethodName) { AbstractMessageSplitter splitter = null; @@ -52,7 +63,7 @@ public class SplitterFactoryBean extends AbstractMessageHandlerFactoryBean { } @Override - MessageHandler createExpressionEvaluatingHandler(String expression) { + MessageHandler createExpressionEvaluatingHandler(Expression expression) { return this.configureSplitter(new ExpressionEvaluatingSplitter(expression)); } @@ -68,11 +79,5 @@ public class SplitterFactoryBean extends AbstractMessageHandlerFactoryBean { splitter.setRequiresReply(requiresReply); return splitter; } - public boolean isRequiresReply() { - return requiresReply; - } - public void setRequiresReply(boolean requiresReply) { - this.requiresReply = requiresReply; - } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/TransformerFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/TransformerFactoryBean.java index 02545292be..aa782aef31 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/TransformerFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/TransformerFactoryBean.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.transformer.ExpressionEvaluatingTransformer; import org.springframework.integration.transformer.MessageTransformingHandler; @@ -54,7 +55,7 @@ public class TransformerFactoryBean extends AbstractMessageHandlerFactoryBean { } @Override - MessageHandler createExpressionEvaluatingHandler(String expression) { + MessageHandler createExpressionEvaluatingHandler(Expression expression) { Transformer transformer = new ExpressionEvaluatingTransformer(expression); return this.createHandler(transformer); } 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/expression/DynamicExpression.java b/spring-integration-core/src/main/java/org/springframework/integration/expression/DynamicExpression.java index 2857c6fcc8..2ad9983bcb 100644 --- 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 @@ -68,15 +68,15 @@ public class DynamicExpression implements Expression { } public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException { - return this.getValue(context, rootObject); + return this.resolveExpression().getValue(context, rootObject); } public T getValue(EvaluationContext context, Class desiredResultType) throws EvaluationException { - return this.getValue(context, desiredResultType); + return this.resolveExpression().getValue(context, desiredResultType); } public T getValue(EvaluationContext context, Object rootObject, Class desiredResultType) throws EvaluationException { - return this.getValue(context, rootObject, desiredResultType); + return this.resolveExpression().getValue(context, rootObject, desiredResultType); } public Class getValueType() throws EvaluationException { @@ -120,7 +120,7 @@ public class DynamicExpression implements Expression { } public boolean isWritable(Object rootObject) throws EvaluationException { - return this.isWritable(rootObject); + return this.resolveExpression().isWritable(rootObject); } public void setValue(EvaluationContext context, Object value) throws EvaluationException { @@ -141,7 +141,9 @@ public class DynamicExpression implements Expression { private Expression resolveExpression() { Locale locale = LocaleContextHolder.getLocale(); - return this.expressionSource.getExpression(this.key, locale); + 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-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/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/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/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..39d65cdae5 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,17 @@ 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 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/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..abad1e9ca6 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 @@ -2357,7 +2357,12 @@ Name of the header whose value to use. - + + + + + + @@ -2369,6 +2374,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/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-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/test/java/org/springframework/integration/http/DefaultInboundRequestMapperTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/DefaultInboundRequestMapperTests.java deleted file mode 100644 index a6019f11c9..0000000000 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/DefaultInboundRequestMapperTests.java +++ /dev/null @@ -1,127 +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.http; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.util.HashMap; -import java.util.Map; - -import org.junit.Test; -import org.springframework.integration.Message; -import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.util.FileCopyUtils; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.multipart.MultipartHttpServletRequest; -import org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest; - -/** - * @author Iwein Fuld - * @author Mark Fisher - */ -@SuppressWarnings("unchecked") -public class DefaultInboundRequestMapperTests { - - private static final String SIMPLE_STRING = "just ascii"; - - private static final String COMPLEX_STRING = "A\u00ea\u00f1\u00fcC"; - - private DefaultInboundRequestMapper mapper = new DefaultInboundRequestMapper(); - - @Test - public void simpleUtf8TextMapping() 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); - 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)); - } - -} From dbf76ee73ad2b73cb40520fd6d2255382f58fdb7 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 13 Oct 2010 07:08:07 -0400 Subject: [PATCH 34/58] INT-1377 - first round of Router hierarchy refactoring to make them more dynamic, moved every router under ACNRMR (later ACNRMR will be merged with AMR), removed type preloading in PTR parser, fixed tests, removed MapBasedChannelResolver and related tests. fixed test for other components that were dependent on MapBasedChannelResolver --- .../channel/MapBasedChannelResolver.java | 64 -------- .../integration/config/RouterFactoryBean.java | 12 ++ ...tractChannelNameResolvingRouterParser.java | 26 ++- .../config/xml/DefaultRouterParser.java | 14 +- .../config/xml/PayloadTypeRouterParser.java | 37 +---- .../xml/PublishingInterceptorParser.java | 3 +- ...ractChannelNameResolvingMessageRouter.java | 48 ++++-- .../ErrorMessageExceptionTypeRouter.java | 33 ++-- .../integration/router/PayloadTypeRouter.java | 55 ++++--- .../MessagePublishingInterceptorTests.java | 12 +- ...ublishingInterceptorUsageTests-context.xml | 14 +- .../channel/MapBasedChannelResolverTests.java | 73 --------- ...uterFactoryBeanDelegationTests-context.xml | 4 +- .../core/MessagingTemplateTests.java | 20 ++- .../ErrorMessageExceptionTypeRouterTests.java | 80 ++++++---- .../router/HeaderValueRouterTests.java | 11 +- .../router/MultiChannelRouterTests.java | 4 + .../router/PayloadTypeRouterTests.java | 151 +++++++++++++----- .../integration/router/RouterTests.java | 5 + .../config/PayloadTypeRouterParserTests.java | 9 -- 20 files changed, 336 insertions(+), 339 deletions(-) delete mode 100644 spring-integration-core/src/main/java/org/springframework/integration/channel/MapBasedChannelResolver.java delete mode 100644 spring-integration-core/src/test/java/org/springframework/integration/channel/MapBasedChannelResolverTests.java 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/RouterFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java index 789cd57294..e2f138a984 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java @@ -13,6 +13,8 @@ package org.springframework.integration.config; +import java.util.Map; + import org.springframework.aop.TargetSource; import org.springframework.aop.framework.Advised; import org.springframework.expression.Expression; @@ -31,10 +33,13 @@ import org.springframework.util.StringUtils; * * @author Mark Fisher * @author Jonas Partner + * @author Oleg Zhurakousky */ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean { private volatile ChannelResolver channelResolver; + + private volatile Map channelIdentifierMap; private volatile MessageChannel defaultOutputChannel; @@ -75,6 +80,10 @@ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean { public void setIgnoreSendFailures(Boolean ignoreSendFailures) { this.ignoreSendFailures = ignoreSendFailures; } + + public void setChannelIdentifierMap(Map channelIdentifierMap) { + this.channelIdentifierMap = channelIdentifierMap; + } @Override MessageHandler createMethodInvokingHandler(Object targetObject, String targetMethodName) { @@ -138,6 +147,9 @@ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean { if (this.channelResolver != null && router instanceof AbstractChannelNameResolvingMessageRouter) { ((AbstractChannelNameResolvingMessageRouter) router).setChannelResolver(this.channelResolver); } + if (this.channelIdentifierMap != null && router instanceof AbstractChannelNameResolvingMessageRouter) { + ((AbstractChannelNameResolvingMessageRouter) router).setChannelIdentifierMap(this.channelIdentifierMap); + } if (this.defaultOutputChannel != null) { router.setDefaultOutputChannel(this.defaultOutputChannel); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java index d16b34df42..52554afc14 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java @@ -18,19 +18,19 @@ package org.springframework.integration.config.xml; import java.util.List; -import org.w3c.dom.Element; - +import org.springframework.beans.factory.BeanCreationException; 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.xml.DomUtils; +import org.w3c.dom.Element; /** * Base parser for routers that create instances that are subclasses of AbstractChannelNameResolvingMessageRouter. * * @author Mark Fisher + * @author Oleg Zhurakousky */ public abstract class AbstractChannelNameResolvingRouterParser extends AbstractRouterParser { @@ -42,13 +42,23 @@ public abstract class AbstractChannelNameResolvingRouterParser extends AbstractR List childElements = DomUtils.getChildElementsByTagName(element, "mapping"); if (childElements != null && childElements.size() > 0) { 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 childElement : childElements) { - channelMap.put(childElement.getAttribute("value"), - new RuntimeBeanReference(childElement.getAttribute("channel"))); + String beanClassName = beanDefinition.getBeanClassName(); + String key = null; + if (beanClassName.endsWith("PayloadTypeRouter")){ + key = childElement.getAttribute("type"); + } + else if (beanClassName.endsWith("HeaderValueRouter")){ + key = childElement.getAttribute("value"); + } + else { + throw new BeanCreationException("Building '" + beanClassName + "' is not supported by this parser"); + } + channelMap.put(key, childElement.getAttribute("channel")); } - channelResolverBuilder.addPropertyValue("channelMap", channelMap); + beanDefinition.getPropertyValues().add("channelIdentifierMap", channelMap); beanDefinition.getPropertyValues().add("channelResolver", channelResolverBuilder.getBeanDefinition()); } } 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/PayloadTypeRouterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PayloadTypeRouterParser.java index 5a87301842..b948e11557 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,19 +16,10 @@ package org.springframework.integration.config.xml; -import java.util.List; - -import org.w3c.dom.Element; - -import org.springframework.beans.BeanMetadataElement; 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 +28,13 @@ import org.springframework.util.xml.DomUtils; * @author Mark Fisher * @since 1.0.3 */ -public class PayloadTypeRouterParser extends AbstractRouterParser { - +public class PayloadTypeRouterParser extends AbstractChannelNameResolvingRouterParser { + @Override - protected BeanDefinition parseRouter(Element element, ParserContext parserContext) { - BeanDefinitionBuilder payloadTypeRouterBuilder = BeanDefinitionBuilder.genericBeanDefinition( + protected BeanDefinition doParseRouter(Element element, + ParserContext parserContext) { + BeanDefinitionBuilder headerValueRouterBuilder = 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(); + return headerValueRouterBuilder.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/router/AbstractChannelNameResolvingMessageRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractChannelNameResolvingMessageRouter.java index 93ccd27120..e6e116529d 100644 --- 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 @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Map; import org.springframework.beans.factory.BeanFactory; import org.springframework.core.convert.ConversionService; @@ -39,6 +40,7 @@ import org.springframework.util.StringUtils; * * @author Mark Fisher * @author Jonas Partner + * @author Oleg Zhurakousky */ public abstract class AbstractChannelNameResolvingMessageRouter extends AbstractMessageRouter { @@ -49,6 +51,8 @@ public abstract class AbstractChannelNameResolvingMessageRouter extends Abstract private volatile ChannelResolver channelResolver; private volatile boolean ignoreChannelNameResolutionFailures; + + protected volatile Map channelIdentifierMap; /** @@ -155,32 +159,58 @@ public abstract class AbstractChannelNameResolvingMessageRouter extends Abstract } } - private void addChannelFromString(Collection channels, String channelName, Message message) { - if (channelName.indexOf(',') != -1) { - for (String name : StringUtils.commaDelimitedListToStringArray(channelName)) { + 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) { - channelName = this.prefix + channelName; + channelIdentifier = this.prefix + channelIdentifier; } if (this.suffix != null) { - channelName = channelName + suffix; + channelIdentifier = channelIdentifier + suffix; } - MessageChannel channel = resolveChannelForName(channelName, message); - if (channel != null) { - channels.add(channel); + /* + * Some routers due to their complex nature will already resolve 'channelIdentifier' + * to 'channelName' (e.g., PTR, EMETR) + */ + String channelName = channelIdentifier; + if (channelIdentifierMap != null && channelIdentifierMap.containsKey(channelIdentifier)){ + channelName = channelIdentifierMap.get(channelIdentifier); + } + + if (this.channelResolver != null){ + MessageChannel channel = resolveChannelForName(channelName, message); + if (channel != null) { + channels.add(channel); + } } } - private ConversionService getRequiredConversionService() { + protected ConversionService getRequiredConversionService() { if (this.getConversionService() == null) { this.setConversionService(ConversionServiceFactory.createDefaultConversionService()); } return this.getConversionService(); } + + public Map getChannelIdentifierMap() { + return channelIdentifierMap; + } + public void setChannelIdentifierMap(Map channelIdentifierMap) { + this.channelIdentifierMap = channelIdentifierMap; + } + + public void setChannelMapping(String channelIdentifier, String channelName){ + this.channelIdentifierMap.put(channelIdentifier, channelName); + } + + public void removeChannelMapping(String channelIdentifier){ + this.channelIdentifierMap.remove(channelIdentifier); + } /** * Subclasses must implement this method to return the channel indicators. */ 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..c51647465b 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 @@ -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 + * @author Oleg Zhurakousky */ -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; - } - +public class ErrorMessageExceptionTypeRouter extends AbstractChannelNameResolvingMessageRouter { @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/PayloadTypeRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/PayloadTypeRouter.java index d8221ec928..910f8fb641 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,54 @@ 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.integration.util.ClassUtils; -import org.springframework.util.Assert; +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 AbstractChannelNameResolvingMessageRouter { @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) { + 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/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..6a4e2addc3 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,6 +45,7 @@ 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.converter.SimpleMessageConverter; import org.springframework.integration.test.util.TestUtils; @@ -306,9 +307,12 @@ public class MessagingTemplateTests { @Test public void sendByChannelNameWithCustomChannelResolver() { QueueChannel testChannel = new QueueChannel(); - Map channelMap = new HashMap(); - channelMap.put("testChannel", testChannel); - MapBasedChannelResolver channelResolver = new MapBasedChannelResolver(channelMap); +// Map channelMap = new HashMap(); +// channelMap.put("testChannel", testChannel); + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerSingleton("testChannel", testChannel); + BeanFactoryChannelResolver channelResolver = new BeanFactoryChannelResolver(beanFactory); +// MapBasedChannelResolver channelResolver = new MapBasedChannelResolver(channelMap); MessagingTemplate template = new MessagingTemplate(); template.setChannelResolver(channelResolver); template.afterPropertiesSet(); @@ -352,11 +356,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.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); template.afterPropertiesSet(); Message message = MessageBuilder.withPayload("test").build(); testChannel.send(message); 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..04f6fcb8b7 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.setChannelResolver(new BeanFactoryChannelResolver(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.setChannelResolver(new BeanFactoryChannelResolver(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.setChannelResolver(new BeanFactoryChannelResolver(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.setChannelResolver(new BeanFactoryChannelResolver(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.setChannelResolver(new BeanFactoryChannelResolver(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.setChannelResolver(new BeanFactoryChannelResolver(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..9989315fae 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 @@ -20,19 +20,19 @@ 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.channel.QueueChannel; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.support.channel.BeanFactoryChannelResolver; /** * @author Mark Fisher + * @author Oleg Zhurakousky */ public class HeaderValueRouterTests { @@ -76,12 +76,13 @@ 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 channelResolverBeanDefinition = new RootBeanDefinition(BeanFactoryChannelResolver.class); + channelResolverBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(context); RootBeanDefinition routerBeanDefinition = new RootBeanDefinition(HeaderValueRouter.class); routerBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue("testHeaderName"); routerBeanDefinition.getPropertyValues().addPropertyValue("resolutionRequired", "true"); + routerBeanDefinition.getPropertyValues().addPropertyValue("channelIdentifierMap", channelMap); routerBeanDefinition.getPropertyValues().addPropertyValue("channelResolver", new RuntimeBeanReference("resolver")); context.registerBeanDefinition("resolver", channelResolverBeanDefinition); context.registerBeanDefinition("router", routerBeanDefinition); 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..c7a32b913d 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; /** @@ -81,6 +84,7 @@ public class MultiChannelRouterTests { return CollectionUtils.arrayToList(new String[] {"noSuchChannel"}); } }; + router.setChannelResolver(new BeanFactoryChannelResolver(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..d6e8112af5 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 @@ -25,15 +25,17 @@ 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; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.support.channel.BeanFactoryChannelResolver; /** * @author Mark Fisher + * @author Oleg Zhurakousky */ public class PayloadTypeRouterTests { @@ -41,15 +43,24 @@ 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.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + Message message1 = new GenericMessage("test"); Message message2 = new GenericMessage(123); - MessageChannel result1 = router.determineTargetChannel(message1); - MessageChannel result2 = router.determineTargetChannel(message2); + MessageChannel result1 = router.determineTargetChannels(message1).iterator().next(); + MessageChannel result2 = router.determineTargetChannels(message2).iterator().next(); + //MessageChannel result1 = router.determineTargetChannel(message1); + //MessageChannel result2 = router.determineTargetChannel(message2); assertEquals(stringChannel, result1); assertEquals(integerChannel, result2); } @@ -60,10 +71,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.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); router.setDefaultOutputChannel(defaultChannel); Message message = new GenericMessage(99); router.handleMessage(message); @@ -81,11 +97,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.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setChannelIdentifierMap(payloadTypeChannelMap); + router.setDefaultOutputChannel(defaultChannel); Message message = new GenericMessage(99); router.handleMessage(message); @@ -102,10 +127,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.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setChannelIdentifierMap(payloadTypeChannelMap); + router.setDefaultOutputChannel(defaultChannel); Message message = new GenericMessage(99); router.handleMessage(message); @@ -123,11 +156,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.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setChannelIdentifierMap(payloadTypeChannelMap); + router.setDefaultOutputChannel(defaultChannel); Message message = new GenericMessage(99); router.handleMessage(message); @@ -146,11 +188,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.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setChannelIdentifierMap(payloadTypeChannelMap); + router.setDefaultOutputChannel(defaultChannel); Message message = new GenericMessage("test"); try { @@ -169,11 +220,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.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setChannelIdentifierMap(payloadTypeChannelMap); + router.setDefaultOutputChannel(defaultChannel); Message message = new GenericMessage(99); router.handleMessage(message); @@ -190,11 +252,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.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setChannelIdentifierMap(payloadTypeChannelMap); + Message message1 = new GenericMessage("test"); Message message2 = new GenericMessage(123); router.handleMessage(message1); @@ -211,10 +281,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.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setChannelIdentifierMap(payloadTypeChannelMap); + router.setDefaultOutputChannel(defaultChannel); Message message1 = new GenericMessage("test"); Message message2 = new GenericMessage(123); 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..5409947e6d 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,6 +17,7 @@ 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; @@ -24,6 +25,7 @@ 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; @@ -32,6 +34,7 @@ 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; /** @@ -148,6 +151,7 @@ public class RouterTests { return "notImportant"; } }; + router.setChannelResolver(new BeanFactoryChannelResolver(mock(BeanFactory.class))); router.handleMessage(new GenericMessage("this should fail")); } @@ -159,6 +163,7 @@ public class RouterTests { return CollectionUtils.arrayToList(new String[] { "notImportant" }); } }; + router.setChannelResolver(new BeanFactoryChannelResolver(mock(BeanFactory.class))); router.handleMessage(new GenericMessage("this should fail")); } 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()); From c7fab44e0c8288bb4f647948da886eff1c5e1c51 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 13 Oct 2010 08:13:02 -0400 Subject: [PATCH 35/58] INT-1441 reverted back to calling canConvert(), but added check to see if source is empty Collection and target is a Collection and simply returning back what was passed for conversion since empty Collection could be cast to any other typed Collection, added test --- .../util/BeanFactoryTypeConverter.java | 16 ++++++---- .../util/BeanFactoryTypeConverterTests.java | 29 +++++++++++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/util/BeanFactoryTypeConverterTests.java 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 d7820b9bdf..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 @@ -16,6 +16,7 @@ package org.springframework.integration.util; import java.beans.PropertyEditor; +import java.util.Collection; import org.springframework.beans.BeansException; import org.springframework.beans.SimpleTypeConverter; @@ -27,6 +28,7 @@ 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 @@ -95,13 +97,15 @@ public class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware if (targetType.getType() == Void.class || targetType.getType() == Void.TYPE) { return null; } - try { - return conversionService.convert(value, sourceType, targetType); - } catch (ConversionFailedException e) { - throw e; - } catch (Exception ex){ - // ignore because we have a fallback strategy, see SPR-7548 for more details + 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); if (editor != null){ // INT-1441 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); + } +} From b2698bf65f84ff803b6008a7b6c1fff249d5f620 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Wed, 13 Oct 2010 09:07:51 -0400 Subject: [PATCH 36/58] INT-1382 added support for 'expression' sub-elements for router element --- .../config/xml/spring-integration-2.0.xsd | 1 + ...pressionRouterIntegrationTests-context.xml | 26 ++++++ ...namicExpressionRouterIntegrationTests.java | 85 +++++++++++++++++++ .../router/config/expressions.properties | 1 + 4 files changed, 113 insertions(+) create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/router/config/DynamicExpressionRouterIntegrationTests-context.xml create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/router/config/DynamicExpressionRouterIntegrationTests.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/router/config/expressions.properties 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 abad1e9ca6..32dc2233e0 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 @@ -1929,6 +1929,7 @@ Name of the header whose value to use. + 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/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 From b791921a1fdb9b798ae7c005bba37d247d320421 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 13 Oct 2010 09:49:26 -0400 Subject: [PATCH 37/58] INT-1474, documentation was updated to explain various scenarios for dealing with Messaging Gateway when reply is not coming --- src/docbkx/gateway.xml | 76 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) 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 From a1e73b6ee81763fbcba06df8d42007f1ebf36a8f Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Wed, 13 Oct 2010 10:22:39 -0400 Subject: [PATCH 38/58] INT-1382 added tests for Splitter with DynamicExpression --- ...essionSplitterIntegrationTests-context.xml | 22 +++++ ...micExpressionSplitterIntegrationTests.java | 89 +++++++++++++++++++ .../splitter/expressions.properties | 1 + 3 files changed, 112 insertions(+) create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/splitter/DynamicExpressionSplitterIntegrationTests-context.xml create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/splitter/DynamicExpressionSplitterIntegrationTests.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/splitter/expressions.properties 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 From 92bce40378072248339f1f4ddc64d5675d744e55 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 13 Oct 2010 10:23:15 -0400 Subject: [PATCH 39/58] INT-1377, modified tests, parsers and other components to ensure that BeanFactoryChannelResolver is the default one created internally by any router endpoint based on it being BeanFactoryAware --- ...tractChannelNameResolvingRouterParser.java | 3 --- .../core/MessagingTemplateTests.java | 10 ++++----- .../ErrorMessageExceptionTypeRouterTests.java | 12 +++++------ .../router/MultiChannelRouterTests.java | 2 +- .../router/PayloadTypeRouterTests.java | 21 +++++++++---------- .../integration/router/RouterTests.java | 4 ++-- 6 files changed, 23 insertions(+), 29 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java index 52554afc14..3c4c3f7c33 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java @@ -41,8 +41,6 @@ public abstract class AbstractChannelNameResolvingRouterParser extends AbstractR // 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) { - BeanDefinitionBuilder channelResolverBuilder = BeanDefinitionBuilder.genericBeanDefinition( - IntegrationNamespaceUtils.BASE_PACKAGE + ".support.channel.BeanFactoryChannelResolver"); ManagedMap channelMap = new ManagedMap(); for (Element childElement : childElements) { String beanClassName = beanDefinition.getBeanClassName(); @@ -59,7 +57,6 @@ public abstract class AbstractChannelNameResolvingRouterParser extends AbstractR channelMap.put(key, childElement.getAttribute("channel")); } beanDefinition.getPropertyValues().add("channelIdentifierMap", channelMap); - beanDefinition.getPropertyValues().add("channelResolver", channelResolverBuilder.getBeanDefinition()); } } return beanDefinition; 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 6a4e2addc3..d5e120b29c 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 @@ -307,14 +307,12 @@ public class MessagingTemplateTests { @Test public void sendByChannelNameWithCustomChannelResolver() { QueueChannel testChannel = new QueueChannel(); -// Map channelMap = new HashMap(); -// channelMap.put("testChannel", testChannel); + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); beanFactory.registerSingleton("testChannel", testChannel); - BeanFactoryChannelResolver channelResolver = new BeanFactoryChannelResolver(beanFactory); -// MapBasedChannelResolver channelResolver = new MapBasedChannelResolver(channelMap); + MessagingTemplate template = new MessagingTemplate(); - template.setChannelResolver(channelResolver); + template.setBeanFactory(beanFactory); template.afterPropertiesSet(); Message message = MessageBuilder.withPayload("test").build(); template.send("testChannel", message); @@ -360,7 +358,7 @@ public class MessagingTemplateTests { beanFactory.registerSingleton("testChannel", testChannel); MessagingTemplate template = new MessagingTemplate(); - template.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + 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/router/ErrorMessageExceptionTypeRouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/ErrorMessageExceptionTypeRouterTests.java index 04f6fcb8b7..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 @@ -75,7 +75,7 @@ public class ErrorMessageExceptionTypeRouterTests { exceptionTypeChannelMap.put(MessageHandlingException.class.getName(), "messageHandlingExceptionChannel"); router.setChannelIdentifierMap(exceptionTypeChannelMap); - router.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setBeanFactory(beanFactory); router.setDefaultOutputChannel(defaultChannel); router.handleMessage(message); @@ -97,7 +97,7 @@ public class ErrorMessageExceptionTypeRouterTests { exceptionTypeChannelMap.put(RuntimeException.class.getName(), "runtimeExceptionChannel"); exceptionTypeChannelMap.put(MessageHandlingException.class.getName(), "runtimeExceptionChannel"); router.setChannelIdentifierMap(exceptionTypeChannelMap); - router.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setBeanFactory(beanFactory); router.setDefaultOutputChannel(defaultChannel); router.handleMessage(message); @@ -118,7 +118,7 @@ public class ErrorMessageExceptionTypeRouterTests { Map exceptionTypeChannelMap = new HashMap(); exceptionTypeChannelMap.put(MessageHandlingException.class.getName(), "messageHandlingExceptionChannel"); router.setChannelIdentifierMap(exceptionTypeChannelMap); - router.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setBeanFactory(beanFactory); router.setDefaultOutputChannel(defaultChannel); router.handleMessage(message); assertNotNull(messageHandlingExceptionChannel.receive(1000)); @@ -154,7 +154,7 @@ public class ErrorMessageExceptionTypeRouterTests { Map exceptionTypeChannelMap = new HashMap(); exceptionTypeChannelMap.put(MessageDeliveryException.class.getName(), "messageDeliveryExceptionChannel"); router.setChannelIdentifierMap(exceptionTypeChannelMap); - router.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setBeanFactory(beanFactory); router.setResolutionRequired(true); router.handleMessage(message); } @@ -172,7 +172,7 @@ public class ErrorMessageExceptionTypeRouterTests { exceptionTypeChannelMap.put(RuntimeException.class.getName(), "runtimeExceptionChannel"); exceptionTypeChannelMap.put(MessageHandlingException.class.getName(), "messageHandlingExceptionChannel"); router.setChannelIdentifierMap(exceptionTypeChannelMap); - router.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setBeanFactory(beanFactory); router.setDefaultOutputChannel(defaultChannel); router.handleMessage(message); assertNotNull(illegalArgumentChannel.receive(1000)); @@ -193,7 +193,7 @@ public class ErrorMessageExceptionTypeRouterTests { exceptionTypeChannelMap.put(IllegalArgumentException.class.getName(), "illegalArgumentChannel"); exceptionTypeChannelMap.put(MessageHandlingException.class.getName(), "messageHandlingExceptionChannel"); router.setChannelIdentifierMap(exceptionTypeChannelMap); - router.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + 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/MultiChannelRouterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/MultiChannelRouterTests.java index c7a32b913d..e220d69130 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 @@ -84,7 +84,7 @@ public class MultiChannelRouterTests { return CollectionUtils.arrayToList(new String[] {"noSuchChannel"}); } }; - router.setChannelResolver(new BeanFactoryChannelResolver(mock(BeanFactory.class))); + 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 d6e8112af5..ad2a628b60 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 @@ -53,14 +53,13 @@ public class PayloadTypeRouterTests { payloadTypeChannelMap.put(Integer.class.getName(), "integerChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); router.setChannelIdentifierMap(payloadTypeChannelMap); - router.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setBeanFactory(beanFactory); Message message1 = new GenericMessage("test"); Message message2 = new GenericMessage(123); MessageChannel result1 = router.determineTargetChannels(message1).iterator().next(); MessageChannel result2 = router.determineTargetChannels(message2).iterator().next(); - //MessageChannel result1 = router.determineTargetChannel(message1); - //MessageChannel result2 = router.determineTargetChannel(message2); + assertEquals(stringChannel, result1); assertEquals(integerChannel, result2); } @@ -79,7 +78,7 @@ public class PayloadTypeRouterTests { payloadTypeChannelMap.put(Number.class.getName(), "numberChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); router.setChannelIdentifierMap(payloadTypeChannelMap); - router.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setBeanFactory(beanFactory); router.setDefaultOutputChannel(defaultChannel); Message message = new GenericMessage(99); router.handleMessage(message); @@ -108,7 +107,7 @@ public class PayloadTypeRouterTests { payloadTypeChannelMap.put(Integer.class.getName(), "integerChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); - router.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setBeanFactory(beanFactory); router.setChannelIdentifierMap(payloadTypeChannelMap); router.setDefaultOutputChannel(defaultChannel); @@ -136,7 +135,7 @@ public class PayloadTypeRouterTests { payloadTypeChannelMap.put(Comparable.class.getName(), "comparableChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); - router.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setBeanFactory(beanFactory); router.setChannelIdentifierMap(payloadTypeChannelMap); router.setDefaultOutputChannel(defaultChannel); @@ -167,7 +166,7 @@ public class PayloadTypeRouterTests { payloadTypeChannelMap.put(Comparable.class.getName(), "comparableChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); - router.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setBeanFactory(beanFactory); router.setChannelIdentifierMap(payloadTypeChannelMap); router.setDefaultOutputChannel(defaultChannel); @@ -199,7 +198,7 @@ public class PayloadTypeRouterTests { payloadTypeChannelMap.put(Comparable.class.getName(), "comparableChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); - router.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setBeanFactory(beanFactory); router.setChannelIdentifierMap(payloadTypeChannelMap); router.setDefaultOutputChannel(defaultChannel); @@ -233,7 +232,7 @@ public class PayloadTypeRouterTests { payloadTypeChannelMap.put(Serializable.class.getName(), "serializableChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); - router.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setBeanFactory(beanFactory); router.setChannelIdentifierMap(payloadTypeChannelMap); router.setDefaultOutputChannel(defaultChannel); @@ -262,7 +261,7 @@ public class PayloadTypeRouterTests { payloadTypeChannelMap.put(Integer.class.getName(), "integerChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); - router.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setBeanFactory(beanFactory); router.setChannelIdentifierMap(payloadTypeChannelMap); Message message1 = new GenericMessage("test"); @@ -291,7 +290,7 @@ public class PayloadTypeRouterTests { payloadTypeChannelMap.put(String.class.getName(), "stringChannel"); PayloadTypeRouter router = new PayloadTypeRouter(); - router.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + router.setBeanFactory(beanFactory); router.setChannelIdentifierMap(payloadTypeChannelMap); router.setDefaultOutputChannel(defaultChannel); 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 5409947e6d..8e6753a3dc 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 @@ -151,7 +151,7 @@ public class RouterTests { return "notImportant"; } }; - router.setChannelResolver(new BeanFactoryChannelResolver(mock(BeanFactory.class))); + router.setBeanFactory(mock(BeanFactory.class)); router.handleMessage(new GenericMessage("this should fail")); } @@ -163,7 +163,7 @@ public class RouterTests { return CollectionUtils.arrayToList(new String[] { "notImportant" }); } }; - router.setChannelResolver(new BeanFactoryChannelResolver(mock(BeanFactory.class))); + router.setBeanFactory(mock(BeanFactory.class)); router.handleMessage(new GenericMessage("this should fail")); } From 9f8b4d93a598680c7cb60032802e7befe18141e9 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 13 Oct 2010 10:53:43 -0400 Subject: [PATCH 40/58] INT-1377, deleted AbstractSingleChannelRouter and the corresponding tests --- .../router/AbstractSingleChannelRouter.java | 44 --------- .../router/SingleChannelRouterTests.java | 93 ------------------- 2 files changed, 137 deletions(-) delete mode 100644 spring-integration-core/src/main/java/org/springframework/integration/router/AbstractSingleChannelRouter.java delete mode 100644 spring-integration-core/src/test/java/org/springframework/integration/router/SingleChannelRouterTests.java 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/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); - } - -} From f69a178c1f3b913788f115633a915c106f32e845 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 13 Oct 2010 08:17:07 -0700 Subject: [PATCH 41/58] JMX: remove duplicate annotations --- .../integration/handler/LoggingHandler.java | 124 +++++++++--------- .../handler/LoggingHandlerTests.java | 2 - .../monitor/DirectChannelMetrics.java | 16 +-- .../LifecycleMessageHandlerMetrics.java | 1 - .../monitor/MessageHandlerMetrics.java | 14 +- .../monitor/SimpleMessageHandlerMetrics.java | 13 +- .../monitor/SimpleMessageSourceMetrics.java | 5 - .../jmx/config/MBeanRegistrationTests.java | 2 + 8 files changed, 76 insertions(+), 101 deletions(-) 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/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-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMetrics.java index 17b0a9a773..8c4f780dd1 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMetrics.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/DirectChannelMetrics.java @@ -20,10 +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.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.jmx.support.MetricType; import org.springframework.util.StopWatch; /** @@ -110,7 +107,7 @@ public class DirectChannelMetrics implements MethodInterceptor, MessageChannelMe timer.stop(); if ((Boolean)result) { sendSuccessRatio.success(); - sendDuration.append(timer.getTotalTimeSeconds()); + sendDuration.append(timer.getTotalTimeMillis()); } else { sendSuccessRatio.failure(); sendErrorCount.incrementAndGet(); @@ -132,7 +129,6 @@ public class DirectChannelMetrics implements MethodInterceptor, MessageChannelMe } } - @ManagedOperation public synchronized void reset() { sendDuration.reset(); sendErrorRate.reset(); @@ -142,52 +138,42 @@ public class DirectChannelMetrics implements MethodInterceptor, MessageChannelMe 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/LifecycleMessageHandlerMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMetrics.java index 2364e75b3a..c50c6ba78c 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMetrics.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/LifecycleMessageHandlerMetrics.java @@ -56,7 +56,6 @@ public class LifecycleMessageHandlerMetrics implements MessageHandlerMetrics, Li lifecycle.stop(); } - @ManagedOperation public void reset() { delegate.reset(); } diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMetrics.java index 141147c23a..30006df772 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMetrics.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/MessageHandlerMetrics.java @@ -32,37 +32,37 @@ public interface MessageHandlerMetrics { /** * @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 Status") + @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Active Count") int getActiveCount(); /** diff --git a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMetrics.java b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMetrics.java index 1c3e3f34d7..9dc052f9db 100644 --- a/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMetrics.java +++ b/spring-integration-jmx/src/main/java/org/springframework/integration/monitor/SimpleMessageHandlerMetrics.java @@ -26,10 +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.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; -import org.springframework.jmx.support.MetricType; import org.springframework.util.StopWatch; /** @@ -114,7 +111,7 @@ public class SimpleMessageHandlerMetrics implements MethodInterceptor, MessageHa handler.handleMessage(message); timer.stop(); - duration.append(timer.getTotalTimeSeconds()); + duration.append(timer.getTotalTimeMillis()); } catch (RuntimeException e) { errorCount.incrementAndGet(); throw e; @@ -126,14 +123,12 @@ public class SimpleMessageHandlerMetrics implements MethodInterceptor, MessageHa } } - @ManagedOperation public synchronized void reset() { duration.reset(); errorCount.set(0); handleCount.set(0); } - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Handler Execution Count", description = "rate=1h") public int getHandleCount() { if (logger.isTraceEnabled()) { logger.trace("Getting Handle Count:" + this); @@ -141,32 +136,26 @@ public class SimpleMessageHandlerMetrics implements MethodInterceptor, MessageHa 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(); } - @ManagedMetric(metricType = MetricType.GAUGE, displayName = "Handler Active Count") public int getActiveCount() { return activeCount.get(); } 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 index edcd1a84ad..0edda2aa6e 100644 --- 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 @@ -18,9 +18,6 @@ import java.util.concurrent.atomic.AtomicInteger; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.integration.core.MessageSource; -import org.springframework.jmx.export.annotation.ManagedMetric; -import org.springframework.jmx.export.annotation.ManagedOperation; -import org.springframework.jmx.support.MetricType; /** * @author Dave Syer @@ -62,12 +59,10 @@ public class SimpleMessageSourceMetrics implements MethodInterceptor, MessageSou return messageSource; } - @ManagedOperation public void reset() { messageCount.set(0); } - @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Message Source Message Count") public int getMessageCount() { return messageCount.get(); } 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 index 924916c5cc..d02421101b 100644 --- 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 @@ -15,6 +15,7 @@ package org.springframework.integration.jmx.config; import static org.junit.Assert.assertEquals; +import java.util.Arrays; import java.util.Set; import javax.management.MBeanServer; @@ -46,6 +47,7 @@ public class MBeanRegistrationTests { @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()); } From ecc8b23cf18adc0d1da458c0450cff884a49a8cf Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 13 Oct 2010 11:30:09 -0400 Subject: [PATCH 42/58] INT-1377, merged AbstractSingleChannelNameRouter with AbstractMessageRouter, fixed corresponding tests, removed dependencies on it across the workspace --- .../integration/config/RouterFactoryBean.java | 15 +- ...ractChannelNameResolvingMessageRouter.java | 219 ------------------ .../AbstractMessageProcessingRouter.java | 2 +- .../router/AbstractMessageRouter.java | 192 ++++++++++++++- .../AbstractSingleChannelNameRouter.java | 2 +- .../ErrorMessageExceptionTypeRouter.java | 2 +- .../integration/router/HeaderValueRouter.java | 2 +- .../integration/router/PayloadTypeRouter.java | 2 +- .../router/RecipientListRouter.java | 35 ++- .../router/MultiChannelRouterTests.java | 6 +- .../integration/router/RouterTests.java | 32 +-- .../router/config/RouterParserTests.java | 6 +- .../xml/router/AbstractXPathRouter.java | 4 +- 13 files changed, 252 insertions(+), 267 deletions(-) delete mode 100644 spring-integration-core/src/main/java/org/springframework/integration/router/AbstractChannelNameResolvingMessageRouter.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java index e2f138a984..4d804179e8 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/RouterFactoryBean.java @@ -20,7 +20,6 @@ import org.springframework.aop.framework.Advised; import org.springframework.expression.Expression; import org.springframework.integration.MessageChannel; import org.springframework.integration.core.MessageHandler; -import org.springframework.integration.router.AbstractChannelNameResolvingMessageRouter; import org.springframework.integration.router.AbstractMessageRouter; import org.springframework.integration.router.ExpressionEvaluatingRouter; import org.springframework.integration.router.MethodInvokingRouter; @@ -144,11 +143,11 @@ public class RouterFactoryBean extends AbstractMessageHandlerFactoryBean { } 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 AbstractChannelNameResolvingMessageRouter) { - ((AbstractChannelNameResolvingMessageRouter) router).setChannelIdentifierMap(this.channelIdentifierMap); + if (this.channelIdentifierMap != null && router instanceof AbstractMessageRouter) { + ((AbstractMessageRouter) router).setChannelIdentifierMap(this.channelIdentifierMap); } if (this.defaultOutputChannel != null) { router.setDefaultOutputChannel(this.defaultOutputChannel); @@ -157,10 +156,10 @@ 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) + + AbstractMessageRouter.class.getName()); + ((AbstractMessageRouter) router) .setIgnoreChannelNameResolutionFailures(ignoreChannelNameResolutionFailures); } if (this.applySequence != null) { 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 e6e116529d..0000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/AbstractChannelNameResolvingMessageRouter.java +++ /dev/null @@ -1,219 +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 java.util.Map; - -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 - * @author Oleg Zhurakousky - */ -public abstract class AbstractChannelNameResolvingMessageRouter extends AbstractMessageRouter { - - private volatile String prefix; - - private volatile String suffix; - - private volatile ChannelResolver channelResolver; - - private volatile boolean ignoreChannelNameResolutionFailures; - - protected volatile Map channelIdentifierMap; - - - /** - * 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; - } - - 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 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 (channelIdentifierMap != null && channelIdentifierMap.containsKey(channelIdentifier)){ - channelName = channelIdentifierMap.get(channelIdentifier); - } - - if (this.channelResolver != null){ - MessageChannel channel = resolveChannelForName(channelName, message); - if (channel != null) { - channels.add(channel); - } - } - } - - protected ConversionService getRequiredConversionService() { - if (this.getConversionService() == null) { - this.setConversionService(ConversionServiceFactory.createDefaultConversionService()); - } - return this.getConversionService(); - } - - public Map getChannelIdentifierMap() { - return channelIdentifierMap; - } - - public void setChannelIdentifierMap(Map channelIdentifierMap) { - this.channelIdentifierMap = channelIdentifierMap; - } - - public void setChannelMapping(String channelIdentifier, String channelName){ - this.channelIdentifierMap.put(channelIdentifier, channelName); - } - - public void removeChannelMapping(String channelIdentifier){ - this.channelIdentifierMap.remove(channelIdentifier); - } - /** - * 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..1eae37810a 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,15 @@ 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 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 +32,17 @@ 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.StringUtils; /** * Base class for Message Routers. * * @author Mark Fisher + * @author Oleg Zhurakousky */ public abstract class AbstractMessageRouter extends AbstractMessageHandler { @@ -42,6 +55,177 @@ 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; + + /** + * 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; + } + + 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 (channelIdentifierMap != null && 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() + "]"); + } + } + } + + + 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(); + } + + public Map getChannelIdentifierMap() { + return channelIdentifierMap; + } + + public void setChannelIdentifierMap(Map channelIdentifierMap) { + this.channelIdentifierMap = channelIdentifierMap; + } + + public void setChannelMapping(String channelIdentifier, String channelName){ + this.channelIdentifierMap.put(channelIdentifier, channelName); + } + + public void removeChannelMapping(String channelIdentifier){ + this.channelIdentifierMap.remove(channelIdentifier); + } + /** + * Subclasses must implement this method to return the channel indicators. + */ + protected abstract List getChannelIndicatorList(Message message); /** * Set the default channel where Messages should be sent if channel resolution fails to return any channels. If no @@ -137,9 +321,9 @@ 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); +// /** +// * Subclasses must implement this method to return the target channels for a given Message. +// */ +// protected abstract Collection determineTargetChannels(Message message); } 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/ErrorMessageExceptionTypeRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/ErrorMessageExceptionTypeRouter.java index c51647465b..50548a1ec2 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 @@ -30,7 +30,7 @@ import org.springframework.integration.MessageChannel; * @author Mark Fisher * @author Oleg Zhurakousky */ -public class ErrorMessageExceptionTypeRouter extends AbstractChannelNameResolvingMessageRouter { +public class ErrorMessageExceptionTypeRouter extends AbstractMessageRouter { @Override protected List getChannelIndicatorList(Message message) { 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 910f8fb641..1c7e965fcb 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 @@ -30,7 +30,7 @@ import org.springframework.util.StringUtils; * @author Mark Fisher * @author Oleg Zhurakousky */ -public class PayloadTypeRouter extends AbstractChannelNameResolvingMessageRouter { +public class PayloadTypeRouter extends AbstractMessageRouter { @Override protected List getChannelIndicatorList(Message message) { 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..00812ab592 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 @@ -89,17 +89,17 @@ public class RecipientListRouter extends AbstractMessageRouter implements Initia Assert.notEmpty(this.recipients, "a non-empty recipient list is required"); } - @Override - protected Collection determineTargetChannels(Message message) { - List channels = new ArrayList(); - List recipientList = this.recipients; - for (Recipient recipient : recipientList) { - if (recipient.accept(message)) { - channels.add(recipient.getChannel()); - } - } - return channels; - } +// @Override +// protected Collection determineTargetChannels(Message message) { +// List channels = new ArrayList(); +// List recipientList = this.recipients; +// for (Recipient recipient : recipientList) { +// if (recipient.accept(message)) { +// channels.add(recipient.getChannel()); +// } +// } +// return channels; +// } public static class Recipient { @@ -126,4 +126,17 @@ public class RecipientListRouter extends AbstractMessageRouter implements Initia } } + + @Override + protected List getChannelIndicatorList(Message message) { + List channels = new ArrayList(); + List recipientList = this.recipients; + for (Recipient recipient : recipientList) { + if (recipient.accept(message)) { + channels.add(recipient.getChannel()); + } + } + return channels; + } + } 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 e220d69130..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 @@ -40,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"}); @@ -64,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"} ); @@ -78,7 +78,7 @@ 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"}); 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 8e6753a3dc..53eaef25d3 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 @@ -45,9 +45,11 @@ 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); @@ -56,7 +58,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; } }; @@ -68,8 +71,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"); @@ -79,8 +83,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); @@ -90,8 +95,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; } }; @@ -103,7 +109,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; } @@ -118,7 +124,7 @@ public class RouterTests { @Test public void emptyChannelNameArrayIgnoredByDefault() { - AbstractChannelNameResolvingMessageRouter router = new AbstractChannelNameResolvingMessageRouter() { + AbstractMessageRouter router = new AbstractMessageRouter() { protected List getChannelIndicatorList(Message message) { return new ArrayList(); } @@ -131,7 +137,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[] {}); @@ -157,7 +163,7 @@ public class RouterTests { @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" }); @@ -185,7 +191,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/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-xml/src/main/java/org/springframework/integration/xml/router/AbstractXPathRouter.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/AbstractXPathRouter.java index fad0bafb25..4bc897c5ac 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/AbstractXPathRouter.java @@ -19,7 +19,7 @@ package org.springframework.integration.xml.router; import java.util.HashMap; import java.util.Map; -import org.springframework.integration.router.AbstractChannelNameResolvingMessageRouter; +import org.springframework.integration.router.AbstractMessageRouter; import org.springframework.integration.xml.DefaultXmlPayloadConverter; import org.springframework.integration.xml.XmlPayloadConverter; import org.springframework.xml.xpath.XPathExpression; @@ -31,7 +31,7 @@ import org.springframework.xml.xpath.XPathExpressionFactory; * * @author Jonas Partner */ -public abstract class AbstractXPathRouter extends AbstractChannelNameResolvingMessageRouter { +public abstract class AbstractXPathRouter extends AbstractMessageRouter { private final XPathExpression xPathExpression; From 4a8984bc0a9ada2465795bb1e0453bcb6d5d5ea5 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 13 Oct 2010 08:41:32 -0700 Subject: [PATCH 43/58] remove System.err reference --- .../integration/jmx/config/MBeanRegistrationTests.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 index d02421101b..01668ebf68 100644 --- 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 @@ -15,7 +15,6 @@ package org.springframework.integration.jmx.config; import static org.junit.Assert.assertEquals; -import java.util.Arrays; import java.util.Set; import javax.management.MBeanServer; @@ -47,7 +46,7 @@ public class MBeanRegistrationTests { @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())); + // 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()); } From 6a5efcc7b848d6e47e05f65fe3fa9306ca0e50c5 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Wed, 13 Oct 2010 12:48:06 -0400 Subject: [PATCH 44/58] INT-1382 added tests for DynamicExpression-based transformer, and the default bean name for the ExpressionSource instance is now defined in the XSD as 'expressionSource' --- .../config/xml/spring-integration-2.0.xsd | 2 +- ...ionTransformerIntegrationTests-context.xml | 22 +++++++ ...ExpressionTransformerIntegrationTests.java | 61 +++++++++++++++++++ .../transformer/expressions.properties | 1 + 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionTransformerIntegrationTests-context.xml create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionTransformerIntegrationTests.java create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/transformer/expressions.properties 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 32dc2233e0..ba96804367 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 @@ -2383,7 +2383,7 @@ Name of the header whose value to use. - + The reference to an ExpressionSource. 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..77ccc19867 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/transformer/expressions.properties @@ -0,0 +1 @@ +test.transform=payload.foo + headers.bar \ No newline at end of file From 713baedf43b5b429adafca5be58fda18687508a5 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 13 Oct 2010 13:23:09 -0400 Subject: [PATCH 45/58] INT-1377, Added test: a) to validate that call to determineTargetChannel always return no more then 1 channel for PTR, b) to make sure that if mapping changes the appropriate channel is selected 3) to make sure if mapping was removed messages are forwarded to defaultChannel or exception is thrown if defaultChannel is not provided and resolutionRequired is set to 'true', d) MessagingTemplate although defaults to BFCR can still rely on Custom CR Added, the same custom CR was tested for Routers --- .../router/AbstractMessageRouter.java | 14 +++-- .../integration/router/PayloadTypeRouter.java | 5 +- .../core/MessagingTemplateTests.java | 12 +++++ .../router/HeaderValueRouterTests.java | 52 +++++++++++++++++-- .../router/PayloadTypeRouterTests.java | 42 ++++++++++++++- .../integration/router/RouterTests.java | 8 +-- 6 files changed, 112 insertions(+), 21 deletions(-) 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 1eae37810a..5183f017ad 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 @@ -21,6 +21,7 @@ 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; @@ -215,11 +216,14 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler { this.channelIdentifierMap = channelIdentifierMap; } - public void setChannelMapping(String channelIdentifier, String channelName){ + public synchronized void setChannelMapping(String channelIdentifier, String channelName){ + if (channelIdentifierMap == null){ + channelIdentifierMap = new ConcurrentHashMap(); + } this.channelIdentifierMap.put(channelIdentifier, channelName); } - public void removeChannelMapping(String channelIdentifier){ + public synchronized void removeChannelMapping(String channelIdentifier){ this.channelIdentifierMap.remove(channelIdentifier); } /** @@ -320,10 +324,4 @@ 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); - } 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 1c7e965fcb..3293db5845 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 @@ -19,8 +19,11 @@ package org.springframework.integration.router; 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.support.channel.BeanFactoryChannelResolver; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -37,7 +40,7 @@ public class PayloadTypeRouter extends AbstractMessageRouter { Class firstInterfaceMatch = null; Class type = message.getPayload().getClass(); - while (type != null) { + while (type != null && channelIdentifierMap != null) { 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) { 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 d5e120b29c..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 @@ -47,6 +47,7 @@ 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; @@ -307,16 +308,27 @@ public class MessagingTemplateTests { @Test public void sendByChannelNameWithCustomChannelResolver() { QueueChannel testChannel = new QueueChannel(); + final QueueChannel anotherChannel = new QueueChannel(); DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); beanFactory.registerSingleton("testChannel", testChannel); MessagingTemplate template = new MessagingTemplate(); 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) 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 9989315fae..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,6 +16,7 @@ package org.springframework.integration.router; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; @@ -25,10 +26,12 @@ 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.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 @@ -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 @@ -77,14 +95,11 @@ public class HeaderValueRouterTests { StaticApplicationContext context = new StaticApplicationContext(); ManagedMap channelMap = new ManagedMap(); channelMap.put("testKey", "testChannel"); - RootBeanDefinition channelResolverBeanDefinition = new RootBeanDefinition(BeanFactoryChannelResolver.class); - channelResolverBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(context); RootBeanDefinition routerBeanDefinition = new RootBeanDefinition(HeaderValueRouter.class); routerBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue("testHeaderName"); routerBeanDefinition.getPropertyValues().addPropertyValue("resolutionRequired", "true"); routerBeanDefinition.getPropertyValues().addPropertyValue("channelIdentifierMap", channelMap); - routerBeanDefinition.getPropertyValues().addPropertyValue("channelResolver", new RuntimeBeanReference("resolver")); - context.registerBeanDefinition("resolver", channelResolverBeanDefinition); + routerBeanDefinition.getPropertyValues().addPropertyValue("beanFactory", context); context.registerBeanDefinition("router", routerBeanDefinition); context.registerBeanDefinition("testChannel", new RootBeanDefinition(QueueChannel.class)); context.refresh(); @@ -96,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() { @@ -145,4 +188,5 @@ public class HeaderValueRouterTests { assertSame(message, result2); } + } 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 ad2a628b60..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; @@ -31,7 +32,6 @@ import org.springframework.integration.MessageChannel; import org.springframework.integration.MessageHandlingException; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.message.GenericMessage; -import org.springframework.integration.support.channel.BeanFactoryChannelResolver; /** * @author Mark Fisher @@ -57,11 +57,32 @@ public class PayloadTypeRouterTests { Message message1 = new GenericMessage("test"); Message message2 = new GenericMessage(123); + 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 @@ -86,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 @@ -177,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) @@ -305,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 53eaef25d3..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 @@ -20,25 +20,22 @@ 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; import org.springframework.integration.channel.TestChannelResolver; import org.springframework.integration.message.GenericMessage; -import org.springframework.integration.support.channel.BeanFactoryChannelResolver; import org.springframework.util.CollectionUtils; /** * @author Mark Fisher + * @author Oleg Zhurakousky */ public class RouterTests { @@ -48,8 +45,7 @@ public class RouterTests { @Override protected List getChannelIndicatorList(Message message) { return null; - } - + } }; Message message = new GenericMessage("test"); router.handleMessage(message); From 5b6a96c34fcd98d57e8814261c39950e9c19b0af Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 13 Oct 2010 15:14:33 -0400 Subject: [PATCH 46/58] INT-1377, polishing, added test for expression based router dynamics --- .../router/AbstractMessageRouter.java | 257 +++++++++--------- .../ErrorMessageExceptionTypeRouter.java | 4 +- .../integration/router/PayloadTypeRouter.java | 14 +- .../router/RecipientListRouter.java | 44 +-- .../config/RouterWithMappingTests-context.xml | 2 +- .../router/config/RouterWithMappingTests.java | 15 + 6 files changed, 174 insertions(+), 162 deletions(-) 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 5183f017ad..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 @@ -37,10 +37,11 @@ import org.springframework.integration.support.channel.BeanFactoryChannelResolve 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 @@ -65,7 +66,7 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler { private volatile boolean ignoreChannelNameResolutionFailures; - protected volatile Map channelIdentifierMap; + protected volatile Map channelIdentifierMap = new ConcurrentHashMap(); /** * Specify the {@link ChannelResolver} strategy to use. @@ -97,139 +98,26 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler { 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; - } - - 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 (channelIdentifierMap != null && 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() + "]"); - } - } - } - - - 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(); - } - - public Map getChannelIdentifierMap() { - return channelIdentifierMap; - } - + /** + * 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 = channelIdentifierMap; + this.channelIdentifierMap.clear(); + this.channelIdentifierMap.putAll(channelIdentifierMap); } - public synchronized void setChannelMapping(String channelIdentifier, String channelName){ - if (channelIdentifierMap == null){ - channelIdentifierMap = new ConcurrentHashMap(); - } + public void setChannelMapping(String channelIdentifier, String channelName){ this.channelIdentifierMap.put(channelIdentifier, channelName); } - - public synchronized void removeChannelMapping(String channelIdentifier){ + /** + * Removes channel mapping for a give channel identifier + * @param channelIdentifier + */ + public void removeChannelMapping(String channelIdentifier){ this.channelIdentifierMap.remove(channelIdentifier); } - /** - * Subclasses must implement this method to return the channel indicators. - */ - protected abstract List getChannelIndicatorList(Message message); /** * Set the default channel where Messages should be sent if channel resolution fails to return any channels. If no @@ -287,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) { @@ -324,4 +239,90 @@ public abstract class AbstractMessageRouter extends AbstractMessageHandler { } } } + + 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/ErrorMessageExceptionTypeRouter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/ErrorMessageExceptionTypeRouter.java index 50548a1ec2..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. @@ -29,7 +29,7 @@ import org.springframework.integration.MessageChannel; * * @author Mark Fisher * @author Oleg Zhurakousky - */ + */ public class ErrorMessageExceptionTypeRouter extends AbstractMessageRouter { @Override 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 3293db5845..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 @@ -24,6 +24,7 @@ import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.support.channel.BeanFactoryChannelResolver; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** @@ -34,13 +35,22 @@ import org.springframework.util.StringUtils; * @author Oleg Zhurakousky */ 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 List getChannelIndicatorList(Message message) { Class firstInterfaceMatch = null; Class type = message.getPayload().getClass(); - while (type != null && channelIdentifierMap != null) { + 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) { 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 00812ab592..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,20 +89,19 @@ 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(); -// List recipientList = this.recipients; -// for (Recipient recipient : recipientList) { -// if (recipient.accept(message)) { -// channels.add(recipient.getChannel()); -// } -// } -// return channels; -// } - - + + @Override + protected List getChannelIndicatorList(Message message) { + List channels = new ArrayList(); + List recipientList = this.recipients; + for (Recipient recipient : recipientList) { + if (recipient.accept(message)) { + channels.add(recipient.getChannel()); + } + } + return channels; + } + public static class Recipient { private final MessageChannel channel; @@ -125,18 +125,4 @@ public class RecipientListRouter extends AbstractMessageRouter implements Initia return this.channel; } } - - - @Override - protected List getChannelIndicatorList(Message message) { - List channels = new ArrayList(); - List recipientList = this.recipients; - for (Recipient recipient : recipientList) { - if (recipient.accept(message)) { - channels.add(recipient.getChannel()); - } - } - return channels; - } - } 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 From 6c053a9e110cae0632a7ddfa43b6088df5efd5d6 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 13 Oct 2010 16:16:48 -0400 Subject: [PATCH 47/58] INT-1377, added support for dynamics in XML XPath router, added mapping support for XPath router configuration --- spring-integration-xml/.springBeans | 14 ---- .../xml/config/XPathRouterParser.java | 18 +++++ .../xml/config/spring-integration-xml-2.0.xsd | 15 ++++ .../xml/config/XPathRouterParserTests.java | 69 +++++++++++++++++++ .../xml/config/XPathRouterTests-context.xml | 34 +++++++++ 5 files changed, 136 insertions(+), 14 deletions(-) delete mode 100644 spring-integration-xml/.springBeans create mode 100644 spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathRouterTests-context.xml 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/config/XPathRouterParser.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XPathRouterParser.java index c7f4ca8ddc..95f5a7de6a 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,16 +16,21 @@ package org.springframework.integration.xml.config; +import java.util.List; + import org.w3c.dom.Element; import org.w3c.dom.NodeList; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.ManagedMap; 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.Assert; import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; /** * Parser for the <xpath-router/> element. @@ -63,6 +68,19 @@ public class XPathRouterParser extends AbstractConsumerEndpointParser { String classname = "org.springframework.integration.xml.router." + ((multiChannel) ? "XPathMultiChannelRouter" : "XPathSingleChannelRouter"); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(classname); + + + List childElements = DomUtils.getChildElementsByTagName(element, "mapping"); + if (childElements != null && childElements.size() > 0) { + ManagedMap channelMap = new ManagedMap(); + for (Element childElement : childElements) { + String key = childElement.getAttribute("value"); + channelMap.put(key, childElement.getAttribute("channel")); + } + builder.addPropertyValue("channelIdentifierMap", channelMap); + } + + if (xPathExpressionChildPresent) { BeanDefinition beanDefinition = this.xpathParser.parse( (Element) xPathExpressionNodes.item(0), parserContext); 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 85290c7a1b..adf751481d 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,6 +352,21 @@ + + + + + + + + + + + + + + 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..18abbb4381 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; @@ -195,5 +201,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..f9ecfa7233 --- /dev/null +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XPathRouterTests-context.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 7b70e3dcfed2462d5fc4741876977c9fe9b31c1b Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Wed, 13 Oct 2010 16:21:03 -0400 Subject: [PATCH 48/58] INT-1382 added support for expression sub-elements for header-enricher --- .../xml/HeaderEnricherParserSupport.java | 21 ++++++-- .../transformer/HeaderEnricher.java | 8 +++ ...HeaderEnricherIntegrationTests-context.xml | 24 +++++++++ ...ressionHeaderEnricherIntegrationTests.java | 53 +++++++++++++++++++ .../transformer/expressions.properties | 3 +- 5 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionHeaderEnricherIntegrationTests-context.xml create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/transformer/DynamicExpressionHeaderEnricherIntegrationTests.java 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/transformer/HeaderEnricher.java b/spring-integration-core/src/main/java/org/springframework/integration/transformer/HeaderEnricher.java index 39d65cdae5..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 @@ -175,6 +175,14 @@ public class HeaderEnricher implements Transformer { private final ExpressionEvaluatingMessageProcessor targetProcessor; + /** + * 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. 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/expressions.properties b/spring-integration-core/src/test/java/org/springframework/integration/transformer/expressions.properties index 77ccc19867..90fa9aeacb 100644 --- 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 @@ -1 +1,2 @@ -test.transform=payload.foo + headers.bar \ No newline at end of file +test.transform=payload.foo + headers.bar +test.header='foo' From fead4270cf34826bb7de4a0ecaeb42316e26dd2f Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Wed, 13 Oct 2010 16:45:38 -0400 Subject: [PATCH 49/58] INT-1382 updated XSD to support 'expression' sub-elements for header-enricher --- .../integration/config/xml/spring-integration-2.0.xsd | 3 +++ 1 file changed, 3 insertions(+) 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 ba96804367..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 @@ + + + From d58dd2fe25e1ded61746080ac831944300ba00c4 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 13 Oct 2010 17:35:32 -0400 Subject: [PATCH 50/58] INT-1377, polishing --- .../integration/config/xml/PayloadTypeRouterParser.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 b948e11557..38167dc759 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 @@ -33,8 +33,8 @@ public class PayloadTypeRouterParser extends AbstractChannelNameResolvingRouterP @Override protected BeanDefinition doParseRouter(Element element, ParserContext parserContext) { - BeanDefinitionBuilder headerValueRouterBuilder = BeanDefinitionBuilder.genericBeanDefinition( + BeanDefinitionBuilder payloadTypeRouterBuilder = BeanDefinitionBuilder.genericBeanDefinition( IntegrationNamespaceUtils.BASE_PACKAGE + ".router.PayloadTypeRouter"); - return headerValueRouterBuilder.getBeanDefinition(); + return payloadTypeRouterBuilder.getBeanDefinition(); } } From f5fe76d7155122bbcc11dbd825c3129cda12ceb1 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Wed, 13 Oct 2010 22:08:12 -0400 Subject: [PATCH 51/58] INT-1382 added simple 'expression' sub-element description to the 'filter' chapter --- src/docbkx/filter.xml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) 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 From a389fa9795d8b48096750ca8bd6b4b473240a44a Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 14 Oct 2010 05:56:19 -0400 Subject: [PATCH 52/58] INT-1515 initial round of refactoring to turn validating-router into validating-filter --- .../IntegrationXmlNamespaceHandler.java | 2 +- .../XmlPayloadValidatingFilterParser.java | 59 ++++++++++ .../XmlPayloadValidatingRouterParser.java | 101 ------------------ .../router/XmlPayloadValidatingRouter.java | 61 ----------- .../SchemaValidatingMessageSelector.java | 73 +++++++++++++ .../xml/config/spring-integration-xml-2.0.xsd | 17 +-- ...oadValidatingFilterParserTests-context.xml | 24 +++++ ...XmlPayloadValidatingFilterParserTests.java | 62 +++++++++++ ...XmlPayloadValidatingRouterParserTests.java | 88 --------------- .../XmlPayloadValidatingRouterTests.java | 81 -------------- 10 files changed, 223 insertions(+), 345 deletions(-) create mode 100644 spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParser.java delete mode 100644 spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingRouterParser.java delete mode 100644 spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XmlPayloadValidatingRouter.java create mode 100644 spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/SchemaValidatingMessageSelector.java create mode 100644 spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests-context.xml create mode 100644 spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests.java delete mode 100644 spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingRouterParserTests.java delete mode 100644 spring-integration-xml/src/test/java/org/springframework/integration/xml/router/XmlPayloadValidatingRouterTests.java 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/XmlPayloadValidatingFilterParser.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParser.java new file mode 100644 index 0000000000..f688a4c1b8 --- /dev/null +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParser.java @@ -0,0 +1,59 @@ +/* + * 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.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.w3c.dom.Element; + +/** + * @author Jonas Partner + * @author Oleg Zhurakousky + */ +public class XmlPayloadValidatingFilterParser extends AbstractConsumerEndpointParser { + private static String SELECTOR = + "org.springframework.integration.xml.selector.SchemaValidatingMessageSelector"; + private static String FILTER = + "org.springframework.integration.config.FilterFactoryBean"; + + @Override + protected boolean shouldGenerateId() { + return true; + } + + @Override + protected boolean shouldGenerateIdAsFallback() { + return true; + } + + @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); + selectorBuilder.addConstructorArgValue(element.getAttribute("schema-location")); + selectorBuilder.addPropertyValue("schemaType", element.getAttribute("schema-type")); + + filterBuilder.addPropertyValue("targetObject", selectorBuilder.getBeanDefinition()); + return filterBuilder; + } +} 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/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/SchemaValidatingMessageSelector.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/SchemaValidatingMessageSelector.java new file mode 100644 index 0000000000..33df93f65e --- /dev/null +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/SchemaValidatingMessageSelector.java @@ -0,0 +1,73 @@ +/* + * 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.DefaultXmlPayloadConverter; +import org.springframework.integration.xml.XmlPayloadConverter; +import org.springframework.util.Assert; +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 SchemaValidatingMessageSelector implements MessageSelector{ + + private final XmlValidator xmlValidator; + private volatile String schemaType = XmlValidatorFactory.SCHEMA_W3C_XML; + + private volatile XmlPayloadConverter converter = new DefaultXmlPayloadConverter(); + + + public SchemaValidatingMessageSelector(Resource schema) throws Exception{ + Assert.notNull(schema, "You must provide XML schema location to perform validation"); + this.xmlValidator = XmlValidatorFactory.createValidator(schema, schemaType); + } + + /** + * Converter used to convert payloads prior to validation + * + * @param converter + */ + public void setConverter(XmlPayloadConverter converter) { + this.converter = converter; + } + + public void setSchemaType(String schemaType) { + this.schemaType = schemaType; + } + + @Override + public boolean accept(Message message) { + // TODO Need to figure out how the exceptions could be propagated since the return from this method is true/false + // and 'throw-exception-on-rejection'is actually set on the filter + try { + SAXParseException[] validationExceptions = xmlValidator.validate(converter.convertToSource(message.getPayload())); + return validationExceptions.length == 0 ? true : false; + } catch (Exception e) { + e.printStackTrace(); + throw new MessageHandlingException(message, e); + } + } +} 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 adf751481d..438f986123 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 @@ -510,11 +510,11 @@ - + - Defines a validating router. + Defines a validating filter. @@ -530,17 +530,8 @@ - - - - - - - - - - - + + 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..30e11e3ae2 --- /dev/null +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests-context.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + 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..a0f7a58930 --- /dev/null +++ b/spring-integration-xml/src/test/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParserTests.java @@ -0,0 +1,62 @@ +/* + * 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.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("inputChannel", MessageChannel.class); + inputChannel.send(docMessage); + assertNotNull(validChannel.receive(100)); + + } + @Test + public void testInvalidMessage() 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("inputChannel", MessageChannel.class); + inputChannel.send(docMessage); + assertNotNull(invalidChannel.receive(100)); + assertNull(validChannel.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/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; - } - - } - -} From 3a8444c949418913acdea588bd020fcb9f901a3a Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 14 Oct 2010 06:07:44 -0400 Subject: [PATCH 53/58] INT-1515, removed @Override on accept method --- .../xml/selector/SchemaValidatingMessageSelector.java | 1 - 1 file changed, 1 deletion(-) diff --git a/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/SchemaValidatingMessageSelector.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/SchemaValidatingMessageSelector.java index 33df93f65e..e4c82c0857 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/SchemaValidatingMessageSelector.java +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/SchemaValidatingMessageSelector.java @@ -58,7 +58,6 @@ public class SchemaValidatingMessageSelector implements MessageSelector{ this.schemaType = schemaType; } - @Override public boolean accept(Message message) { // TODO Need to figure out how the exceptions could be propagated since the return from this method is true/false // and 'throw-exception-on-rejection'is actually set on the filter From 1d0b773f2b29f6d681a725a0fa52ffa2dfcfcc0c Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 14 Oct 2010 06:45:22 -0400 Subject: [PATCH 54/58] INT-1517, combined Single and Multi channel XPath routers, modified XPathRouterParser to inherit form the base parser for all routers, combine single/multiple xpath router tests into one --- ...tractChannelNameResolvingRouterParser.java | 11 ++- .../xml/config/XPathRouterParser.java | 62 +++--------- .../xml/router/XPathMultiChannelRouter.java | 95 ------------------- ...tractXPathRouter.java => XPathRouter.java} | 34 ++++++- .../xml/router/XPathSingleChannelRouter.java | 93 ------------------ .../xml/config/spring-integration-xml-2.0.xsd | 1 - .../xml/config/XPathRouterParserTests.java | 1 + .../xml/config/XPathRouterTests-context.xml | 2 +- ...RouterTests.java => XPathRouterTests.java} | 56 +++++++++-- .../router/XPathSingleChannelRouterTests.java | 78 --------------- 10 files changed, 101 insertions(+), 332 deletions(-) delete mode 100644 spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XPathMultiChannelRouter.java rename spring-integration-xml/src/main/java/org/springframework/integration/xml/router/{AbstractXPathRouter.java => XPathRouter.java} (73%) delete mode 100644 spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XPathSingleChannelRouter.java rename spring-integration-xml/src/test/java/org/springframework/integration/xml/router/{XPathMultiChannelRouterTests.java => XPathRouterTests.java} (59%) delete mode 100644 spring-integration-xml/src/test/java/org/springframework/integration/xml/router/XPathSingleChannelRouterTests.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java index 3c4c3f7c33..0d274480b6 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java @@ -20,9 +20,11 @@ import java.util.List; import org.springframework.beans.factory.BeanCreationException; 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; import org.w3c.dom.Element; @@ -38,6 +40,10 @@ public abstract class AbstractChannelNameResolvingRouterParser extends AbstractR 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) { @@ -48,12 +54,9 @@ public abstract class AbstractChannelNameResolvingRouterParser extends AbstractR if (beanClassName.endsWith("PayloadTypeRouter")){ key = childElement.getAttribute("type"); } - else if (beanClassName.endsWith("HeaderValueRouter")){ + else { key = childElement.getAttribute("value"); } - else { - throw new BeanCreationException("Building '" + beanClassName + "' is not supported by this parser"); - } channelMap.put(key, childElement.getAttribute("channel")); } beanDefinition.getPropertyValues().add("channelIdentifierMap", channelMap); 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 95f5a7de6a..372f934501 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,84 +16,48 @@ package org.springframework.integration.xml.config; -import java.util.List; - -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; - -import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.ManagedMap; 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.AbstractChannelNameResolvingRouterParser; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; +import org.w3c.dom.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 AbstractChannelNameResolvingRouterParser { 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); - - - List childElements = DomUtils.getChildElementsByTagName(element, "mapping"); - if (childElements != null && childElements.size() > 0) { - ManagedMap channelMap = new ManagedMap(); - for (Element childElement : childElements) { - String key = childElement.getAttribute("value"); - channelMap.put(key, childElement.getAttribute("channel")); - } - builder.addPropertyValue("channelIdentifierMap", channelMap); - } - - 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/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 73% 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 4bc897c5ac..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.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 AbstractMessageRouter { +public class XPathRouter extends AbstractMessageRouter { + + private volatile NodeMapper nodeMapper = new TextContentNodeMapper(); private final XPathExpression xPathExpression; @@ -45,7 +53,7 @@ public abstract class AbstractXPathRouter extends AbstractMessageRouter { * @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 AbstractMessageRouter { * @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 AbstractMessageRouter { * * @param expression */ - public AbstractXPathRouter(String expression) { + public XPathRouter(String expression) { this.xPathExpression = XPathExpressionFactory.createXPathExpression(expression); } @@ -78,7 +86,7 @@ public abstract class AbstractXPathRouter extends AbstractMessageRouter { * * @param expression */ - public AbstractXPathRouter(XPathExpression expression) { + public XPathRouter(XPathExpression expression) { this.xPathExpression = expression; } @@ -103,4 +111,20 @@ public abstract class AbstractXPathRouter extends AbstractMessageRouter { 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/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 438f986123..dde312d875 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 @@ -387,7 +387,6 @@ - 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 18abbb4381..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 @@ -43,6 +43,7 @@ import org.w3c.dom.Document; /** * @author Jonas Partner * @author Mark Fisher + * @author Oleg Zhurakousky */ @ContextConfiguration public class XPathRouterParserTests { 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 index f9ecfa7233..a6635ee746 100644 --- 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 @@ -17,7 +17,7 @@ - + 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); - } - -} From 697a939370a8ece2d3094f165bde591278a16912 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 14 Oct 2010 07:04:10 -0400 Subject: [PATCH 55/58] INT-1377 merged AbstractRouterParser with AbstractChannelNameResolvingRouterParser, removed AbstractChannelNameResolvingRouterParser, now AbstractRouterParser is the base class for all routers --- ...tractChannelNameResolvingRouterParser.java | 70 ------------------- .../config/xml/AbstractRouterParser.java | 36 +++++++++- .../config/xml/HeaderValueRouterParser.java | 2 +- .../config/xml/PayloadTypeRouterParser.java | 2 +- .../xml/config/XPathRouterParser.java | 4 +- 5 files changed, 39 insertions(+), 75 deletions(-) delete mode 100644 spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java deleted file mode 100644 index 0d274480b6..0000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/AbstractChannelNameResolvingRouterParser.java +++ /dev/null @@ -1,70 +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.config.xml; - -import java.util.List; - -import org.springframework.beans.factory.BeanCreationException; -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; -import org.w3c.dom.Element; - -/** - * Base parser for routers that create instances that are subclasses of AbstractChannelNameResolvingMessageRouter. - * - * @author Mark Fisher - * @author Oleg Zhurakousky - */ -public abstract class AbstractChannelNameResolvingRouterParser extends AbstractRouterParser { - - @Override - 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/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/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 38167dc759..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 @@ -28,7 +28,7 @@ import org.w3c.dom.Element; * @author Mark Fisher * @since 1.0.3 */ -public class PayloadTypeRouterParser extends AbstractChannelNameResolvingRouterParser { +public class PayloadTypeRouterParser extends AbstractRouterParser { @Override protected BeanDefinition doParseRouter(Element element, 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 372f934501..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 @@ -19,7 +19,7 @@ package org.springframework.integration.xml.config; 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.AbstractChannelNameResolvingRouterParser; +import org.springframework.integration.config.xml.AbstractRouterParser; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.w3c.dom.Element; @@ -32,7 +32,7 @@ import org.w3c.dom.NodeList; * @author Mark Fisher * @author Oleg Zhurakousky */ -public class XPathRouterParser extends AbstractChannelNameResolvingRouterParser { +public class XPathRouterParser extends AbstractRouterParser { private XPathExpressionParser xpathParser = new XPathExpressionParser(); From 8b219a6a9feceb49c77de2aff44af06560b75f5c Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 14 Oct 2010 10:31:01 -0400 Subject: [PATCH 56/58] polished several POMs to make then consistent with others --- spring-integration-ftp/pom.xml | 7 +------ spring-integration-groovy/pom.xml | 4 +--- spring-integration-sftp/pom.xml | 7 +------ spring-integration-twitter/pom.xml | 7 +------ spring-integration-xmpp/pom.xml | 7 +------ 5 files changed, 5 insertions(+), 27 deletions(-) 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-groovy/pom.xml b/spring-integration-groovy/pom.xml index 03f33d398c..8be1f46cc4 100644 --- a/spring-integration-groovy/pom.xml +++ b/spring-integration-groovy/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-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-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-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-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 From e738d34535d9993d812014f8caf91d44fe76ad04 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 14 Oct 2010 11:41:08 -0400 Subject: [PATCH 57/58] INT-1519 removed XmlValidator and dependency on it in favor of XmlValidator startegy in Spring --- .../xml/router/SchemaValidator.java | 52 ----------------- .../integration/xml/router/XmlValidator.java | 25 -------- .../xml/router/SchemaValidatorTests.java | 57 ------------------- 3 files changed, 134 deletions(-) delete mode 100644 spring-integration-xml/src/main/java/org/springframework/integration/xml/router/SchemaValidator.java delete mode 100644 spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XmlValidator.java delete mode 100644 spring-integration-xml/src/test/java/org/springframework/integration/xml/router/SchemaValidatorTests.java 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/XmlValidator.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XmlValidator.java deleted file mode 100644 index b32ddb8816..0000000000 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/router/XmlValidator.java +++ /dev/null @@ -1,25 +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 javax.xml.transform.Source; - -public interface XmlValidator { - - public boolean isValid(Source source) ; - -} 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)) ; - } - - -} From fc515930010ded8b1d6fb106a0a2e13ee94c2ae3 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 14 Oct 2010 14:12:54 -0400 Subject: [PATCH 58/58] INT-1519, INT-957, INT-1515, Added support for propagating XML Validation exceptions with MessageRejectedException, added support for 'xml-validator' attribute, change XmlValidatingMessageSelector to be bootstrapped with either Resource/Schema or XmlValidator --- .../integration/filter/MessageFilter.java | 18 +++++-- ...gregatedXmlMessageValidationException.java | 29 +++++++++++ .../XmlPayloadValidatingFilterParser.java | 49 ++++++++++++++----- ...java => XmlValidatingMessageSelector.java} | 35 ++++++++----- .../xml/config/spring-integration-xml-2.0.xsd | 30 ++++++++++-- ...oadValidatingFilterParserTests-context.xml | 22 ++++++++- ...XmlPayloadValidatingFilterParserTests.java | 41 ++++++++++++++-- 7 files changed, 186 insertions(+), 38 deletions(-) create mode 100644 spring-integration-xml/src/main/java/org/springframework/integration/xml/AggregatedXmlMessageValidationException.java rename spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/{SchemaValidatingMessageSelector.java => XmlValidatingMessageSelector.java} (62%) 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 0e4ed71c07..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, "MessageFilter '" + this.getComponentName() + "' rejected 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-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/XmlPayloadValidatingFilterParser.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/config/XmlPayloadValidatingFilterParser.java index f688a4c1b8..7ffa7deb52 100644 --- 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 @@ -16,10 +16,12 @@ 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; /** @@ -28,19 +30,15 @@ import org.w3c.dom.Element; */ public class XmlPayloadValidatingFilterParser extends AbstractConsumerEndpointParser { private static String SELECTOR = - "org.springframework.integration.xml.selector.SchemaValidatingMessageSelector"; + "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"; - @Override - protected boolean shouldGenerateId() { - return true; - } - - @Override - protected boolean shouldGenerateIdAsFallback() { - return true; - } + /** 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) { @@ -49,11 +47,36 @@ public class XmlPayloadValidatingFilterParser extends AbstractConsumerEndpointPa IntegrationNamespaceUtils.setReferenceIfAttributeDefined(filterBuilder, element, "discard-channel"); IntegrationNamespaceUtils.setValueIfAttributeDefined(filterBuilder, element, "throw-exception-on-rejection"); - BeanDefinitionBuilder selectorBuilder = BeanDefinitionBuilder.genericBeanDefinition(SELECTOR); - selectorBuilder.addConstructorArgValue(element.getAttribute("schema-location")); - selectorBuilder.addPropertyValue("schemaType", element.getAttribute("schema-type")); + 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/selector/SchemaValidatingMessageSelector.java b/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelector.java similarity index 62% rename from spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/SchemaValidatingMessageSelector.java rename to spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelector.java index e4c82c0857..0d0b3e9900 100644 --- a/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/SchemaValidatingMessageSelector.java +++ b/spring-integration-xml/src/main/java/org/springframework/integration/xml/selector/XmlValidatingMessageSelector.java @@ -20,9 +20,12 @@ 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; @@ -32,19 +35,28 @@ import org.xml.sax.SAXParseException; * @since 2.0 * */ -public class SchemaValidatingMessageSelector implements MessageSelector{ +public class XmlValidatingMessageSelector implements MessageSelector { private final XmlValidator xmlValidator; - private volatile String schemaType = XmlValidatorFactory.SCHEMA_W3C_XML; + private volatile boolean throwExceptionOnRejection; private volatile XmlPayloadConverter converter = new DefaultXmlPayloadConverter(); - public SchemaValidatingMessageSelector(Resource schema) throws Exception{ + 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 * @@ -54,19 +66,18 @@ public class SchemaValidatingMessageSelector implements MessageSelector{ this.converter = converter; } - public void setSchemaType(String schemaType) { - this.schemaType = schemaType; - } - + @SuppressWarnings("unchecked") public boolean accept(Message message) { - // TODO Need to figure out how the exceptions could be propagated since the return from this method is true/false - // and 'throw-exception-on-rejection'is actually set on the filter + SAXParseException[] validationExceptions = null; try { - SAXParseException[] validationExceptions = xmlValidator.validate(converter.convertToSource(message.getPayload())); - return validationExceptions.length == 0 ? true : false; + validationExceptions = xmlValidator.validate(converter.convertToSource(message.getPayload())); } catch (Exception e) { - e.printStackTrace(); 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 dde312d875..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 @@ -513,7 +513,7 @@ - Defines a validating filter. + Defines an XML validating filter. @@ -529,9 +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. + + + + + + + + + @@ -540,6 +563,7 @@ + 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 index 30e11e3ae2..aaa7df5eb3 100644 --- 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 @@ -8,11 +8,29 @@ http://www.springframework.org/schema/integration/xml http://www.springframework.org/schema/integration/xml/spring-integration-xml-2.0.xsd"> - + + + + + + + + + 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 index a0f7a58930..42779cecfa 100644 --- 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 @@ -23,6 +23,7 @@ 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; @@ -42,21 +43,53 @@ public class XmlPayloadValidatingFilterParserTests { Document doc = XmlTestUtil.getDocumentForString("hello"); GenericMessage docMessage = new GenericMessage(doc); PollableChannel validChannel = ac.getBean("validOutputChannel", PollableChannel.class); - MessageChannel inputChannel = ac.getBean("inputChannel", MessageChannel.class); + MessageChannel inputChannel = ac.getBean("inputChannelA", MessageChannel.class); inputChannel.send(docMessage); assertNotNull(validChannel.receive(100)); - } @Test - public void testInvalidMessage() throws Exception { + 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("inputChannel", MessageChannel.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)); + } }