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:
committed by
Artem Bilan
parent
f0c4bdb756
commit
182b232fda
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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><gateway/></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><default-header/></code> elements
|
||||
are not allowed on the gateway; similarly, the <code>payload-expression</code> attribute and
|
||||
<code><header/></code> elements are not allowed on any <code><method/></code> elements.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section id="gateway-calling-no-argument-methods">
|
||||
<title>Invoking No-Argument Methods</title>
|
||||
<para>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user