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 <header/> for that method (current behavior)
- An `@Header` in the interface is NOT overridden by a  <default-header/>
- 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:gateway id="sampleGateway"
			service-interface="org.springframework.integration.gateway.GatewayInterfaceTests.Bar"
			default-request-channel="requestChannelBaz">
		<int:default-header name="name" expression="#methodName"/>
		<int:default-header name="string" expression="#methodString"/>
		<int:default-header name="object" expression="#methodObject"/>
		<int:method name="baz">
			<int:header name="name" value="overrideGlobal"/>
		</int:method>
	</int:gateway>

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
This commit is contained in:
Gary Russell
2013-10-23 19:34:10 +03:00
committed by Artem Bilan
parent 6d9e48b9ac
commit 36795e5ee8
10 changed files with 281 additions and 39 deletions

1
.gitignore vendored
View File

@@ -26,3 +26,4 @@ spring-integration-jms/activemq-data/
spring-integration-samples/loanshark/application.log*
target
vf.gf.dmn-*
/atlassian-ide-plugin.xml

View File

@@ -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 &lt;gateway/&gt; 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<Element> 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<Element> elements = DomUtils.getChildElementsByTagName(element, "method");
ManagedMap<String, BeanDefinition> 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<Element> invocationHeaders = DomUtils.getChildElementsByTagName(methodElement, "header");
invocationHeaders = DomUtils.getChildElementsByTagName(methodElement, "header");
if (!CollectionUtils.isEmpty(invocationHeaders)) {
this.setMethodInvocationHeaders(methodMetadataBuilder, invocationHeaders);
}

View File

@@ -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<Object[]>, BeanFactoryAware {
@@ -80,6 +81,8 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
private final Map<String, Expression> headerExpressions;
private final Map<String, Expression> globalHeaderExpressions;
private final List<MethodParameter> parameterList;
private volatile Expression payloadExpression;
@@ -96,9 +99,15 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
}
public GatewayMethodInboundMessageMapper(Method method, Map<String, Expression> headerExpressions) {
this(method, headerExpressions, null);
}
public GatewayMethodInboundMessageMapper(Method method, Map<String, Expression> headerExpressions,
Map<String, Expression> 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<Object[]
? MessageBuilder.fromMessage((Message<?>) messageOrPayload)
: MessageBuilder.withPayload(messageOrPayload);
builder.copyHeadersIfAbsent(headers);
// Explicit headers in XML override any @Header annotations...
if (!CollectionUtils.isEmpty(this.headerExpressions)) {
Map<String, Object> evaluatedHeaders = new HashMap<String, Object>();
for (Map.Entry<String, Expression> entry : this.headerExpressions.entrySet()) {
Object value = entry.getValue().getValue(methodInvocationEvaluationContext);
if (value != null) {
evaluatedHeaders.put(entry.getKey(), value);
}
}
Map<String, Object> evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, this.headerExpressions);
builder.copyHeaders(evaluatedHeaders);
}
// ...whereas global (default) headers do not...
if (!CollectionUtils.isEmpty(this.globalHeaderExpressions)) {
Map<String, Object> evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, this.globalHeaderExpressions);
builder.copyHeadersIfAbsent(evaluatedHeaders);
}
return builder.build();
}
private Map<String, Object> evaluateHeaders(EvaluationContext methodInvocationEvaluationContext, Map<String, Expression> headerExpressions) {
Map<String, Object> evaluatedHeaders = new HashMap<String, Object>();
for (Map.Entry<String, Expression> 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;
}

View File

@@ -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<String, GatewayMethodMetadata> methodMetadataMap;
private volatile Map<String, GatewayMethodMetadata> 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<String, Expression> 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);
}

View File

@@ -515,7 +515,17 @@
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:sequence minOccurs="0" maxOccurs="1">
<xsd:element name="default-header" minOccurs="0" maxOccurs="unbounded" type="headerSubElementType">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Provides a mechanism to enrich the message with custom message headers. These default headers are created for
all methods on the service-interface (unless overridden by a specific method element).
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="method" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
@@ -530,7 +540,7 @@
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Provides mechanism to enrich content of the message with custom message headers. When this method is going to be invoked
Provides a mechanism to enrich the message with custom message headers. When this method is invoked,
the generated message will be enriched with these headers.
]]>
</xsd:documentation>
@@ -621,6 +631,17 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="default-payload-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
An expression that will be used to generate the payload for all methods in the service interface
unless explicitly overriden by a method declaration. Variables include #args, #methodName, #methodString
and #methodObject; a bean resolver is also available, enabling expressions like "@someBean(#args)".
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="default-request-channel" type="xsd:string">
<xsd:annotation>
<xsd:documentation>

