From 36795e5ee85ec5d9ae95d3dbd58a8b125492a0c4 Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Wed, 23 Oct 2013 19:34:10 +0300 Subject: [PATCH] INT-3069 Add Global Gateway Method Metadata https://jira.springsource.org/browse/INT-3069 Provide a mechanism to specify headers and payload-expression that can be applied to all methods in the gateway. - Headers defined as 'default' are globally applied to all gateway methods. - Also supports 'default-payload-expression'. - Headers defined on a specific method override the global settings. - An `@Header` in the interface is overridden by a specific
for that method (current behavior) - An `@Header` in the interface is NOT overridden by a - Add 3 new SpEL variables: -- #methodName (synonym for #method - deprecated) -- #methodString (a string representation of the method showing return type and arg types) -- #methodObject (the Method object) INT-3069 Polishing - PR Comments - Remove extra 'method' variables, just provide `gatewayMethod`. - Parser improvements - Schema now enforces default-header elements to precede method elements - Doc polishing --- .gitignore | 1 + .../integration/config/xml/GatewayParser.java | 28 ++++- .../GatewayMethodInboundMessageMapper.java | 38 ++++-- .../gateway/GatewayProxyFactoryBean.java | 16 ++- .../config/xml/spring-integration-3.0.xsd | 25 +++- .../gateway/GatewayInterfaceTests-context.xml | 15 ++- .../gateway/GatewayInterfaceTests.java | 116 +++++++++++++++--- .../GatewayInterfaceTests2-context.xml | 23 ++++ src/reference/docbook/gateway.xml | 44 ++++++- src/reference/docbook/whats-new.xml | 14 +++ 10 files changed, 281 insertions(+), 39 deletions(-) create mode 100644 spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests2-context.xml diff --git a/.gitignore b/.gitignore index 93179d40cb..2950dab738 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ spring-integration-jms/activemq-data/ spring-integration-samples/loanshark/application.log* target vf.gf.dmn-* +/atlassian-ide-plugin.xml diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GatewayParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GatewayParser.java index bdfd72e9e4..dea4f53dac 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GatewayParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/GatewayParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 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. @@ -27,6 +27,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; +import org.springframework.integration.gateway.GatewayProxyFactoryBean; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -34,9 +35,10 @@ import org.springframework.util.xml.DomUtils; /** * Parser for the <gateway/> element. - * + * * @author Mark Fisher * @author Oleg Zhurakousky + * @author Gary Russell */ public class GatewayParser extends AbstractSimpleBeanDefinitionParser { @@ -51,9 +53,10 @@ public class GatewayParser extends AbstractSimpleBeanDefinitionParser { @Override protected String getBeanClassName(Element element) { - return IntegrationNamespaceUtils.BASE_PACKAGE + ".gateway.GatewayProxyFactoryBean"; + return GatewayProxyFactoryBean.class.getName(); } - + + @Override protected boolean shouldGenerateIdAsFallback() { return true; } @@ -62,6 +65,7 @@ public class GatewayParser extends AbstractSimpleBeanDefinitionParser { protected boolean isEligibleAttribute(String attributeName) { return !ObjectUtils.containsElement(referenceAttributes, attributeName) && !ObjectUtils.containsElement(innerAttributes, attributeName) + && !("default-payload-expression".equals(attributeName)) && super.isEligibleAttribute(attributeName); } @@ -79,7 +83,7 @@ public class GatewayParser extends AbstractSimpleBeanDefinitionParser { IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "request-channel", "defaultRequestChannel"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "reply-channel", "defaultReplyChannel"); IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "request-timeout", "defaultRequestTimeout"); - IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout", "defaultReplyTimeout"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout", "defaultReplyTimeout"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "error-channel"); IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "async-executor"); } @@ -88,6 +92,18 @@ public class GatewayParser extends AbstractSimpleBeanDefinitionParser { for (String attributeName : referenceAttributes) { IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, attributeName); } + + List invocationHeaders = DomUtils.getChildElementsByTagName(element, "default-header"); + if (!CollectionUtils.isEmpty(invocationHeaders) + || StringUtils.hasText(element.getAttribute("default-payload-expression"))) { + BeanDefinitionBuilder methodMetadataBuilder = BeanDefinitionBuilder.genericBeanDefinition( + "org.springframework.integration.gateway.GatewayMethodMetadata"); + this.setMethodInvocationHeaders(methodMetadataBuilder, invocationHeaders); + IntegrationNamespaceUtils.setValueIfAttributeDefined(methodMetadataBuilder, element, + "default-payload-expression", "payloadExpression"); + builder.addPropertyValue("globalMethodMetadata", methodMetadataBuilder.getBeanDefinition()); + } + List elements = DomUtils.getChildElementsByTagName(element, "method"); ManagedMap methodMetadataMap = null; if (elements != null && elements.size() > 0) { @@ -102,7 +118,7 @@ public class GatewayParser extends AbstractSimpleBeanDefinitionParser { methodMetadataBuilder.addPropertyValue("requestTimeout", methodElement.getAttribute("request-timeout")); methodMetadataBuilder.addPropertyValue("replyTimeout", methodElement.getAttribute("reply-timeout")); IntegrationNamespaceUtils.setValueIfAttributeDefined(methodMetadataBuilder, methodElement, "payload-expression"); - List invocationHeaders = DomUtils.getChildElementsByTagName(methodElement, "header"); + invocationHeaders = DomUtils.getChildElementsByTagName(methodElement, "header"); if (!CollectionUtils.isEmpty(invocationHeaders)) { this.setMethodInvocationHeaders(methodMetadataBuilder, invocationHeaders); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java index 074cb4c675..7ec9dd3fe3 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapper.java @@ -68,6 +68,7 @@ import org.springframework.util.StringUtils; * @author Mark Fisher * @author Iwein Fuld * @author Oleg Zhurakousky + * @author Gary Russell * @since 2.0 */ class GatewayMethodInboundMessageMapper implements InboundMessageMapper, BeanFactoryAware { @@ -80,6 +81,8 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper headerExpressions; + private final Map globalHeaderExpressions; + private final List parameterList; private volatile Expression payloadExpression; @@ -96,9 +99,15 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper headerExpressions) { + this(method, headerExpressions, null); + } + + public GatewayMethodInboundMessageMapper(Method method, Map headerExpressions, + Map globalHeaderExpressions) { Assert.notNull(method, "method must not be null"); this.method = method; this.headerExpressions = headerExpressions; + this.globalHeaderExpressions = globalHeaderExpressions; this.parameterList = getMethodParameterList(method); this.payloadExpression = parsePayloadExpression(method); } @@ -194,23 +203,38 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper) messageOrPayload) : MessageBuilder.withPayload(messageOrPayload); builder.copyHeadersIfAbsent(headers); + // Explicit headers in XML override any @Header annotations... if (!CollectionUtils.isEmpty(this.headerExpressions)) { - Map evaluatedHeaders = new HashMap(); - for (Map.Entry entry : this.headerExpressions.entrySet()) { - Object value = entry.getValue().getValue(methodInvocationEvaluationContext); - if (value != null) { - evaluatedHeaders.put(entry.getKey(), value); - } - } + Map evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, this.headerExpressions); builder.copyHeaders(evaluatedHeaders); } + // ...whereas global (default) headers do not... + if (!CollectionUtils.isEmpty(this.globalHeaderExpressions)) { + Map evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, this.globalHeaderExpressions); + builder.copyHeadersIfAbsent(evaluatedHeaders); + } return builder.build(); } + private Map evaluateHeaders(EvaluationContext methodInvocationEvaluationContext, Map headerExpressions) { + Map evaluatedHeaders = new HashMap(); + for (Map.Entry entry : headerExpressions.entrySet()) { + Object value = entry.getValue().getValue(methodInvocationEvaluationContext); + if (value != null) { + evaluatedHeaders.put(entry.getKey(), value); + } + } + return evaluatedHeaders; + } + private StandardEvaluationContext createMethodInvocationEvaluationContext(Object[] arguments) { StandardEvaluationContext context = ExpressionUtils.createStandardEvaluationContext(this.beanFactory); context.setVariable("args", arguments); + + // TODO deprecated in 3.0/4.0 - retained for backwards compatibility context.setVariable("method", this.method.getName()); + + context.setVariable("gatewayMethod", this.method); return context; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java index 5a36590be8..a2b7983061 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -28,6 +28,7 @@ import java.util.concurrent.Future; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; + import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.AopUtils; import org.springframework.beans.SimpleTypeConverter; @@ -100,8 +101,9 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab private final Object initializationMonitor = new Object(); - private Map methodMetadataMap; + private volatile Map methodMetadataMap; + private volatile GatewayMethodMetadata globalMethodMetadata; /** * Create a Factory whose service interface type can be configured by setter injection. @@ -204,6 +206,10 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab this.methodMetadataMap = methodMetadataMap; } + public void setGlobalMethodMetadata(GatewayMethodMetadata globalMethodMetadata) { + this.globalMethodMetadata = globalMethodMetadata; + } + public void setBeanClassLoader(ClassLoader beanClassLoader) { this.beanClassLoader = beanClassLoader; } @@ -339,7 +345,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab MessageChannel replyChannel = this.defaultReplyChannel; Long requestTimeout = this.defaultRequestTimeout; Long replyTimeout = this.defaultReplyTimeout; - String payloadExpression = null; + String payloadExpression = this.globalMethodMetadata != null ? this.globalMethodMetadata.getPayloadExpression() + : null; Map headerExpressions = null; if (gatewayAnnotation != null) { String requestChannelName = gatewayAnnotation.requestChannel(); @@ -387,7 +394,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab } } } - GatewayMethodInboundMessageMapper messageMapper = new GatewayMethodInboundMessageMapper(method, headerExpressions); + GatewayMethodInboundMessageMapper messageMapper = new GatewayMethodInboundMessageMapper(method, headerExpressions, + this.globalMethodMetadata != null ? this.globalMethodMetadata.getHeaderExpressions() : null); if (StringUtils.hasText(payloadExpression)) { messageMapper.setPayloadExpression(payloadExpression); } diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd index ed596cf9e7..2cd4802b3e 100644 --- a/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd +++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/xml/spring-integration-3.0.xsd @@ -515,7 +515,17 @@ - + + + + + + + + @@ -530,7 +540,7 @@ @@ -621,6 +631,17 @@ + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests-context.xml index 217e18539a..788bd10290 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests-context.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests-context.xml @@ -5,10 +5,17 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd"> - - + + + + + + + + + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java index 2230838492..69242b4597 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java @@ -16,13 +16,18 @@ package org.springframework.integration.gateway; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicBoolean; + import org.junit.Test; import org.mockito.Mockito; @@ -30,29 +35,71 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.integration.Message; +import org.springframework.integration.MessagingException; import org.springframework.integration.annotation.Gateway; +import org.springframework.integration.annotation.Header; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.core.MessageHandler; /** * @author Oleg Zhurakousky * @author Gunnar Hillert + * @author Gary Russell */ public class GatewayInterfaceTests { @Test - public void testWithServiceSuperclassAnnotatedMethod(){ + public void testWithServiceSuperclassAnnotatedMethod() throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); DirectChannel channel = ac.getBean("requestChannelFoo", DirectChannel.class); - MessageHandler handler = mock(MessageHandler.class); + final Method fooMethod = Foo.class.getMethod("foo", String.class); + final AtomicBoolean called = new AtomicBoolean(); + MessageHandler handler = new MessageHandler() { + + @Override + public void handleMessage(Message message) throws MessagingException { + assertThat((String) message.getHeaders().get("name"), equalTo("foo")); + assertThat( + (String) message.getHeaders().get("string"), + equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Foo.foo(java.lang.String)")); + assertThat((Method) message.getHeaders().get("object"), equalTo(fooMethod)); + assertThat((String) message.getPayload(), equalTo("hello")); + called.set(true); + } + }; channel.subscribe(handler); Bar bar = ac.getBean(Bar.class); bar.foo("hello"); - verify(handler, times(1)).handleMessage(Mockito.any(Message.class)); + assertTrue(called.get()); } @Test - public void testWithServiceAnnotatedMethod(){ + public void testWithServiceSuperclassAnnotatedMethodOverridePE() throws Exception { + ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests2-context.xml", this.getClass()); + DirectChannel channel = ac.getBean("requestChannelFoo", DirectChannel.class); + final Method fooMethod = Foo.class.getMethod("foo", String.class); + final AtomicBoolean called = new AtomicBoolean(); + MessageHandler handler = new MessageHandler() { + + @Override + public void handleMessage(Message message) throws MessagingException { + assertThat((String) message.getHeaders().get("name"), equalTo("foo")); + assertThat( + (String) message.getHeaders().get("string"), + equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Foo.foo(java.lang.String)")); + assertThat((Method) message.getHeaders().get("object"), equalTo(fooMethod)); + assertThat((String) message.getPayload(), equalTo("foo")); + called.set(true); + } + }; + channel.subscribe(handler); + Bar bar = ac.getBean(Bar.class); + bar.foo("hello"); + assertTrue(called.get()); + } + + @Test + public void testWithServiceAnnotatedMethod() { ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); DirectChannel channel = ac.getBean("requestChannelBar", DirectChannel.class); MessageHandler handler = mock(MessageHandler.class); @@ -63,18 +110,57 @@ public class GatewayInterfaceTests { } @Test - public void testWithServiceSuperclassUnAnnotatedMethod(){ + public void testWithServiceSuperclassUnAnnotatedMethod() throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); - MessageHandler handler = mock(MessageHandler.class); + final Method bazMethod = Foo.class.getMethod("baz", String.class); + final AtomicBoolean called = new AtomicBoolean(); + MessageHandler handler = new MessageHandler() { + + @Override + public void handleMessage(Message message) throws MessagingException { + assertThat((String) message.getHeaders().get("name"), equalTo("overrideGlobal")); + assertThat( + (String) message.getHeaders().get("string"), + equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Foo.baz(java.lang.String)")); + assertThat((Method) message.getHeaders().get("object"), equalTo(bazMethod)); + assertThat((String) message.getPayload(), equalTo("hello")); + called.set(true); + } + }; channel.subscribe(handler); Bar bar = ac.getBean(Bar.class); bar.baz("hello"); - verify(handler, times(1)).handleMessage(Mockito.any(Message.class)); + assertTrue(called.get()); } @Test - public void testWithServiceCastAsSuperclassAnnotatedMethod(){ + public void testWithServiceUnAnnotatedMethodGlobalHeaderDoesntOverride() throws Exception { + ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); + DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); + final Method quxMethod = Bar.class.getMethod("qux", String.class, String.class); + final AtomicBoolean called = new AtomicBoolean(); + MessageHandler handler = new MessageHandler() { + + @Override + public void handleMessage(Message message) throws MessagingException { + assertThat((String) message.getHeaders().get("name"), equalTo("arg1")); + assertThat( + (String) message.getHeaders().get("string"), + equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Bar.qux(java.lang.String,java.lang.String)")); + assertThat((Method) message.getHeaders().get("object"), equalTo(quxMethod)); + assertThat((String) message.getPayload(), equalTo("hello")); + called.set(true); + } + }; + channel.subscribe(handler); + Bar bar = ac.getBean(Bar.class); + bar.qux("hello", "arg1"); + assertTrue(called.get()); + } + + @Test + public void testWithServiceCastAsSuperclassAnnotatedMethod() { ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); DirectChannel channel = ac.getBean("requestChannelFoo", DirectChannel.class); MessageHandler handler = mock(MessageHandler.class); @@ -85,7 +171,7 @@ public class GatewayInterfaceTests { } @Test - public void testWithServiceCastAsSuperclassUnAnnotatedMethod(){ + public void testWithServiceCastAsSuperclassUnAnnotatedMethod() { ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); MessageHandler handler = mock(MessageHandler.class); @@ -96,7 +182,7 @@ public class GatewayInterfaceTests { } @Test - public void testWithServiceHashcode() throws Exception{ + public void testWithServiceHashcode() throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); MessageHandler handler = mock(MessageHandler.class); @@ -107,7 +193,7 @@ public class GatewayInterfaceTests { } @Test - public void testWithServiceToString(){ + public void testWithServiceToString() { ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); MessageHandler handler = mock(MessageHandler.class); @@ -118,7 +204,7 @@ public class GatewayInterfaceTests { } @Test - public void testWithServiceEquals() throws Exception{ + public void testWithServiceEquals() throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); MessageHandler handler = mock(MessageHandler.class); @@ -137,7 +223,7 @@ public class GatewayInterfaceTests { } @Test - public void testWithServiceGetClass(){ + public void testWithServiceGetClass() { ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); MessageHandler handler = mock(MessageHandler.class); @@ -160,9 +246,11 @@ public class GatewayInterfaceTests { public void baz(String payload); } - public static interface Bar extends Foo{ + public static interface Bar extends Foo { @Gateway(requestChannel="requestChannelBar") public void bar(String payload); + + public void qux(String payload, @Header("name") String nameHeader); } public static class NotAnInterface { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests2-context.xml b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests2-context.xml new file mode 100644 index 0000000000..96e1740401 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests2-context.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + diff --git a/src/reference/docbook/gateway.xml b/src/reference/docbook/gateway.xml index f4ed93d69f..e0f12db1be 100644 --- a/src/reference/docbook/gateway.xml +++ b/src/reference/docbook/gateway.xml @@ -116,9 +116,10 @@ public interface Cafe { + - - + + ]]> @@ -145,6 +146,45 @@ public interface Cafe { In the above case you can clearly see how a different value will be set for the 'RESPONSE_TYPE' header based on the gateway's method. + Expressions and "Global" Headers + + The <header/> element supports expression as an alternative to + value. The SpEL expression is evaluated to determine the value of the header. There is no + #root object but the following variables are available: + + + #args - an Object[] containing the method arguments + + + #gatewayMethod - the java.reflect.Method object representing the method in the + service-interface that was invoked. A header containing this variable can be used + later in the flow, for example, for routing. For example, if you wish to route on the simple method + name, you might add a header, with expression #gatewayMethod.name. + + The java.reflect.Method is not serializable; a header with expression + #gatewayMethod will be lost if you later serialize the message. So, you may wish + to use #gatewayMethod.name or #gatewayMethod.toString() in those cases; + the toString() method provides a String representation of the method, including + parameter and return types. + + + Prior to 3.0, the #method variable was available, representing the method name only. + This is still available, but deprecated; use #gatewayMethod.name instead. + + + + + + Since 3.0, <default-header/>s can be defined to add headers to all messages produced + by the gateway, regardless of the method invoked. Specific headers defined for a method take precedence + over default headers. Specific headers defined for a method here will override any @Header annotations + in the service interface. However, default headers will NOT override any @Header annotations + in the service interface. + + + The gateway now also supports a default-payload-expression which will be applied for all methods + (unless overridden). +
diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index e14ed1ffd3..6d3f2ab7b4 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -155,6 +155,20 @@
General Changes +
+ <gateway> Changes + + + + It is now possible to set common headers across all gateway methods, and more options + are provided for adding, to the message, information about which method was invoked. + + + + + For more information see . + +
Aggregator 'empty-group-min-timeout' property AbstractCorrelatingMessageHandler provides a new property