INT-2629 Extract Gateway Argument Mapping Strategy

https://jira.springsource.org/browse/INT-2629

Strategy for mapping gateway methods to map method arguments
to a message.

Default strategy works as today, but pluggable using attribute
'mapper' on <gateway/>.

A custom mapper should implement InboundMessageMapper<MethodArgsHolder>

where the MethodArgsHolder contains the Method object and the argument
values.

When using a custom mapper, the mapper is entirely responsible for
creating the Message - therefore 'payload-expression' attributes
and <header/> elements are not allowed.

INT-2629 Polishing - Javadocs

INT-2629 Polishing

- Introduce MethodArgsMessageMapper - higher level API for custom mapper.

INT-2629: Polishing docs
This commit is contained in:
Gary Russell
2013-10-25 14:43:13 +03:00
committed by Artem Bilan
parent f0c4bdb756
commit 182b232fda
11 changed files with 319 additions and 87 deletions

View File

@@ -28,6 +28,7 @@ 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.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -43,7 +44,7 @@ import org.springframework.util.xml.DomUtils;
public class GatewayParser extends AbstractSimpleBeanDefinitionParser {
private static String[] referenceAttributes = new String[] {
"default-request-channel", "default-reply-channel", "error-channel", "message-mapper", "async-executor"
"default-request-channel", "default-reply-channel", "error-channel", "message-mapper", "async-executor", "mapper"
};
private static String[] innerAttributes = new String[] {
@@ -93,9 +94,16 @@ public class GatewayParser extends AbstractSimpleBeanDefinitionParser {
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, attributeName);
}
boolean hasMapper = StringUtils.hasText(element.getAttribute("mapper"));
boolean hasDefaultPayloadExpression = StringUtils.hasText(element.getAttribute("default-payload-expression"));
Assert.state(hasMapper ? !hasDefaultPayloadExpression : true, "'default-payload-expression' is not allowed when a 'mapper' is provided");
List<Element> invocationHeaders = DomUtils.getChildElementsByTagName(element, "default-header");
if (!CollectionUtils.isEmpty(invocationHeaders)
|| StringUtils.hasText(element.getAttribute("default-payload-expression"))) {
boolean hasDefaultHeaders = !CollectionUtils.isEmpty(invocationHeaders);
Assert.state(hasMapper ? !hasDefaultHeaders : true, "default-header elements are not allowed when a 'mapper' is provided");
if (hasDefaultHeaders || hasDefaultPayloadExpression) {
BeanDefinitionBuilder methodMetadataBuilder = BeanDefinitionBuilder.genericBeanDefinition(
"org.springframework.integration.gateway.GatewayMethodMetadata");
this.setMethodInvocationHeaders(methodMetadataBuilder, invocationHeaders);
@@ -118,8 +126,11 @@ 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");
Assert.state(hasMapper ? !StringUtils.hasText(element.getAttribute("payload-expression")) : true,
"'payload-expression' is not allowed when a 'mapper' is provided");
invocationHeaders = DomUtils.getChildElementsByTagName(methodElement, "header");
if (!CollectionUtils.isEmpty(invocationHeaders)) {
Assert.state(!hasMapper, "header elements are not allowed when a 'mapper' is provided");
this.setMethodInvocationHeaders(methodMetadataBuilder, invocationHeaders);
}
methodMetadataMap.put(methodName, methodMetadataBuilder.getBeanDefinition());

View File

@@ -42,6 +42,7 @@ import org.springframework.integration.annotation.Headers;
import org.springframework.integration.annotation.Payload;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.mapping.InboundMessageMapper;
import org.springframework.integration.mapping.MessageMappingException;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -85,6 +86,8 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
private final List<MethodParameter> parameterList;
private final MethodArgsMessageMapper argsMapper;
private volatile Expression payloadExpression;
private final Map<String, Expression> parameterPayloadExpressions = new HashMap<String, Expression>();
@@ -93,23 +96,28 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
private volatile BeanFactory beanFactory;
public GatewayMethodInboundMessageMapper(Method method) {
this(method, null);
}
public GatewayMethodInboundMessageMapper(Method method, Map<String, Expression> headerExpressions) {
this(method, headerExpressions, null);
this(method, headerExpressions, null, null);
}
public GatewayMethodInboundMessageMapper(Method method, Map<String, Expression> headerExpressions,
Map<String, Expression> globalHeaderExpressions) {
Map<String, Expression> globalHeaderExpressions, MethodArgsMessageMapper mapper) {
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);
if (mapper == null) {
this.argsMapper = new DefaultMethodArgsMessageMapper();
}
else {
this.argsMapper = mapper;
}
}
@@ -135,85 +143,17 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
}
private Message<?> mapArgumentsToMessage(Object[] arguments) {
Object messageOrPayload = null;
boolean foundPayloadAnnotation = false;
Map<String, Object> headers = new HashMap<String, Object>();
EvaluationContext methodInvocationEvaluationContext = createMethodInvocationEvaluationContext(arguments);
if (this.payloadExpression != null) {
messageOrPayload = this.payloadExpression.getValue(methodInvocationEvaluationContext);
try {
return this.argsMapper.toMessage(new MethodArgsHolder(this.method, arguments));
}
for (int i = 0; i < this.parameterList.size(); i++) {
Object argumentValue = arguments[i];
MethodParameter methodParameter = this.parameterList.get(i);
Annotation annotation = this.findMappingAnnotation(methodParameter.getParameterAnnotations());
if (annotation != null) {
if (annotation.annotationType().equals(Payload.class)) {
if (messageOrPayload != null) {
this.throwExceptionForMultipleMessageOrPayloadParameters(methodParameter);
}
String expression = ((Payload) annotation).value();
if (!StringUtils.hasText(expression)) {
messageOrPayload = argumentValue;
}
else {
messageOrPayload = this.evaluatePayloadExpression(expression, argumentValue);
}
foundPayloadAnnotation = true;
}
else if (annotation.annotationType().equals(Header.class)) {
Header headerAnnotation = (Header) annotation;
String headerName = this.determineHeaderName(headerAnnotation, methodParameter);
if (headerAnnotation.required() && argumentValue == null) {
throw new IllegalArgumentException("Received null argument value for required header: '" + headerName + "'");
}
headers.put(headerName, argumentValue);
}
else if (annotation.annotationType().equals(Headers.class)) {
if (argumentValue != null) {
if (!(argumentValue instanceof Map)) {
throw new IllegalArgumentException("@Headers annotation is only valid for Map-typed parameters");
}
for (Object key : ((Map<?, ?>) argumentValue).keySet()) {
Assert.isInstanceOf(String.class, key, "Invalid header name [" + key +
"], name type must be String.");
Object value = ((Map<?, ?>) argumentValue).get(key);
headers.put((String) key, value);
}
}
}
catch (Exception e) {
if (e instanceof MessagingException) {
throw (MessagingException) e;
}
else if (messageOrPayload == null) {
messageOrPayload = argumentValue;
}
else if (Map.class.isAssignableFrom(methodParameter.getParameterType())) {
if (messageOrPayload instanceof Map && !foundPayloadAnnotation) {
if (payloadExpression == null){
throw new MessagingException("Ambiguous method parameters; found more than one " +
"Map-typed parameter and neither one contains a @Payload annotation");
}
}
this.copyHeaders((Map<?, ?>) argumentValue, headers);
}
else if (this.payloadExpression == null) {
this.throwExceptionForMultipleMessageOrPayloadParameters(methodParameter);
else {
throw new MessageMappingException("Failed to map arguments", e);
}
}
Assert.isTrue(messageOrPayload != null, "unable to determine a Message or payload parameter on method [" + method + "]");
MessageBuilder<?> builder = (messageOrPayload instanceof Message)
? 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 = 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) {
@@ -318,4 +258,94 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper<Object[]
return expression;
}
public class DefaultMethodArgsMessageMapper implements MethodArgsMessageMapper {
@Override
public Message<?> toMessage(MethodArgsHolder holder) throws Exception {
Object messageOrPayload = null;
boolean foundPayloadAnnotation = false;
Object[] arguments = holder.getArgs();
EvaluationContext methodInvocationEvaluationContext = createMethodInvocationEvaluationContext(arguments);
Map<String, Object> headers = new HashMap<String, Object>();
if (GatewayMethodInboundMessageMapper.this.payloadExpression != null) {
messageOrPayload = GatewayMethodInboundMessageMapper.this.payloadExpression.getValue(methodInvocationEvaluationContext);
}
for (int i = 0; i < GatewayMethodInboundMessageMapper.this.parameterList.size(); i++) {
Object argumentValue = arguments[i];
MethodParameter methodParameter = GatewayMethodInboundMessageMapper.this.parameterList.get(i);
Annotation annotation = GatewayMethodInboundMessageMapper.this.findMappingAnnotation(methodParameter.getParameterAnnotations());
if (annotation != null) {
if (annotation.annotationType().equals(Payload.class)) {
if (messageOrPayload != null) {
GatewayMethodInboundMessageMapper.this.throwExceptionForMultipleMessageOrPayloadParameters(methodParameter);
}
String expression = ((Payload) annotation).value();
if (!StringUtils.hasText(expression)) {
messageOrPayload = argumentValue;
}
else {
messageOrPayload = GatewayMethodInboundMessageMapper.this.evaluatePayloadExpression(expression, argumentValue);
}
foundPayloadAnnotation = true;
}
else if (annotation.annotationType().equals(Header.class)) {
Header headerAnnotation = (Header) annotation;
String headerName = GatewayMethodInboundMessageMapper.this.determineHeaderName(headerAnnotation, methodParameter);
if (headerAnnotation.required() && argumentValue == null) {
throw new IllegalArgumentException("Received null argument value for required header: '" + headerName + "'");
}
headers.put(headerName, argumentValue);
}
else if (annotation.annotationType().equals(Headers.class)) {
if (argumentValue != null) {
if (!(argumentValue instanceof Map)) {
throw new IllegalArgumentException("@Headers annotation is only valid for Map-typed parameters");
}
for (Object key : ((Map<?, ?>) argumentValue).keySet()) {
Assert.isInstanceOf(String.class, key, "Invalid header name [" + key +
"], name type must be String.");
Object value = ((Map<?, ?>) argumentValue).get(key);
headers.put((String) key, value);
}
}
}
}
else if (messageOrPayload == null) {
messageOrPayload = argumentValue;
}
else if (Map.class.isAssignableFrom(methodParameter.getParameterType())) {
if (messageOrPayload instanceof Map && !foundPayloadAnnotation) {
if (payloadExpression == null){
throw new MessagingException("Ambiguous method parameters; found more than one " +
"Map-typed parameter and neither one contains a @Payload annotation");
}
}
GatewayMethodInboundMessageMapper.this.copyHeaders((Map<?, ?>) argumentValue, headers);
}
else if (GatewayMethodInboundMessageMapper.this.payloadExpression == null) {
GatewayMethodInboundMessageMapper.this.throwExceptionForMultipleMessageOrPayloadParameters(methodParameter);
}
}
Assert.isTrue(messageOrPayload != null, "unable to determine a Message or payload parameter on method [" + method + "]");
MessageBuilder<?> builder = (messageOrPayload instanceof Message)
? MessageBuilder.fromMessage((Message<?>) messageOrPayload)
: MessageBuilder.withPayload(messageOrPayload);
builder.copyHeadersIfAbsent(headers);
// Explicit headers in XML override any @Header annotations...
if (!CollectionUtils.isEmpty(GatewayMethodInboundMessageMapper.this.headerExpressions)) {
Map<String, Object> evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext,
GatewayMethodInboundMessageMapper.this.headerExpressions);
builder.copyHeaders(evaluatedHeaders);
}
// ...whereas global (default) headers do not...
if (!CollectionUtils.isEmpty(GatewayMethodInboundMessageMapper.this.globalHeaderExpressions)) {
Map<String, Object> evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext,
GatewayMethodInboundMessageMapper.this.globalHeaderExpressions);
builder.copyHeadersIfAbsent(evaluatedHeaders);
}
return builder.build();
}
}
}

View File

@@ -105,6 +105,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab
private volatile GatewayMethodMetadata globalMethodMetadata;
private volatile MethodArgsMessageMapper argsMapper;
/**
* Create a Factory whose service interface type can be configured by setter injection.
* If none is set, it will fall back to the default service interface type,
@@ -214,6 +216,15 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab
this.beanClassLoader = beanClassLoader;
}
/**
* Provide a custom {@link MethodArgsMessageMapper} to map from a {@link MethodArgsHolder}
* to a {@link Message}.
* @param mapper the mapper.
*/
public final void setMapper(MethodArgsMessageMapper mapper) {
this.argsMapper = mapper;
}
@Override
protected void onInit() {
synchronized (this.initializationMonitor) {
@@ -395,7 +406,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Trackab
}
}
GatewayMethodInboundMessageMapper messageMapper = new GatewayMethodInboundMessageMapper(method, headerExpressions,
this.globalMethodMetadata != null ? this.globalMethodMetadata.getHeaderExpressions() : null);
this.globalMethodMetadata != null ? this.globalMethodMetadata.getHeaderExpressions() : null,
this.argsMapper);
if (StringUtils.hasText(payloadExpression)) {
messageMapper.setPayloadExpression(payloadExpression);
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 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.
* 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.gateway;
import java.lang.reflect.Method;
/**
* Simple wrapper class containing a {@link Method} and an object
* array containing the arguments for an invocation of that method.
* For example used by a {@link MethodArgsMessageMapper} with this generic
* type to provide custom argument mapping when creating a message
* in a {@code GatewayProxyFactoryBean}.
*
* @author Gary Russell
* @since 3.0
*
*/
public final class MethodArgsHolder {
private final Method method;
private final Object[] args;
public MethodArgsHolder(Method method, Object[] args) {
this.method = method;
this.args = args;
}
public final Method getMethod() {
return method;
}
public final Object[] getArgs() {
return args;
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 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.
* 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.gateway;
import org.springframework.integration.mapping.InboundMessageMapper;
/**
* Implementations of this interface are {@link InboundMessageMapper}s
* that map a {@link MethodArgsHolder} to a {@link org.springframework.integration.Message}.
*
* @author Gary Russell
* @since 3.0
*
*/
public interface MethodArgsMessageMapper extends InboundMessageMapper<MethodArgsHolder> {
}

View File

@@ -717,6 +717,22 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mapper" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
An MethodArgsMessageMapper to map the method arguments to a Message. When this
is provided, no payload-expressions or headers are allowed; the custom mapper is
responsible for creating the message.
]]>
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.gateway.MethodArgsMessageMapper" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>

View File

@@ -20,4 +20,10 @@
<int:channel id="requestChannelBar"/>
<int:channel id="requestChannelBaz"/>
<int:gateway id="customMappedGateway"
service-interface="org.springframework.integration.gateway.GatewayInterfaceTests.Baz"
default-request-channel="requestChannelBaz" mapper="mapper"/>
<bean id="mapper" class="org.springframework.integration.gateway.GatewayInterfaceTests$BazMapper"/>
</beans>

View File

@@ -40,6 +40,7 @@ import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.Header;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.support.MessageBuilder;
/**
* @author Oleg Zhurakousky
@@ -238,6 +239,26 @@ public class GatewayInterfaceTests {
new GatewayProxyFactoryBean(NotAnInterface.class);
}
@Test
public void testWithCustomMapper() {
ApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
final AtomicBoolean called = new AtomicBoolean();
MessageHandler handler = new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
assertThat((String) message.getPayload(), equalTo("fizbuz"));
called.set(true);
}
};
channel.subscribe(handler);
Baz baz = ac.getBean(Baz.class);
baz.baz("hello");
assertTrue(called.get());
}
public interface Foo {
@Gateway(requestChannel="requestChannelFoo")
@@ -256,4 +277,18 @@ public class GatewayInterfaceTests {
public static class NotAnInterface {
public void fail(String payload){}
}
public interface Baz {
public void baz(String payload);
}
public static class BazMapper implements MethodArgsMessageMapper {
@Override
public Message<?> toMessage(MethodArgsHolder object) throws Exception {
return MessageBuilder.withPayload("fizbuz").build();
}
}
}

View File

@@ -34,6 +34,7 @@ import org.springframework.integration.Message;
import org.springframework.integration.annotation.Header;
import org.springframework.integration.annotation.Headers;
import org.springframework.integration.annotation.Payload;
import org.springframework.integration.mapping.MessageMappingException;
import org.springframework.integration.support.MessageBuilder;
/**
@@ -78,7 +79,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests {
assertEquals("bar", message.getHeaders().get("foo"));
}
@Test(expected = IllegalArgumentException.class)
@Test(expected = MessageMappingException.class)
public void toMessageWithPayloadAndRequiredHeaderButNullValue() throws Exception {
Method method = TestService.class.getMethod(
"sendPayloadAndHeader", String.class, String.class);
@@ -134,7 +135,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests {
assertEquals("test", message.getPayload());
}
@Test(expected = IllegalArgumentException.class)
@Test(expected = MessageMappingException.class)
public void toMessageWithPayloadAndHeadersMapWithNonStringKey() throws Exception {
Method method = TestService.class.getMethod(
"sendPayloadAndHeadersMap", String.class, Map.class);
@@ -166,7 +167,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests {
assertEquals("bar", message.getHeaders().get("foo"));
}
@Test(expected = IllegalArgumentException.class)
@Test(expected = MessageMappingException.class)
public void toMessageWithMessageParameterAndRequiredHeaderButNullValue() throws Exception {
Method method = TestService.class.getMethod("sendMessageAndHeader", Message.class, String.class);
GatewayMethodInboundMessageMapper mapper = new GatewayMethodInboundMessageMapper(method);
@@ -197,7 +198,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests {
assertNull(message.getHeaders().get("foo"));
}
@Test(expected = IllegalArgumentException.class)
@Test(expected = MessageMappingException.class)
public void noArgs() throws Exception {
Method method = TestService.class.getMethod("noArgs", new Class<?>[] {});
GatewayMethodInboundMessageMapper mapper = new GatewayMethodInboundMessageMapper(method);
@@ -205,7 +206,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests {
mapper.toMessage(new Object[] {});
}
@Test(expected = IllegalArgumentException.class)
@Test(expected = MessageMappingException.class)
public void onlyHeaders() throws Exception {
Method method = TestService.class.getMethod("onlyHeaders", String.class, String.class);
GatewayMethodInboundMessageMapper mapper = new GatewayMethodInboundMessageMapper(method);

View File

@@ -187,6 +187,43 @@ public interface Cafe {
</para>
</section>
<section id="gateway-mapping">
<title>Mapping Method Arguments to a Message</title>
<para>
Using the configuration techniques in the previous section allows control of how method arguments are mapped
to message elements (payload and header(s)). When no explicit configuration is used, certain conventions are
used to perform the mapping. In some cases, these conventions cannot determine which argument is the payload
and which should be mapped to headers.
</para>
<programlisting language="java"><![CDATA[
public String send1(Object foo, Map bar);
public String send2(Map foo, Map bar);
]]></programlisting>
<para>
In the first case, the convention will map the first argument to the payload (as long as it is not a
<code>Map</code>) and the contents of the second become headers.
</para>
<para>
In the second case (or the first when the argument for parameter <code>foo</code> is a <code>Map</code>),
the framework cannot determine
which argument should be the payload; mapping will fail. This can generally be resolved using a
<code>payload-expression</code>, a <code>@Payload</code> annotation and/or a <code>@Headers</code>
annotation.
</para>
<para>
Alternatively, and whenever the conventions break down, you can take the entire responsibility for
mapping the method calls to messages. To do this, implement an
<classname>MethodArgsMessageMapper</classname> and provide it to the
<code>&lt;gateway/&gt;</code> using the <code>mapper</code> attribute. The mapper maps a
<classname>MethodArgsHolder</classname>, which is a simple class wrapping the <classname>java.reflect.Method</classname>
instance and an <code>Object[]</code> containing the arguments. When providing a custom mapper,
the <code>default-payload-expression</code> attribute and <code>&lt;default-header/&gt;</code> elements
are not allowed on the gateway; similarly, the <code>payload-expression</code> attribute and
<code>&lt;header/&gt;</code> elements are not allowed on any <code>&lt;method/&gt;</code> elements.
</para>
</section>
<section id="gateway-calling-no-argument-methods">
<title>Invoking No-Argument Methods</title>
<para>

View File

@@ -163,6 +163,10 @@
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>
<listitem>
It is now possible to entirely customize the way that gateway method calls are mapped
to messages.
</listitem>
</itemizedlist>
</para>
<para>