From 0e8aa077488d0ccf92768964319b9bf65e44b2e7 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Tue, 29 Sep 2009 17:29:52 +0000 Subject: [PATCH] INT-681, INT-710 more refactoring of code for mapping between Messages and argument arrays --- .../gateway/GatewayProxyFactoryBean.java | 4 +- .../handler/MessageMappingMethodInvoker.java | 1 - .../handler/MethodArgumentMessageMapper.java | 307 +++++++++++++++- .../message/MethodParameterMessageMapper.java | 329 ------------------ ...arameterMessageMapperFromMessageTests.java | 26 +- ...meterMessageMapperInitializationTests.java | 8 +- ...dParameterMessageMapperToMessageTests.java | 35 +- 7 files changed, 337 insertions(+), 373 deletions(-) delete mode 100644 org.springframework.integration/src/main/java/org/springframework/integration/message/MethodParameterMessageMapper.java diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java b/org.springframework.integration/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java index bf7e45ede3..9fd4b80692 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java @@ -36,7 +36,7 @@ import org.springframework.integration.annotation.Gateway; import org.springframework.integration.core.Message; import org.springframework.integration.core.MessageChannel; import org.springframework.integration.endpoint.AbstractEndpoint; -import org.springframework.integration.message.MethodParameterMessageMapper; +import org.springframework.integration.handler.MethodArgumentMessageMapper; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -225,7 +225,7 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint implements Factory private MessagingGateway createGatewayForMethod(Method method) throws Exception { SimpleMessagingGateway gateway = new SimpleMessagingGateway( - new MethodParameterMessageMapper(method), new SimpleMessageMapper()); + new MethodArgumentMessageMapper(method), new SimpleMessageMapper()); if (this.getTaskScheduler() != null) { gateway.setTaskScheduler(this.getTaskScheduler()); } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/handler/MessageMappingMethodInvoker.java b/org.springframework.integration/src/main/java/org/springframework/integration/handler/MessageMappingMethodInvoker.java index 10bb0d0f1b..806923ab02 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/handler/MessageMappingMethodInvoker.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/handler/MessageMappingMethodInvoker.java @@ -34,7 +34,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.integration.core.Message; import org.springframework.integration.message.MessageHandlingException; -import org.springframework.integration.message.MethodParameterMessageMapper; import org.springframework.integration.util.DefaultMethodInvoker; import org.springframework.integration.util.MethodInvoker; import org.springframework.util.Assert; diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/handler/MethodArgumentMessageMapper.java b/org.springframework.integration/src/main/java/org/springframework/integration/handler/MethodArgumentMessageMapper.java index 18b0a839ab..43d7755375 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/handler/MethodArgumentMessageMapper.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/handler/MethodArgumentMessageMapper.java @@ -16,43 +16,199 @@ package org.springframework.integration.handler; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.Map; +import java.util.Properties; +import java.util.Set; +import org.springframework.core.GenericTypeResolver; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.MethodParameter; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.integration.annotation.Header; +import org.springframework.integration.annotation.Headers; import org.springframework.integration.core.Message; -import org.springframework.integration.message.MethodParameterMessageMapper; +import org.springframework.integration.core.MessageHeaders; +import org.springframework.integration.message.InboundMessageMapper; +import org.springframework.integration.message.MessageBuilder; +import org.springframework.integration.message.MessageHandlingException; import org.springframework.integration.message.OutboundMessageMapper; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; /** + * A Message Mapper implementation that supports mapping from a Message + * to an argument array when invoking handler methods, and mapping to a + * Message from an argument array when invoking gateway methods. + *

+ * When mapping from a Message, the method parameters are matched + * against the Message, its payload as well as its headers. A message or + * payload parameter must not be annotated, and there can be at most one of + * these. In certain special cases, more than one non-annotated parameter can + * be used (more on this later), but there should always be at most one + * parameter that is expecting the message or its payload. + *

+ * If a method parameter is annotated with {@link Header @Header}, the + * annotation's value will be used as a header name. If such an annotation + * contains no value, then the parameter name will be used as long as the + * information is available in the class file (requires compilation with debug + * settings for parameter names). + *

+ * In addition a Map or Properties parameter can receive multiple message + * headers. In the case of a Map argument, all headers will be passed, but in + * the case of a Properties argument, only the headers with String-typed values + * will be passed. These parameters can be labeled explicitly with the + * {@link Headers @Headers} annotation, or matched implicitly by using a non- + * ambiguous method signature. There can be as many parameters annotated with + * @Header as necessary, but typically there should be only one parameter + * expecting multiple headers (with or without the @Headers annotation). + *

+ * If a Map or Properties object is expected, and the payload is not itself + * assignable to that type or capable of being converted to that type, then + * the MessageHeaders' values will be passed in the case of a Map-typed + * parameter, or the MessageHeaders' String-based values will be passed in the + * case of a Properties-typed parameter. In these cases multiple unannotated + * parameters are legal. If, however, the actual payload type is a Map or + * Properties instance, then this ambiguity cannot be resolved. For that + * reason, it is highly recommended to use the explicit + * {@link Headers @Headers} annotation whenever possible. + *

+ * Some examples of legal method signatures:
+ * public void dealWith(Object payload);
+ * public void dealWith(Message message);
+ * public void dealWith(@Header String myHeader, Object payload);
+ * public void dealWith(@Header String myHeader, @Header String anotherHeader); + *
+ * public void dealWith(@Headers Map headers, Object payload);
+ * public void dealWith(@Headers Properties headers, Map payload);
+ * public void dealWith(Properties headers, Object payload);
+ *

+ * Some examples of illegal method signatures:
+ * public void dealWith(Object payload, String payload);
+ * public void dealWith(Message message, Object payload);
+ * public void dealWith(Properties headers, Map payload);
+ * * @author Mark Fisher + * @author Iwein Fuld * @since 2.0 */ -public class MethodArgumentMessageMapper implements OutboundMessageMapper { +public class MethodArgumentMessageMapper implements InboundMessageMapper, OutboundMessageMapper { private final Method method; - //TODO: move core mapping logic from MethodParameterMessageMapper into this class - private final MethodParameterMessageMapper innerMapper; + private final MethodParameterMetadata[] parameterMetadata; + + private final MethodParameterMetadata payloadParameterMetadata; + + private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); public MethodArgumentMessageMapper(Method method) { Assert.notNull(method, "method must not be null"); this.method = method; - this.innerMapper = new MethodParameterMessageMapper(method); + this.parameterMetadata = this.initializeParameterMetadata(); + this.payloadParameterMetadata = getPayloadParameterFrom(this.parameterMetadata); } + public Message toMessage(Object[] parameters) { + Assert.isTrue(!ObjectUtils.isEmpty(parameters), "parameter array is required"); + Assert.isTrue(parameters.length == this.parameterMetadata.length, "wrong number of parameters: expected " + + this.parameterMetadata.length + ", received " + parameters.length); + Message message = null; + Object payload = null; + Map headers = new HashMap(); + for (int i = 0; i < parameters.length; i++) { + Object value = parameters[i]; + MethodParameterMetadata metadata = this.parameterMetadata[i]; + Header headerAnnotation = metadata.getHeaderAnnotation(); + if (metadata == payloadParameterMetadata) { + Assert.notNull(value, "payload object must not be null"); + payload = value; + } + else if (headerAnnotation != null) { + String headerName = metadata.getHeaderName(); + boolean required = headerAnnotation.required(); + if (value != null) { + headers.put(headerName, value); + } + else { + Assert.isTrue(!required, "header '" + headerName + "' is required"); + } + } + else if (metadata.hasHeadersAnnotation() || metadata.isMapOrProperties()) { + if (value != null) { + this.addHeadersAnnotatedParameterToMap(value, headers); + } + } + else { + Assert.isTrue(Message.class.isAssignableFrom(metadata.getParameterType())); + message = (Message) value; + } + } + if (message != null) { + if (headers.isEmpty()) { + return message; + } + return MessageBuilder.fromMessage(message).copyHeadersIfAbsent(headers).build(); + } + Assert.notNull(payload, "no parameter available for Message or payload"); + return MessageBuilder.withPayload(payload).copyHeaders(headers).build(); + } public Object[] fromMessage(Message message) throws Exception { - Object args[] = null; - Object mappingResult = this.innerMapper.fromMessage(message); - if (mappingResult != null && mappingResult.getClass().isArray() + if (message == null) { + return null; + } + + // TODO: allow null payload but return null above? + Assert.notNull(message.getPayload(), "Message payload must not be null."); + + Object[] args = new Object[this.parameterMetadata.length]; + for (int i = 0; i < this.parameterMetadata.length; i++) { + MethodParameterMetadata metadata = this.parameterMetadata[i]; + Class expectedType = metadata.getParameterType(); + Header headerAnnotation = metadata.getHeaderAnnotation(); + if (metadata == this.payloadParameterMetadata) { + args[i] = message.getPayload(); + } + else if (headerAnnotation != null) { + String headerName = metadata.getHeaderName(); + Object value = message.getHeaders().get(headerName); + if (value == null && headerAnnotation.required()) { + throw new MessageHandlingException(message, "required header '" + headerName + "' not available"); + } + args[i] = value; + } + else if (metadata.isMapOrProperties()) { + if (Properties.class.isAssignableFrom(expectedType)) { + args[i] = this.getStringTypedHeaders(message); + } + else { + args[i] = message.getHeaders(); + } + } + else { + Assert.isTrue(expectedType.isAssignableFrom(message.getClass()) + && Message.class.isAssignableFrom(expectedType), + "Argument is neither header or payload, so it should be of type message."); + args[i] = message; + } + } + + /*if (mappingResult != null && mappingResult.getClass().isArray() && (Object.class.isAssignableFrom(mappingResult.getClass().getComponentType()))) { args = (Object[]) mappingResult; } else { args = new Object[] { mappingResult }; } + */ + if (args.length > 1 && message != null && message.getPayload() instanceof Map) { int mapArgCount = 0; boolean resolvedMapArg = false; @@ -72,4 +228,139 @@ public class MethodArgumentMessageMapper implements OutboundMessageMapper message) { + Properties properties = new Properties(); + MessageHeaders headers = message.getHeaders(); + for (String key : headers.keySet()) { + Object value = headers.get(key); + if (value instanceof String) { + properties.setProperty(key, (String) value); + } + } + return properties; + } + + @SuppressWarnings("unchecked") + private void addHeadersAnnotatedParameterToMap(Object value, Map headers) { + Map map = (Map) value; + for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) { + Map.Entry entry = (Map.Entry) iter.next(); + Assert.isTrue(entry.getKey() instanceof String, "Map annotated with @Headers must have String-typed keys"); + headers.put((String) entry.getKey(), entry.getValue()); + } + } + + private MethodParameterMetadata[] initializeParameterMetadata() { + Class[] paramTypes = this.method.getParameterTypes(); + MethodParameterMetadata[] parameterMetadata = new MethodParameterMetadata[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) { + MethodParameterMetadata metadata = new MethodParameterMetadata(this.method, i); + metadata.initParameterNameDiscovery(this.parameterNameDiscoverer); + GenericTypeResolver.resolveParameterType(metadata.parameter, this.method.getDeclaringClass()); + parameterMetadata[i] = metadata; + } + return parameterMetadata; + } + + private MethodParameterMetadata getPayloadParameterFrom(MethodParameterMetadata[] mpm) { + Set payloadCandidates = new HashSet(); + int messageTypedParameterCount = 0; + for (MethodParameterMetadata metadata : mpm) { + if (Message.class.isAssignableFrom(metadata.getParameterType())) { + // expecting Message, not a payload candidate + messageTypedParameterCount++; + } + else if (metadata.getHeaderAnnotation() == null && !metadata.hasHeadersAnnotation()) { + // not expecting Message, and not explicitly annotated for headers + payloadCandidates.add(metadata); + } + } + if (payloadCandidates.size() > 1) { + Iterator iterator = payloadCandidates.iterator(); + while (iterator.hasNext()) { + Class type = iterator.next().getParameterType(); + if (Map.class.isAssignableFrom(type)) { + // Map (or Properties) may accept headers rather than payload + iterator.remove(); + } + } + } + if (payloadCandidates.size() + messageTypedParameterCount > 1) { + // too many candidates, create a helpful error message + int count = 0; + String[] candidateTypes = new String[payloadCandidates.size()]; + for (MethodParameterMetadata candidate : payloadCandidates) { + candidateTypes[count++] = candidate.getParameterType().getName(); + } + throw new IllegalArgumentException("At most one message or payload parameter " + + "is allowed on handler method [" + this.method.getName() + + "], but the following payload candidate types were found [" + + StringUtils.arrayToCommaDelimitedString(candidateTypes) + + "] and " + messageTypedParameterCount + " Message type(s)."); + } + return (payloadCandidates.isEmpty() ? null : payloadCandidates.iterator().next()); + } + + + private static class MethodParameterMetadata { + + private final MethodParameter parameter; + + private volatile Header _headerAnnotation; + + private volatile boolean _hasHeadersAnnotation; + + private MethodParameterMetadata(Method method, int index) { + this.parameter = new MethodParameter(method, index); + Annotation[] annotations = this.parameter.getParameterAnnotations(); + for (Object o : annotations) { + if (o instanceof Header) { + this._headerAnnotation = (Header) o; + } + else if (Headers.class.isInstance(o)) { + Assert.isAssignable(Map.class, this.parameter.getParameterType(), + "parameter with the @Headers annotation must be assignable to java.util.Map"); + this._hasHeadersAnnotation = true; + } + } + } + + Header getHeaderAnnotation() { + return this._headerAnnotation; + } + + boolean hasHeadersAnnotation() { + return this._hasHeadersAnnotation; + } + + boolean isMapOrProperties() { + if (Properties.class.isAssignableFrom(this.parameter.getParameterType()) + || Map.class.isAssignableFrom(this.parameter.getParameterType())) { + return true; + } + return false; + } + + String getHeaderName() { + if (this.getHeaderAnnotation() == null) { + return null; + } + String paramName = this.getHeaderAnnotation().value(); + if (!StringUtils.hasText(paramName)) { + paramName = this.parameter.getParameterName(); + Assert.state(paramName != null, + "No parameter name specified on @Header and unable to discover in class file."); + } + return paramName; + } + + private void initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDiscoverer) { + this.parameter.initParameterNameDiscovery(parameterNameDiscoverer); + } + + private Class getParameterType() { + return this.parameter.getParameterType(); + } + } + } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/message/MethodParameterMessageMapper.java b/org.springframework.integration/src/main/java/org/springframework/integration/message/MethodParameterMessageMapper.java deleted file mode 100644 index 1696dbc3df..0000000000 --- a/org.springframework.integration/src/main/java/org/springframework/integration/message/MethodParameterMessageMapper.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright 2002-2009 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.message; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - -import org.springframework.core.GenericTypeResolver; -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; -import org.springframework.core.MethodParameter; -import org.springframework.core.ParameterNameDiscoverer; -import org.springframework.integration.annotation.Header; -import org.springframework.integration.annotation.Headers; -import org.springframework.integration.core.Message; -import org.springframework.integration.core.MessageHeaders; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; - -/** - * Prepares arguments for handler methods. The method parameters are matched - * against the Message, its payload as well as its headers. A message or - * payload parameter must not be annotated, and there can be at most one of - * these. In certain special cases, more than one non-annotated parameter can - * be used (more on this later), but there should always be at most one - * parameter that is expecting the message or its payload. - *

- * If a method parameter is annotated with {@link Header @Header}, the - * annotation's value will be used as a header name. If such an annotation - * contains no value, then the parameter name will be used as long as the - * information is available in the class file (requires compilation with debug - * settings for parameter names). - *

- * In addition a Map or Properties parameter can receive multiple message - * headers. In the case of a Map argument, all headers will be passed, but in - * the case of a Properties argument, only the headers with String-typed values - * will be passed. These parameters can be labeled explicitly with the - * {@link Headers @Headers} annotation, or matched implicitly by using a non- - * ambiguous method signature. There can be as many parameters annotated with - * @Header as necessary, but typically there should be only one parameter - * expecting multiple headers (with or without the @Headers annotation). - *

- * If a Map or Properties object is expected, and the payload is not itself - * assignable to that type or capable of being converted to that type, then - * the MessageHeaders' values will be passed in the case of a Map-typed - * parameter, or the MessageHeaders' String-based values will be passed in the - * case of a Properties-typed parameter. In these cases multiple unannotated - * parameters are legal. If, however, the actual payload type is a Map or - * Properties instance, then this ambiguity cannot be resolved. For that - * reason, it is highly recommended to use the explicit - * {@link Headers @Headers} annotation whenever possible. - *

- * Some examples of legal method signatures:
- * public void dealWith(Object payload);
- * public void dealWith(Message message);
- * public void dealWith(@Header String myHeader, Object payload);
- * public void dealWith(@Header String myHeader, @Header String anotherHeader); - *
- * public void dealWith(@Headers Map headers, Object payload);
- * public void dealWith(@Headers Properties headers, Map payload);
- * public void dealWith(Properties headers, Object payload);
- *

- * Some examples of illegal method signatures:
- * public void dealWith(Object payload, String payload);
- * public void dealWith(Message message, Object payload);
- * public void dealWith(Properties headers, Map payload);
- * - * - * @author Mark Fisher - * @author Iwein Fuld - */ -public class MethodParameterMessageMapper implements InboundMessageMapper, OutboundMessageMapper { - - private final Method method; - - private final MethodParameterMetadata[] parameterMetadata; - - private final MethodParameterMetadata payloadParameterMetadata; - - private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); - - - public MethodParameterMessageMapper(Method method) { - Assert.notNull(method, "method must not be null"); - this.method = method; - this.parameterMetadata = this.initializeParameterMetadata(); - this.payloadParameterMetadata = getPayloadParameterFrom(this.parameterMetadata); - } - - - private MethodParameterMetadata getPayloadParameterFrom(MethodParameterMetadata[] mpm) { - Set payloadCandidates = new HashSet(); - int messageTypedParameterCount = 0; - for (MethodParameterMetadata metadata : mpm) { - if (Message.class.isAssignableFrom(metadata.getParameterType())) { - // expecting Message, not a payload candidate - messageTypedParameterCount++; - } - else if (metadata.getHeaderAnnotation() == null && !metadata.hasHeadersAnnotation()) { - // not expecting Message, and not explicitly annotated for headers - payloadCandidates.add(metadata); - } - } - if (payloadCandidates.size() > 1) { - Iterator iterator = payloadCandidates.iterator(); - while (iterator.hasNext()) { - Class type = iterator.next().getParameterType(); - if (Map.class.isAssignableFrom(type)) { - // Map (or Properties) may accept headers rather than payload - iterator.remove(); - } - } - } - if (payloadCandidates.size() + messageTypedParameterCount > 1) { - // too many candidates, create a helpful error message - int count = 0; - String[] candidateTypes = new String[payloadCandidates.size()]; - for (MethodParameterMetadata candidate : payloadCandidates) { - candidateTypes[count++] = candidate.getParameterType().getName(); - } - throw new IllegalArgumentException("At most one message or payload parameter " + - "is allowed on handler method [" + this.method.getName() + - "], but the following payload candidate types were found [" + - StringUtils.arrayToCommaDelimitedString(candidateTypes) + - "] and " + messageTypedParameterCount + " Message type(s)."); - } - return (payloadCandidates.isEmpty() ? null : payloadCandidates.iterator().next()); - } - - private MethodParameterMetadata[] initializeParameterMetadata() { - Class[] paramTypes = this.method.getParameterTypes(); - MethodParameterMetadata[] parameterMetadata = new MethodParameterMetadata[paramTypes.length]; - for (int i = 0; i < paramTypes.length; i++) { - MethodParameterMetadata metadata = new MethodParameterMetadata(this.method, i); - metadata.initParameterNameDiscovery(this.parameterNameDiscoverer); - GenericTypeResolver.resolveParameterType(metadata.parameter, this.method.getDeclaringClass()); - parameterMetadata[i] = metadata; - } - return parameterMetadata; - } - - public Message toMessage(Object[] parameters) { - Assert.isTrue(!ObjectUtils.isEmpty(parameters), "parameter array is required"); - Assert.isTrue(parameters.length == this.parameterMetadata.length, "wrong number of parameters: expected " - + this.parameterMetadata.length + ", received " + parameters.length); - Message message = null; - Object payload = null; - Map headers = new HashMap(); - for (int i = 0; i < parameters.length; i++) { - Object value = parameters[i]; - MethodParameterMetadata metadata = this.parameterMetadata[i]; - Header headerAnnotation = metadata.getHeaderAnnotation(); - if (metadata == payloadParameterMetadata) { - Assert.notNull(value, "payload object must not be null"); - payload = value; - } - else if (headerAnnotation != null) { - String headerName = metadata.getHeaderName(); - boolean required = headerAnnotation.required(); - if (value != null) { - headers.put(headerName, value); - } - else { - Assert.isTrue(!required, "header '" + headerName + "' is required"); - } - } - else if (metadata.hasHeadersAnnotation() || metadata.isMapOrProperties()) { - if (value != null) { - this.addHeadersAnnotatedParameterToMap(value, headers); - } - } - else { - Assert.isTrue(Message.class.isAssignableFrom(metadata.getParameterType())); - message = (Message) value; - } - } - if (message != null) { - if (headers.isEmpty()) { - return message; - } - return MessageBuilder.fromMessage(message).copyHeadersIfAbsent(headers).build(); - } - Assert.notNull(payload, "no parameter available for Message or payload"); - return MessageBuilder.withPayload(payload).copyHeaders(headers).build(); - } - - public Object[] fromMessage(Message message) { - if (message == null) { - return null; - } - Assert.notNull(message.getPayload(), "Message payload must not be null."); - Object[] args = new Object[this.parameterMetadata.length]; - for (int i = 0; i < this.parameterMetadata.length; i++) { - MethodParameterMetadata metadata = this.parameterMetadata[i]; - Class expectedType = metadata.getParameterType(); - Header headerAnnotation = metadata.getHeaderAnnotation(); - if (metadata == this.payloadParameterMetadata) { - args[i] = message.getPayload(); - } - else if (headerAnnotation != null) { - String headerName = metadata.getHeaderName(); - Object value = message.getHeaders().get(headerName); - if (value == null && headerAnnotation.required()) { - throw new MessageHandlingException(message, "required header '" + headerName + "' not available"); - } - args[i] = value; - } - else if (metadata.isMapOrProperties()) { - if (Properties.class.isAssignableFrom(expectedType)) { - args[i] = this.getStringTypedHeaders(message); - } - else { - args[i] = message.getHeaders(); - } - } - else { - Assert.isTrue(expectedType.isAssignableFrom(message.getClass()) - && Message.class.isAssignableFrom(expectedType), - "Argument is neither header or payload, so it should be of type message."); - args[i] = message; - } - } - return args; - } - - private Properties getStringTypedHeaders(Message message) { - Properties properties = new Properties(); - MessageHeaders headers = message.getHeaders(); - for (String key : headers.keySet()) { - Object value = headers.get(key); - if (value instanceof String) { - properties.setProperty(key, (String) value); - } - } - return properties; - } - - @SuppressWarnings("unchecked") - private void addHeadersAnnotatedParameterToMap(Object value, Map headers) { - Map map = (Map) value; - for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) { - Map.Entry entry = (Map.Entry) iter.next(); - Assert.isTrue(entry.getKey() instanceof String, "Map annotated with @Headers must have String-typed keys"); - headers.put((String) entry.getKey(), entry.getValue()); - } - } - - private static class MethodParameterMetadata { - - private final MethodParameter parameter; - - private volatile Header _headerAnnotation; - - private volatile boolean _hasHeadersAnnotation; - - private MethodParameterMetadata(Method method, int index) { - this.parameter = new MethodParameter(method, index); - Annotation[] annotations = this.parameter.getParameterAnnotations(); - for (Object o : annotations) { - if (o instanceof Header) { - this._headerAnnotation = (Header) o; - } - else if (Headers.class.isInstance(o)) { - Assert.isAssignable(Map.class, this.parameter.getParameterType(), - "parameter with the @Headers annotation must be assignable to java.util.Map"); - this._hasHeadersAnnotation = true; - } - } - } - - Header getHeaderAnnotation() { - return this._headerAnnotation; - } - - boolean hasHeadersAnnotation() { - return this._hasHeadersAnnotation; - } - - boolean isMapOrProperties() { - if (Properties.class.isAssignableFrom(this.parameter.getParameterType()) - || Map.class.isAssignableFrom(this.parameter.getParameterType())) { - return true; - } - return false; - } - - String getHeaderName() { - if (this.getHeaderAnnotation() == null) { - return null; - } - String paramName = this.getHeaderAnnotation().value(); - if (!StringUtils.hasText(paramName)) { - paramName = this.parameter.getParameterName(); - Assert.state(paramName != null, - "No parameter name specified on @Header and unable to discover in class file."); - } - return paramName; - } - - private void initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDiscoverer) { - this.parameter.initParameterNameDiscovery(parameterNameDiscoverer); - } - - private Class getParameterType() { - return this.parameter.getParameterType(); - } - } - -} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodParameterMessageMapperFromMessageTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodParameterMessageMapperFromMessageTests.java index 6da9f7a541..5892ba89bd 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodParameterMessageMapperFromMessageTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodParameterMessageMapperFromMessageTests.java @@ -28,6 +28,7 @@ import org.junit.Test; import org.springframework.integration.annotation.Header; import org.springframework.integration.annotation.Headers; import org.springframework.integration.core.Message; +import org.springframework.integration.handler.MethodArgumentMessageMapper; /** * @author Mark Fisher @@ -38,7 +39,7 @@ public class MethodParameterMessageMapperFromMessageTests { @Test public void fromMessageWithOptionalHeader() throws Exception { Method method = TestService.class.getMethod("optionalHeader", Integer.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Object[] args = mapper.fromMessage(new StringMessage("foo")); assertEquals(1, args.length); assertNull(args[0]); @@ -47,14 +48,14 @@ public class MethodParameterMessageMapperFromMessageTests { @Test(expected = MessageHandlingException.class) public void fromMessageWithRequiredHeaderNotProvided() throws Exception { Method method = TestService.class.getMethod("requiredHeader", Integer.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); mapper.fromMessage(new StringMessage("foo")); } @Test public void fromMessageWithRequiredHeaderProvided() throws Exception { Method method = TestService.class.getMethod("requiredHeader", Integer.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message message = MessageBuilder.withPayload("foo") .setHeader("num", new Integer(123)).build(); Object[] args = mapper.fromMessage(message); @@ -65,7 +66,7 @@ public class MethodParameterMessageMapperFromMessageTests { @Test(expected = MessageHandlingException.class) public void fromMessageWithOptionalAndRequiredHeaderAndOnlyOptionalHeaderProvided() throws Exception { Method method = TestService.class.getMethod("optionalAndRequiredHeader", String.class, Integer.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message message = MessageBuilder.withPayload("foo") .setHeader("prop", "bar").build(); mapper.fromMessage(message); @@ -74,7 +75,7 @@ public class MethodParameterMessageMapperFromMessageTests { @Test public void fromMessageWithOptionalAndRequiredHeaderAndOnlyRequiredHeaderProvided() throws Exception { Method method = TestService.class.getMethod("optionalAndRequiredHeader", String.class, Integer.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message message = MessageBuilder.withPayload("foo") .setHeader("num", new Integer(123)).build(); Object[] args = mapper.fromMessage(message); @@ -86,7 +87,7 @@ public class MethodParameterMessageMapperFromMessageTests { @Test public void fromMessageWithOptionalAndRequiredHeaderAndBothHeadersProvided() throws Exception { Method method = TestService.class.getMethod("optionalAndRequiredHeader", String.class, Integer.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message message = MessageBuilder.withPayload("foo") .setHeader("num", new Integer(123)) .setHeader("prop", "bar") @@ -100,7 +101,7 @@ public class MethodParameterMessageMapperFromMessageTests { @Test public void fromMessageWithPropertiesMethodAndHeadersAnnotation() throws Exception { Method method = TestService.class.getMethod("propertiesHeaders", Properties.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message message = MessageBuilder.withPayload("test") .setHeader("prop1", "foo").setHeader("prop2", "bar").build(); Object[] args = mapper.fromMessage(message); @@ -113,7 +114,7 @@ public class MethodParameterMessageMapperFromMessageTests { @Test public void fromMessageWithPropertiesAndObjectMethod() throws Exception { Method method = TestService.class.getMethod("propertiesHeadersAndPayload", Properties.class, Object.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message message = MessageBuilder.withPayload("test") .setHeader("prop1", "foo").setHeader("prop2", "bar").build(); Object[] args = mapper.fromMessage(message); @@ -128,7 +129,7 @@ public class MethodParameterMessageMapperFromMessageTests { @Test public void fromMessageWithMapAndObjectMethod() throws Exception { Method method = TestService.class.getMethod("mapHeadersAndPayload", Map.class, Object.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message message = MessageBuilder.withPayload("test") .setHeader("prop1", "foo").setHeader("prop2", "bar").build(); Object[] args = mapper.fromMessage(message); @@ -143,7 +144,7 @@ public class MethodParameterMessageMapperFromMessageTests { @Test public void fromMessageWithPropertiesMethodAndPropertiesPayload() throws Exception { Method method = TestService.class.getMethod("propertiesPayload", Properties.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Properties payload = new Properties(); payload.setProperty("prop1", "foo"); payload.setProperty("prop2", "bar"); @@ -160,7 +161,7 @@ public class MethodParameterMessageMapperFromMessageTests { @SuppressWarnings("unchecked") public void fromMessageWithMapMethodAndHeadersAnnotation() throws Exception { Method method = TestService.class.getMethod("mapHeaders", Map.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message message = MessageBuilder.withPayload("test") .setHeader("attrib1", new Integer(123)) .setHeader("attrib2", new Integer(456)).build(); @@ -174,7 +175,7 @@ public class MethodParameterMessageMapperFromMessageTests { @SuppressWarnings("unchecked") public void fromMessageWithMapMethodAndMapPayload() throws Exception { Method method = TestService.class.getMethod("mapPayload", Map.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Map payload = new HashMap(); payload.put("attrib1", new Integer(88)); payload.put("attrib2", new Integer(99)); @@ -189,6 +190,7 @@ public class MethodParameterMessageMapperFromMessageTests { } + @SuppressWarnings("unused") private static class TestService { public String messageOnly(Message message) { diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodParameterMessageMapperInitializationTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodParameterMessageMapperInitializationTests.java index bc48d454ce..86c1b7b153 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodParameterMessageMapperInitializationTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodParameterMessageMapperInitializationTests.java @@ -19,8 +19,8 @@ package org.springframework.integration.message; import java.lang.reflect.Method; import org.junit.Test; - import org.springframework.integration.core.Message; +import org.springframework.integration.handler.MethodArgumentMessageMapper; /** * @author Mark Fisher @@ -30,19 +30,19 @@ public class MethodParameterMessageMapperInitializationTests { @Test(expected = IllegalArgumentException.class) public void messageAndPayload() throws Exception { Method method = TestService.class.getMethod("messageAndPayload", Message.class, String.class); - new MethodParameterMessageMapper(method); + new MethodArgumentMessageMapper(method); } @Test(expected = IllegalArgumentException.class) public void twoMessages() throws Exception { Method method = TestService.class.getMethod("twoMessages", Message.class, Message.class); - new MethodParameterMessageMapper(method); + new MethodArgumentMessageMapper(method); } @Test(expected = IllegalArgumentException.class) public void twoPayloads() throws Exception { Method method = TestService.class.getMethod("twoPayloads", String.class, String.class); - new MethodParameterMessageMapper(method); + new MethodArgumentMessageMapper(method); } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodParameterMessageMapperToMessageTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodParameterMessageMapperToMessageTests.java index d9410f12a4..baa2dae8ce 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodParameterMessageMapperToMessageTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/message/MethodParameterMessageMapperToMessageTests.java @@ -28,6 +28,7 @@ import org.junit.Test; import org.springframework.integration.annotation.Header; import org.springframework.integration.annotation.Headers; import org.springframework.integration.core.Message; +import org.springframework.integration.handler.MethodArgumentMessageMapper; /** * @author Mark Fisher @@ -37,7 +38,7 @@ public class MethodParameterMessageMapperToMessageTests { @Test public void toMessageWithPayload() throws Exception { Method method = TestService.class.getMethod("sendPayload", String.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message message = mapper.toMessage(new Object[] { "test" }); assertEquals("test", message.getPayload()); } @@ -45,14 +46,14 @@ public class MethodParameterMessageMapperToMessageTests { @Test(expected = IllegalArgumentException.class) public void toMessageWithTooManyParameters() throws Exception { Method method = TestService.class.getMethod("sendPayload", String.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); mapper.toMessage(new Object[] { "test" , "oops" }); } @Test(expected = IllegalArgumentException.class) public void toMessageWithEmptyParameterArray() throws Exception { Method method = TestService.class.getMethod("sendPayload", String.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); mapper.toMessage(new Object[] {}); } @@ -60,7 +61,7 @@ public class MethodParameterMessageMapperToMessageTests { public void toMessageWithPayloadAndHeader() throws Exception { Method method = TestService.class.getMethod( "sendPayloadAndHeader", String.class, String.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message message = mapper.toMessage(new Object[] { "test", "bar" }); assertEquals("test", message.getPayload()); assertEquals("bar", message.getHeaders().get("foo")); @@ -70,7 +71,7 @@ public class MethodParameterMessageMapperToMessageTests { public void toMessageWithPayloadAndRequiredHeaderButNullValue() throws Exception { Method method = TestService.class.getMethod( "sendPayloadAndHeader", String.class, String.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); mapper.toMessage(new Object[] { "test", null }); } @@ -78,7 +79,7 @@ public class MethodParameterMessageMapperToMessageTests { public void toMessageWithPayloadAndOptionalHeaderWithValueProvided() throws Exception { Method method = TestService.class.getMethod( "sendPayloadAndOptionalHeader", String.class, String.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message message = mapper.toMessage(new Object[] { "test", "bar" }); assertEquals("test", message.getPayload()); assertEquals("bar", message.getHeaders().get("foo")); @@ -88,7 +89,7 @@ public class MethodParameterMessageMapperToMessageTests { public void toMessageWithPayloadAndOptionalHeaderWithNullValue() throws Exception { Method method = TestService.class.getMethod( "sendPayloadAndOptionalHeader", String.class, String.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message message = mapper.toMessage(new Object[] { "test", null }); assertEquals("test", message.getPayload()); assertNull(message.getHeaders().get("foo")); @@ -98,7 +99,7 @@ public class MethodParameterMessageMapperToMessageTests { public void toMessageWithPayloadAndHeadersMap() throws Exception { Method method = TestService.class.getMethod( "sendPayloadAndHeadersMap", String.class, Map.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Map headers = new HashMap(); headers.put("abc", 123); headers.put("def", 456); @@ -112,7 +113,7 @@ public class MethodParameterMessageMapperToMessageTests { public void toMessageWithPayloadAndNullHeadersMap() throws Exception { Method method = TestService.class.getMethod( "sendPayloadAndHeadersMap", String.class, Map.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message message = mapper.toMessage(new Object[] { "test", null }); assertEquals("test", message.getPayload()); } @@ -121,7 +122,7 @@ public class MethodParameterMessageMapperToMessageTests { public void toMessageWithPayloadAndHeadersMapWithNonStringKey() throws Exception { Method method = TestService.class.getMethod( "sendPayloadAndHeadersMap", String.class, Map.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Map headers = new HashMap(); headers.put(123, "abc"); mapper.toMessage(new Object[] { "test", headers }); @@ -130,7 +131,7 @@ public class MethodParameterMessageMapperToMessageTests { @Test public void toMessageWithMessageParameter() throws Exception { Method method = TestService.class.getMethod("sendMessage", Message.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message inputMessage = MessageBuilder.withPayload("test message").build(); Message message = mapper.toMessage(new Object[] { inputMessage }); assertEquals("test message", message.getPayload()); @@ -139,7 +140,7 @@ public class MethodParameterMessageMapperToMessageTests { @Test public void toMessageWithMessageParameterAndHeader() throws Exception { Method method = TestService.class.getMethod("sendMessageAndHeader", Message.class, String.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message inputMessage = MessageBuilder.withPayload("test message").build(); Message message = mapper.toMessage(new Object[] { inputMessage, "bar" }); assertEquals("test message", message.getPayload()); @@ -149,7 +150,7 @@ public class MethodParameterMessageMapperToMessageTests { @Test(expected = IllegalArgumentException.class) public void toMessageWithMessageParameterAndRequiredHeaderButNullValue() throws Exception { Method method = TestService.class.getMethod("sendMessageAndHeader", Message.class, String.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message inputMessage = MessageBuilder.withPayload("test message").build(); mapper.toMessage(new Object[] { inputMessage, null }); } @@ -157,7 +158,7 @@ public class MethodParameterMessageMapperToMessageTests { @Test public void toMessageWithMessageParameterAndOptionalHeaderWithValue() throws Exception { Method method = TestService.class.getMethod("sendMessageAndOptionalHeader", Message.class, String.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message inputMessage = MessageBuilder.withPayload("test message").build(); Message message = mapper.toMessage(new Object[] { inputMessage, "bar" }); assertEquals("test message", message.getPayload()); @@ -167,7 +168,7 @@ public class MethodParameterMessageMapperToMessageTests { @Test public void toMessageWithMessageParameterAndOptionalHeaderWithNull() throws Exception { Method method = TestService.class.getMethod("sendMessageAndOptionalHeader", Message.class, String.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); Message inputMessage = MessageBuilder.withPayload("test message").build(); Message message = mapper.toMessage(new Object[] { inputMessage, null }); assertEquals("test message", message.getPayload()); @@ -177,14 +178,14 @@ public class MethodParameterMessageMapperToMessageTests { @Test(expected = IllegalArgumentException.class) public void noArgs() throws Exception { Method method = TestService.class.getMethod("noArgs", new Class[] {}); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); mapper.toMessage(new Object[] {}); } @Test(expected = IllegalArgumentException.class) public void onlyHeaders() throws Exception { Method method = TestService.class.getMethod("onlyHeaders", String.class, String.class); - MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method); + MethodArgumentMessageMapper mapper = new MethodArgumentMessageMapper(method); mapper.toMessage(new Object[] { "abc", "def" }); }