INT-681, INT-710 more refactoring of code for mapping between Messages and argument arrays

This commit is contained in:
Mark Fisher
2009-09-29 17:29:52 +00:00
parent 047b31d759
commit 0e8aa07748
7 changed files with 337 additions and 373 deletions

View File

@@ -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());
}

View File

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

View File

@@ -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 <i>from</i> a Message
* to an argument array when invoking handler methods, and mapping <i>to</i> a
* Message from an argument array when invoking gateway methods.
* <p/>
* 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.
* <p/>
* 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).
* <p/>
* 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).
* <p/>
* 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.
* <p/>
* Some examples of legal method signatures:<br/>
* <tt>public void dealWith(Object payload);</tt><br/>
* <tt>public void dealWith(Message message);</tt><br/>
* <tt>public void dealWith(@Header String myHeader, Object payload);</tt><br/>
* <tt>public void dealWith(@Header String myHeader, @Header String anotherHeader);</tt>
* <br/>
* <tt>public void dealWith(@Headers Map headers, Object payload);</tt><br/>
* <tt>public void dealWith(@Headers Properties headers, Map payload);</tt><br/>
* <tt>public void dealWith(Properties headers, Object payload);</tt><br/>
* <p/>
* Some examples of illegal method signatures: <br/>
* <tt>public void dealWith(Object payload, String payload);</tt><br/>
* <tt>public void dealWith(Message message, Object payload);</tt><br/>
* <tt>public void dealWith(Properties headers, Map payload);</tt><br/>
*
* @author Mark Fisher
* @author Iwein Fuld
* @since 2.0
*/
public class MethodArgumentMessageMapper implements OutboundMessageMapper<Object[]> {
public class MethodArgumentMessageMapper implements InboundMessageMapper<Object[]>, OutboundMessageMapper<Object[]> {
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<String, Object> headers = new HashMap<String, Object>();
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<Object
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<String, Object> 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<MethodParameterMetadata> payloadCandidates = new HashSet<MethodParameterMetadata>();
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<MethodParameterMetadata> 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();
}
}
}

View File

@@ -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.
* <p/>
* 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).
* <p/>
* 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).
* <p/>
* 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.
* <p/>
* Some examples of legal method signatures:<br/>
* <tt>public void dealWith(Object payload);</tt><br/>
* <tt>public void dealWith(Message message);</tt><br/>
* <tt>public void dealWith(@Header String myHeader, Object payload);</tt><br/>
* <tt>public void dealWith(@Header String myHeader, @Header String anotherHeader);</tt>
* <br/>
* <tt>public void dealWith(@Headers Map headers, Object payload);</tt><br/>
* <tt>public void dealWith(@Headers Properties headers, Map payload);</tt><br/>
* <tt>public void dealWith(Properties headers, Object payload);</tt><br/>
* <p/>
* Some examples of illegal method signatures: <br/>
* <tt>public void dealWith(Object payload, String payload);</tt><br/>
* <tt>public void dealWith(Message message, Object payload);</tt><br/>
* <tt>public void dealWith(Properties headers, Map payload);</tt><br/>
*
*
* @author Mark Fisher
* @author Iwein Fuld
*/
public class MethodParameterMessageMapper implements InboundMessageMapper<Object[]>, OutboundMessageMapper<Object[]> {
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<MethodParameterMetadata> payloadCandidates = new HashSet<MethodParameterMetadata>();
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<MethodParameterMetadata> 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<String, Object> headers = new HashMap<String, Object>();
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<String, Object> 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();
}
}
}

View File

@@ -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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String, Integer> payload = new HashMap<String, Integer>();
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) {

View File

@@ -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);
}

View File

@@ -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<String, Object> headers = new HashMap<String, Object>();
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<Integer, String> headers = new HashMap<Integer, String>();
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" });
}