From 8d49507cb06a33227349a6581d0858c636b3dc82 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Wed, 28 Oct 2009 20:46:54 +0000 Subject: [PATCH] INT-809 Added namespace support for MessagePublishigInterceptor --- .../xml/IntegrationNamespaceHandler.java | 1 + .../config/xml/PublisherParser.java | 122 ++++++++++++++++++ .../config/xml/spring-integration-2.0.xsd | 29 +++++ ...blishingInterceptorParserTests-context.xml | 34 +++++ ...ssagePublishingInterceptorParserTests.java | 113 ++++++++++++++++ 5 files changed, 299 insertions(+) create mode 100644 org.springframework.integration/src/main/java/org/springframework/integration/config/xml/PublisherParser.java create mode 100644 org.springframework.integration/src/test/java/org/springframework/integration/config/xml/MessagePublishingInterceptorParserTests-context.xml create mode 100644 org.springframework.integration/src/test/java/org/springframework/integration/config/xml/MessagePublishingInterceptorParserTests.java diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceHandler.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceHandler.java index 8a826e9ecd..3b88fe32dc 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceHandler.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/IntegrationNamespaceHandler.java @@ -56,6 +56,7 @@ public class IntegrationNamespaceHandler extends AbstractIntegrationNamespaceHan registerBeanDefinitionParser("annotation-config", new AnnotationConfigParser()); registerBeanDefinitionParser("application-event-multicaster", new ApplicationEventMulticasterParser()); registerBeanDefinitionParser("thread-pool-task-executor", new ThreadPoolTaskExecutorParser()); + registerBeanDefinitionParser("publisher", new PublisherParser()); } } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/PublisherParser.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/PublisherParser.java new file mode 100644 index 0000000000..1a66fe1564 --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/xml/PublisherParser.java @@ -0,0 +1,122 @@ +/* + * 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.config.xml; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.ManagedMap; +import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.aop.MethodNameMappingExpressionSource; +import org.springframework.integration.channel.ChannelResolver; +import org.springframework.integration.channel.MapBasedChannelResolver; +import org.springframework.integration.context.IntegrationContextUtils; +import org.springframework.integration.core.MessageChannel; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +/** + * @author Oleg Zhurakousky + * @since 2.0 + */ +public class PublisherParser extends AbstractBeanDefinitionParser { + + /** + * + */ + protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { + BeanDefinitionBuilder rootBuilder = BeanDefinitionBuilder.genericBeanDefinition( + IntegrationNamespaceUtils.BASE_PACKAGE + ".aop.MessagePublishingInterceptor"); + + BeanDefinitionBuilder spelSourceBilder = BeanDefinitionBuilder.genericBeanDefinition(MethodNameMappingExpressionSource.class.getName()); + Map> mappings = this.getMappings(element, element.getAttribute("default-channel")); + spelSourceBilder.addConstructorArgValue(mappings.get("payload")); + MethodNameMappingExpressionSource m = null; + if (mappings.get("headers") != null){ + spelSourceBilder.addPropertyValue("headerExpressionMap", mappings.get("headers")); + } + BeanDefinitionBuilder chResolverBuilder = BeanDefinitionBuilder.genericBeanDefinition(MapBasedChannelResolver.class.getName()); + if (mappings.get("channels") != null){ + spelSourceBilder.addPropertyValue("channelMap", mappings.get("channels")); + chResolverBuilder.addConstructorArgValue(mappings.get("resolvableChannels")); + } + String chResolverName = + BeanDefinitionReaderUtils.registerWithGeneratedName(chResolverBuilder.getBeanDefinition(), parserContext.getRegistry()); + String defaultChannel = StringUtils.hasText(element.getAttribute("default-channel")) ? + element.getAttribute("default-channel") : IntegrationContextUtils.NULL_CHANNEL_BEAN_NAME; + + String spelSourceName = + BeanDefinitionReaderUtils.registerWithGeneratedName(spelSourceBilder.getBeanDefinition(), parserContext.getRegistry()); + rootBuilder.addConstructorArgReference(spelSourceName); + + rootBuilder.addPropertyReference("channelResolver", chResolverName); + + rootBuilder.addPropertyReference("defaultChannel", defaultChannel); + + return rootBuilder.getBeanDefinition(); + } + + @SuppressWarnings("unchecked") + private Map> getMappings(Element element, String defaultChannel){ + List mappings = DomUtils.getChildElementsByTagName(element, "method"); + Map> interceptorMappings = new HashMap>(); + Map payloadExpressionMap = new HashMap(); + Map headersExpressionMap = new HashMap(); + Map channelMap = new HashMap(); + ManagedMap resolvableChannelMap = new ManagedMap(); + if (mappings != null && mappings.size() > 0){ + for (Element mapping : mappings) { + // set payloadMap + String methodPattern = StringUtils.hasText(mapping.getAttribute("pattern")) ? + mapping.getAttribute("pattern") : "*" ; + String payloadExpression = StringUtils.hasText(mapping.getAttribute("payload")) ? + mapping.getAttribute("payload") : "#return" ; + payloadExpressionMap.put(methodPattern, payloadExpression); + // set headersMap + String headersExpression = mapping.getAttribute("headers"); + if (StringUtils.hasText(headersExpression)){ + headersExpressionMap.put(methodPattern, StringUtils.commaDelimitedListToStringArray(headersExpression)); + } + // set channelMap + String tmpChannel = mapping.getAttribute("channel"); + String channel = StringUtils.hasText(tmpChannel) ? tmpChannel : defaultChannel; + channelMap.put(methodPattern, channel); + resolvableChannelMap.put(channel, new RuntimeBeanReference(channel)); + } + } + + if (payloadExpressionMap.size() == 0){ + payloadExpressionMap.put("*", "#return"); + } + interceptorMappings.put("payload", payloadExpressionMap); + + if (headersExpressionMap.size() > 0){ + interceptorMappings.put("headers", headersExpressionMap); + } + if (channelMap.size() > 0){ + interceptorMappings.put("channels", channelMap); + interceptorMappings.put("resolvableChannels", resolvableChannelMap); + } + return interceptorMappings; + } +} diff --git a/org.springframework.integration/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd b/org.springframework.integration/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd index 51a975505f..f0b1fb04af 100644 --- a/org.springframework.integration/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd +++ b/org.springframework.integration/src/main/resources/org/springframework/integration/config/xml/spring-integration-2.0.xsd @@ -1476,5 +1476,34 @@ + + + + + Defines a MessagePublishingInterceptor which allows you to generate messages as a by-product of + method invocations on Spring configured components. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/xml/MessagePublishingInterceptorParserTests-context.xml b/org.springframework.integration/src/test/java/org/springframework/integration/config/xml/MessagePublishingInterceptorParserTests-context.xml new file mode 100644 index 0000000000..da17d8601c --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/xml/MessagePublishingInterceptorParserTests-context.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/xml/MessagePublishingInterceptorParserTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/xml/MessagePublishingInterceptorParserTests.java new file mode 100644 index 0000000000..f3a300ec75 --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/xml/MessagePublishingInterceptorParserTests.java @@ -0,0 +1,113 @@ +/* + * 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.config.xml; + +import static junit.framework.Assert.assertEquals; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.integration.channel.SubscribableChannel; +import org.springframework.integration.core.Message; +import org.springframework.integration.message.MessageHandler; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Oleg Zhurakousky + * @since 2.0 + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +public class MessagePublishingInterceptorParserTests { + + @Autowired + private TestBean testBean; + @Autowired + private DefaultTestBean defaultTestBean; + @Autowired + @Qualifier("defaultChannel") + private SubscribableChannel defaultChannel; + @Autowired + @Qualifier("echoChannel") + private SubscribableChannel echoChannel; + + @SuppressWarnings("unchecked") + @Test + public void validateDefaultChannelPublishing(){ + MessageHandler handler = Mockito.mock(MessageHandler.class); + defaultChannel.subscribe(handler); + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) { + Message message = (Message) invocation.getArguments()[0]; + assertEquals("hello",message.getPayload()); + return null; + }}) + .when(handler).handleMessage((Message) anyObject()); + + testBean.echoDefaultChannel("hello"); + verify(handler, times(1)).handleMessage((Message) anyObject()); + } + @SuppressWarnings("unchecked") + @Test + public void validateEchoChannelPublishing(){ + MessageHandler handler = Mockito.mock(MessageHandler.class); + echoChannel.subscribe(handler); + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) { + Message message = (Message) invocation.getArguments()[0]; + assertEquals("bar", message.getHeaders().get("foo")); + assertEquals("Echoing: hello", message.getPayload()); + return null; + }}) + .when(handler).handleMessage((Message) anyObject()); + + testBean.echo("hello"); + verify(handler, times(1)).handleMessage((Message) anyObject()); + } + /** + * Need to set 'debug' level + */ + @Test + public void validateNullChannelPublishing(){ + defaultTestBean.echo("hello"); + } + + public static class TestBean{ + public String echo(String str){ + return str; + } + public String echoUpperCase(String str){ + return str.toUpperCase(); + } + public String echoDefaultChannel(String str){ + return str; + } + } + public static class DefaultTestBean{ + public String echo(String str){ + return str; + } + } +}