IN PROGRESS - issue INT-656: MethodParameterMessageMapper - javadoc vs code

http://jira.springframework.org/browse/INT-656

Made unannotated Properties and Map work, refactored MethodParameterMessageMapper a bit
This commit is contained in:
Iwein Fuld
2009-06-11 18:37:25 +00:00
parent 370158155d
commit d7b9107c77
3 changed files with 141 additions and 87 deletions

View File

@@ -18,9 +18,11 @@ package org.springframework.integration.message;
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;
@@ -36,41 +38,80 @@ import org.springframework.util.StringUtils;
/**
* Prepares arguments for handler methods. The method parameters are matched
* against the Message payload as well as its headers. 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).
* If the {@link Header @Header} annotation is not present, then the parameter
* will typically match the Message payload. However, if a Map or Properties
* object is expected, and the paylaod is not itself assignable 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.
* against the Message payload as well as its headers. 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). If the
* {@link Header @Header} annotation is not present, then the parameter will
* typically match the Message payload. However, if a Map or Properties object
* is expected, and the paylaod is not itself assignable 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.
*
* @author Mark Fisher
* @author Iwein Fuld
*/
public class MethodParameterMessageMapper implements InboundMessageMapper<Object[]>, OutboundMessageMapper<Object[]> {
private final Method method;
private volatile MethodParameterMetadata[] parameterMetadata;
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.initializeParameterMetadata();
parameterMetadata = this.initializeParameterMetadata();
payloadParameterMetadata = getPayloadParameterFrom(parameterMetadata);
}
private MethodParameterMetadata getPayloadParameterFrom(MethodParameterMetadata[] parameterMetadata) {
Set<MethodParameterMetadata> payloadCandidates = new HashSet<MethodParameterMetadata>();
int messagesFound = 0;
for (MethodParameterMetadata metadata : parameterMetadata) {
if (metadata.getHeaderAnnotation() == null && !metadata.hasHeadersAnnotation()
&& !Message.class.isAssignableFrom(metadata.getParameterType())) {
payloadCandidates.add(metadata);
} else if (Message.class.isAssignableFrom(metadata.getParameterType())){
messagesFound++;
}
}
if (payloadCandidates.size() > 1) {
Iterator<MethodParameterMetadata> iterator = payloadCandidates.iterator();
while (iterator.hasNext()) {
Class<?> type = iterator.next().getParameterType();
if (Map.class.isAssignableFrom(type) || Properties.class.isAssignableFrom(type)) {
iterator.remove();
}
}
}
Assert.isTrue(payloadCandidates.size()+messagesFound <= 1, "Could not find at most one message or payload parameter among ["
+ parameterMetadata + "] ended up with the candidates [" + payloadCandidates + "]");
MethodParameterMetadata methodParameterMetadata = payloadCandidates.toArray(new MethodParameterMetadata[1])[0];
return methodParameterMetadata;
}
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, 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);
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>();
@@ -78,7 +119,11 @@ public class MethodParameterMessageMapper implements InboundMessageMapper<Object
Object value = parameters[i];
MethodParameterMetadata metadata = this.parameterMetadata[i];
Header headerAnnotation = metadata.getHeaderAnnotation();
if (headerAnnotation != null) {
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) {
@@ -97,8 +142,8 @@ public class MethodParameterMessageMapper implements InboundMessageMapper<Object
message = (Message<?>) value;
}
else {
Assert.notNull(value, "payload object must not be null");
payload = value;
Assert.isTrue(metadata.isMapOrProperties());
this.addHeadersAnnotatedParameterToMap(value, headers);
}
}
if (message != null) {
@@ -121,51 +166,35 @@ public class MethodParameterMessageMapper implements InboundMessageMapper<Object
MethodParameterMetadata metadata = this.parameterMetadata[i];
Class<?> expectedType = metadata.getParameterType();
Header headerAnnotation = metadata.getHeaderAnnotation();
if (headerAnnotation != null) {
if (metadata == 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");
throw new MessageHandlingException(message, "required header '" + headerName + "' not available");
}
args[i] = value;
}
else if (metadata.hasHeadersAnnotation()) {
else if (metadata.isMapOrProperties()) {
if (Properties.class.isAssignableFrom(expectedType)) {
args[i] = this.getStringTypedHeaders(message);
args[i] = this.getStringTypedHeaders(message);
}
else {
args[i] = message.getHeaders();
args[i] = message.getHeaders();
}
}
else if (expectedType.isAssignableFrom(message.getClass())
&& Message.class.isAssignableFrom(expectedType)) {
args[i] = message;
}
else {
args[i] = message.getPayload();
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 void initializeParameterMetadata() {
boolean foundMessageOrPayload = false;
Class<?>[] paramTypes = this.method.getParameterTypes();
this.parameterMetadata = new MethodParameterMetadata[paramTypes.length];
for (int i = 0; i < parameterMetadata.length; i++) {
MethodParameterMetadata metadata = new MethodParameterMetadata(this.method, i);
metadata.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(metadata, this.method.getDeclaringClass());
if (metadata.getHeaderAnnotation() == null && !metadata.hasHeadersAnnotation()) {
// this is either a Message or the Object to be used as a Message payload
Assert.isTrue(!foundMessageOrPayload, "only one Message or payload parameter is allowed");
foundMessageOrPayload = true;
}
parameterMetadata[i] = metadata;
}
}
private Properties getStringTypedHeaders(Message<?> message) {
Properties properties = new Properties();
MessageHeaders headers = message.getHeaders();
@@ -183,38 +212,45 @@ public class MethodParameterMessageMapper implements InboundMessageMapper<Object
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");
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 extends MethodParameter {
private volatile Header _headerAnnotation;
private volatile boolean _hasHeadersAnnotation;
private MethodParameterMetadata(Method method, int index) {
super(method, index);
}
Header getHeaderAnnotation() {
if (this._headerAnnotation != null) {
return this._headerAnnotation;
}
if (this.getParameterAnnotations() == null) {
return null;
}
for (Object o : this.getParameterAnnotations()) {
if (o instanceof Header) {
this._headerAnnotation = (Header) o;
return this._headerAnnotation;
}
else if (Headers.class.isInstance(o)) {
Assert.isAssignable(Map.class, this.getParameterType(),
"parameter with the @Headers annotation must be assignable to java.util.Map");
this._hasHeadersAnnotation = true;
}
}
return null;
}
Header getHeaderAnnotation() {
return this._headerAnnotation;
}
boolean hasHeadersAnnotation() {
return this._hasHeadersAnnotation;
}
boolean isMapOrProperties() {
if (Properties.class.isAssignableFrom(this.getParameterType())
|| Map.class.isAssignableFrom(this.getParameterType())) {
return true;
}
return false;
}
String getHeaderName() {
@@ -229,24 +265,5 @@ public class MethodParameterMessageMapper implements InboundMessageMapper<Object
}
return paramName;
}
boolean hasHeadersAnnotation() {
if (this._hasHeadersAnnotation) {
return true;
}
if (this.getParameterAnnotations() == null) {
return false;
}
for (Object o : this.getParameterAnnotations()) {
if (Headers.class.isInstance(o)) {
Assert.isAssignable(Map.class, this.getParameterType(),
"parameter with the @Headers annotation must be assignable to java.util.Map");
this._hasHeadersAnnotation = true;
return true;
}
}
return false;
}
}
}

View File

@@ -84,7 +84,7 @@ public class MethodInvokingMessageHandlerTests {
endpoint.setTrigger(new IntervalTrigger(10));
context.registerEndpoint("testEndpoint", endpoint);
context.refresh();
String result = queue.poll(1000, TimeUnit.MILLISECONDS);
String result = queue.poll(2000, TimeUnit.MILLISECONDS);
assertNotNull(result);
assertEquals("testing", result);
context.stop();

View File

@@ -16,8 +16,7 @@
package org.springframework.integration.message;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import java.util.HashMap;
@@ -32,6 +31,7 @@ import org.springframework.integration.core.Message;
/**
* @author Mark Fisher
* @author Iwein Fuld
*/
public class MethodParameterMessageMapperFromMessageTests {
@@ -109,6 +109,36 @@ public class MethodParameterMessageMapperFromMessageTests {
assertEquals("foo", result.getProperty("prop1"));
assertEquals("bar", result.getProperty("prop2"));
}
@Test
public void fromMessageWithPropertiesAndObjectMethod() throws Exception {
Method method = TestService.class.getMethod("propertiesHeadersAndPayload", Properties.class, Object.class);
MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method);
Message<String> message = MessageBuilder.withPayload("test")
.setHeader("prop1", "foo").setHeader("prop2", "bar").build();
Object[] args = mapper.fromMessage(message);
Properties result = (Properties) args[0];
assertEquals(2, result.size());
assertEquals("foo", result.getProperty("prop1"));
assertEquals("bar", result.getProperty("prop2"));
assertEquals("test", args[1]);
}
@SuppressWarnings("unchecked")
@Test
public void fromMessageWithMapAndObjectMethod() throws Exception {
Method method = TestService.class.getMethod("mapHeadersAndPayload", Map.class, Object.class);
MethodParameterMessageMapper mapper = new MethodParameterMessageMapper(method);
Message<String> message = MessageBuilder.withPayload("test")
.setHeader("prop1", "foo").setHeader("prop2", "bar").build();
Object[] args = mapper.fromMessage(message);
Map result = (Map) args[0];
//Map also contains id and timestamp
assertEquals(4, result.size());
assertEquals("foo", result.get("prop1"));
assertEquals("bar", result.get("prop2"));
assertEquals("test", args[1]);
}
@Test
public void fromMessageWithPropertiesMethodAndPropertiesPayload() throws Exception {
@@ -192,6 +222,10 @@ public class MethodParameterMessageMapperFromMessageTests {
public Properties propertiesHeaders(@Headers Properties properties) {
return properties;
}
public Object propertiesHeadersAndPayload(Properties headers, Object payload) {
return payload;
}
@SuppressWarnings("unchecked")
public Map mapPayload(Map map) {
@@ -202,11 +236,14 @@ public class MethodParameterMessageMapperFromMessageTests {
public Map mapHeaders(@Headers Map map) {
return map;
}
@SuppressWarnings("unchecked")
public Object mapHeadersAndPayload(Map headers, Object payload) {
return payload;
}
public Integer integerMethod(Integer i) {
return i;
}
}
}