View File

@@ -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">
<int:gateway id="sampleGateway"
service-interface="org.springframework.integration.gateway.GatewayInterfaceTests.Bar"
default-request-channel="requestChannelBaz"/>
<int:gateway id="sampleGateway"
service-interface="org.springframework.integration.gateway.GatewayInterfaceTests.Bar"
default-request-channel="requestChannelBaz">
<int:default-header name="name" expression="#gatewayMethod.name"/>
<int:default-header name="string" expression="#gatewayMethod.toString()"/>
<int:default-header name="object" expression="#gatewayMethod"/>
<int:method name="baz">
<int:header name="name" value="overrideGlobal"/>
</int:method>
</int:gateway>
<int:channel id="requestChannelFoo"/>
<int:channel id="requestChannelBar"/>
<int:channel id="requestChannelBaz"/>

View File

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

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
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">
<int:gateway id="sampleGateway"
service-interface="org.springframework.integration.gateway.GatewayInterfaceTests.Bar"
default-request-channel="requestChannelBaz" default-payload-expression="'foo'">
<int:default-header name="name" expression="#gatewayMethod.name"/>
<int:default-header name="string" expression="#gatewayMethod.toString()"/>
<int:default-header name="object" expression="#gatewayMethod"/>
<int:method name="baz">
<int:header name="name" value="overrideGlobal"/>
</int:method>
</int:gateway>
<int:channel id="requestChannelFoo"/>
<int:channel id="requestChannelBar"/>
<int:channel id="requestChannelBaz"/>
</beans>

View File

@@ -116,9 +116,10 @@ public interface Cafe {
<programlisting language="xml"><![CDATA[<int:gateway id="myGateway" service-interface="org.foo.bar.TestGateway"
default-request-channel="inputC">
<int:default-header name="calledMethod" expression="#gatewayMethod.name"/>
<int:method name="echo" request-channel="inputA" reply-timeout="2" request-timeout="200"/>
<int:method name="echoUpperCase" request-channel="inputB"/>
<int:method name="echoViaDefault"/>
<int:method name="echoUpperCase" request-channel="inputB"/>
<int:method name="echoViaDefault"/>
</int:gateway>]]></programlisting>
<para>
@@ -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.
</para>
<para><emphasis role="bold">Expressions and "Global" Headers</emphasis></para>
<para>
The <code>&lt;header/&gt;</code> element supports <code>expression</code> as an alternative to
<code>value</code>. The SpEL expression is evaluated to determine the value of the header. There is no
<code>#root</code> object but the following variables are available:
<itemizedlist>
<listitem>
#args - an <code>Object[]</code> containing the method arguments
</listitem>
<listitem>
#gatewayMethod - the <classname>java.reflect.Method</classname> object representing the method in the
<code>service-interface</code> 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 <code>#gatewayMethod.name</code>.
<note>
The <classname>java.reflect.Method</classname> is not serializable; a header with expression
<code>#gatewayMethod</code> will be lost if you later serialize the message. So, you may wish
to use <code>#gatewayMethod.name</code> or <code>#gatewayMethod.toString()</code> in those cases;
the <code>toString()</code> method provides a String representation of the method, including
parameter and return types.
</note>
<note>
Prior to 3.0, the <code>#method</code> variable was available, representing the method name only.
This is still available, but deprecated; use <code>#gatewayMethod.name</code> instead.
</note>
</listitem>
</itemizedlist>
</para>
<para>
Since 3.0, <code>&lt;default-header/&gt;</code>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 <code>@Header</code> annotations
in the service interface. However, default headers will NOT override any <code>@Header</code> annotations
in the service interface.
</para>
<para>
The gateway now also supports a <code>default-payload-expression</code> which will be applied for all methods
(unless overridden).
</para>
</section>
<section id="gateway-calling-no-argument-methods">

View File

@@ -155,6 +155,20 @@
<section id="3.0-general">
<title>General Changes</title>
<section id="3.0-gateway">
<title>&lt;gateway&gt; Changes</title>
<para>
<itemizedlist>
<listitem>
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.
</listitem>
</itemizedlist>
</para>
<para>
For more information see <xref linkend="gateway"/>.
</para>
</section>
<section id="3.0-corr-endpoint-empty-groups">
<title>Aggregator 'empty-group-min-timeout' property</title>
<para><classname>AbstractCorrelatingMessageHandler</classname> provides a new property