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 dea4f53dac..5a2d6bf525 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 @@ -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 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()); 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 7ec9dd3fe3..0e4f054658 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 @@ -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 parameterList; + private final MethodArgsMessageMapper argsMapper; + private volatile Expression payloadExpression; private final Map parameterPayloadExpressions = new HashMap(); @@ -93,23 +96,28 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper headerExpressions) { - this(method, headerExpressions, null); + this(method, headerExpressions, null, null); } public GatewayMethodInboundMessageMapper(Method method, Map headerExpressions, - Map globalHeaderExpressions) { + Map 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 mapArgumentsToMessage(Object[] arguments) { - Object messageOrPayload = null; - boolean foundPayloadAnnotation = false; - Map headers = new HashMap(); - 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 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) { @@ -318,4 +258,94 @@ class GatewayMethodInboundMessageMapper implements InboundMessageMapper toMessage(MethodArgsHolder holder) throws Exception { + Object messageOrPayload = null; + boolean foundPayloadAnnotation = false; + Object[] arguments = holder.getArgs(); + EvaluationContext methodInvocationEvaluationContext = createMethodInvocationEvaluationContext(arguments); + Map headers = new HashMap(); + 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 evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, + GatewayMethodInboundMessageMapper.this.headerExpressions); + builder.copyHeaders(evaluatedHeaders); + } + // ...whereas global (default) headers do not... + if (!CollectionUtils.isEmpty(GatewayMethodInboundMessageMapper.this.globalHeaderExpressions)) { + Map evaluatedHeaders = evaluateHeaders(methodInvocationEvaluationContext, + GatewayMethodInboundMessageMapper.this.globalHeaderExpressions); + builder.copyHeadersIfAbsent(evaluatedHeaders); + } + return builder.build(); + } + + } + } 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 a2b7983061..877d647b73 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 @@ -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); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsHolder.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsHolder.java new file mode 100644 index 0000000000..0a8ce4c792 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsHolder.java @@ -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; + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsMessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsMessageMapper.java new file mode 100644 index 0000000000..70df6db7c9 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MethodArgsMessageMapper.java @@ -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 { + +} 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 2cd4802b3e..4c6867c967 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 @@ -717,6 +717,22 @@ + + + + + + + + + + + + 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 788bd10290..ce665b5115 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 @@ -20,4 +20,10 @@ + + + + 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 69242b4597..0e8f04c46d 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 @@ -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(); + } + + } } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapperToMessageTests.java b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapperToMessageTests.java index e076aa4b1a..76b33f6310 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapperToMessageTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayMethodInboundMessageMapperToMessageTests.java @@ -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); diff --git a/src/reference/docbook/gateway.xml b/src/reference/docbook/gateway.xml index e0f12db1be..28012f740a 100644 --- a/src/reference/docbook/gateway.xml +++ b/src/reference/docbook/gateway.xml @@ -187,6 +187,43 @@ public interface Cafe { +
+ Mapping Method Arguments to a Message + + 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. + + + + In the first case, the convention will map the first argument to the payload (as long as it is not a + Map) and the contents of the second become headers. + + + In the second case (or the first when the argument for parameter foo is a Map), + the framework cannot determine + which argument should be the payload; mapping will fail. This can generally be resolved using a + payload-expression, a @Payload annotation and/or a @Headers + annotation. + + + 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 + MethodArgsMessageMapper and provide it to the + <gateway/> using the mapper attribute. The mapper maps a + MethodArgsHolder, which is a simple class wrapping the java.reflect.Method + instance and an Object[] containing the arguments. When providing a custom mapper, + the default-payload-expression attribute and <default-header/> elements + are not allowed on the gateway; similarly, the payload-expression attribute and + <header/> elements are not allowed on any <method/> elements. + +
+
Invoking No-Argument Methods diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 6d3f2ab7b4..bb3b820e95 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -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. + + It is now possible to entirely customize the way that gateway method calls are mapped + to messages. +