Polished up ArgumentArrayMessageMapper (related to INT-681)

This commit is contained in:
Oleg Zhurakousky
2009-10-08 03:56:26 +00:00
parent fbac7b1c13
commit 26587d83ad

View File

@@ -13,12 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.handler;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
@@ -31,6 +30,7 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.expression.Expression;
@@ -108,16 +108,27 @@ public class ArgumentArrayMessageMapper implements InboundMessageMapper<Object[]
private final ExpressionParser expressionParser = new SpelExpressionParser();
private final Method method;
private List<MethodParameter> parameterList;
private GenericConversionService conversionService;
private static GenericConversionService conversionService;
static{ // see INT-829
conversionService = new DefaultConversionService();
conversionService.removeConvertible(Object.class, Map.class);
conversionService.removeConvertible(Map.class, Object.class);
conversionService.removeConvertible(Object.class, String.class);
conversionService.addConverter(new Converter<Number, String>() {
public String convert(Number source) {return null;}
});
conversionService.addConverter(new Converter<Date, String>() {
public String convert(Date source) {return null;}
});
}
/**
*
* @param method
*/
public ArgumentArrayMessageMapper(Method method){
Assert.notNull(method, "Can not create an instance of " + this.getClass().getName() + " with 'null' value");
this.method = method;
parameterList = this.getMethodParameterList(method);
conversionService = new DefaultConversionService();
conversionService.removeConvertible(Object.class, Map.class);
}
/**
*
@@ -125,8 +136,9 @@ public class ArgumentArrayMessageMapper implements InboundMessageMapper<Object[]
* @param method
* @return
*/
public Object[] fromMessage(Message<?> message){
this.validateSignatureToMessage(message);
public Object[] fromMessage(Message<?> message){
Assert.notNull(message, "Can not map Message with 'null' value");
this.validateMessageMapppings(message);
Map<String, Object> messageArgumentsMap = this.mapMessageToArguments(parameterList, message);
return messageArgumentsMap.values().toArray();
}
@@ -140,34 +152,33 @@ public class ArgumentArrayMessageMapper implements InboundMessageMapper<Object[]
Map<String, Object> messageArgumentsMap = new java.util.LinkedHashMap<String, Object>();
for (MethodParameter methodParameter : parameterList) {
String parameterName = methodParameter.getParameterName();
String expression = parameterName;
String[] expressions = null;
Annotation[] annotations = methodParameter.getParameterAnnotations();
Object value = null;
if (annotations.length == 0){
String[] expressions = null;
if ("headers".equals(parameterName) || "payload".equals(parameterName)){
expressions = new String[]{parameterName};
} else if ("message".equals(parameterName)){
expressions = new String[]{"#this", "payload"};
expressions = new String[]{"#this", "payload"}; // just in case if 'parameterName' is 'message' but type is not Message
} else {
expressions = new String[]{"payload."+parameterName, "headers."+parameterName, "payload", "headers", "#this"};
}
value = this.processELExpressionAndGetValue(message, methodParameter.getParameterType(), expressions);
value = this.getValueFromMessageBasedOnEL(message, methodParameter.getParameterType(), false, expressions);
} else {
// for now support only single annotation per parameter
if (annotations[0].annotationType().isAssignableFrom(Header.class)){
value = this.mapHeaderThruAnnotation(annotations[0], message, methodParameter, null)[1];
} else if (annotations[0].annotationType().isAssignableFrom(MessageMapping.class)){
expression = (String) AnnotationUtils.getAnnotationAttributes(annotations[0]).get("expression");
value = this.processELExpressionAndGetValue(message, methodParameter.getParameterType(), expression);
expressions = new String[]{(String) AnnotationUtils.getAnnotationAttributes(annotations[0]).get("expression")};
value = this.getValueFromMessageBasedOnEL(message, methodParameter.getParameterType(), true, expressions);
} else if (annotations[0].annotationType().isAssignableFrom(Headers.class)){
expression = "headers";
value = this.processELExpressionAndGetValue(message, methodParameter.getParameterType(), expression);
expressions = new String[]{"headers"};
value = this.getValueFromMessageBasedOnEL(message, methodParameter.getParameterType(), false, expressions);
} else {
throw new IllegalArgumentException("unknown or unsupported annotation: " + annotations[0]);
}
}
messageArgumentsMap.put(methodParameter.getParameterIndex()+":"+expression, value);
messageArgumentsMap.put(methodParameter.getParameterIndex()+":"+parameterName, value);
}
return messageArgumentsMap;
}
@@ -176,7 +187,8 @@ public class ArgumentArrayMessageMapper implements InboundMessageMapper<Object[]
* @param arguments
* @return
*/
public Message<?> toMessage(Object[] arguments){
public Message<?> toMessage(Object[] arguments){
Assert.notNull(arguments, "Can not map to 'null' arguments to Message");
if (arguments.length > parameterList.size()) {
throw new IllegalArgumentException("Too many parameters provided for: " + method);
} else if (arguments.length < parameterList.size()){
@@ -260,12 +272,12 @@ public class ArgumentArrayMessageMapper implements InboundMessageMapper<Object[]
* @return
*/
private Object[] mapHeaderThruAnnotation(Annotation header, Message<?> message, MethodParameter methodParameter, Object headerValue){
String value = (String) AnnotationUtils.getValue(header);
String headerName = StringUtils.hasText(value) ? value : methodParameter.getParameterName();
String valueAttribute = (String) AnnotationUtils.getValue(header);
String headerName = StringUtils.hasText(valueAttribute) ? valueAttribute : methodParameter.getParameterName();
Assert.notNull(headerName, "Can not determine header name. Possible reasons: -debug is being " +
"disabled or header name is not explicitelt prvided in @Header annotation");
"disabled or header name is not explicitely provided via @Header annotation");
if (message != null){
headerValue = this.processELExpressionAndGetValue(message, methodParameter.getParameterType(), "headers." + headerName);
headerValue = this.getValueFromMessageBasedOnEL(message, methodParameter.getParameterType(), false, "headers." + headerName);
}
this.evauateHeader(header, headerName, headerValue, message);
return new Object[]{headerName, headerValue};
@@ -293,31 +305,29 @@ public class ArgumentArrayMessageMapper implements InboundMessageMapper<Object[]
* @return
*/
@SuppressWarnings("unchecked")
private Object processELExpressionAndGetValue(Message message, Class returnedType, String... expressions){
private Object getValueFromMessageBasedOnEL(Message message, Class targetType, boolean rethrowException, String... expressions){
Object value = null;
returnedType = new TypeDescriptor(returnedType).getObjectType();
targetType = new TypeDescriptor(targetType).getObjectType(); //converts primitives to Object wrappers
for (String expression : expressions) {
StandardEvaluationContext context = new StandardEvaluationContext(message);
try {
Expression exp = expressionParser.parseExpression(expression);
context.addPropertyAccessor(new MapAccessor());
try {
Expression exp = expressionParser.parseExpression(expression);
context.addPropertyAccessor(new MapAccessor());
value = exp.getValue(context);
if ((value != null && expression.equals("headers")) || (value instanceof Map || value instanceof Properties)){
value = context.getTypeConverter().convertValue(new HashMap((Map)value), new TypeDescriptor(returnedType));
}
} catch (Throwable e) {/*do nothing*/}
if (value != null){
if ( (returnedType.isAssignableFrom(value.getClass()) || this.payloadLooksLikeMapOrProperties(message)) ||
(expression.equals("payload") && conversionService.canConvert(value.getClass(), returnedType)) ||
(expression.equals("#this") && returnedType.isAssignableFrom(value.getClass()))
){
if (value != null && this.isConvertable(value, targetType)) {
value = expression.equals("headers") ? conversionService.convert(value, targetType) : value;// to accomodate Map->Properties conversion
break;
} else {
value = null;
}
} catch (Throwable e) {
if (rethrowException){
throw new MessageHandlingException(message, e);
}
}
}
return value;
}
/**
*
* @param method
@@ -334,39 +344,36 @@ public class ArgumentArrayMessageMapper implements InboundMessageMapper<Object[]
}
return parameterList;
}
/**
*
* @return
*/
private void validateSignatureToMessage(Message<?> message){
if (parameterList.size() > 1){
if (message != null){
if (message.getPayload() instanceof Map<?,?> && parameterList.size() > 1){
boolean ambigutePayload = true;
for (MethodParameter parameter : parameterList) {
if (parameter.getParameterAnnotations().length > 0 || !parameter.getParameterType().isAssignableFrom(Map.class)){
ambigutePayload = false;
break;
}
}
if (ambigutePayload){
throw new MessageHandlingException(message, "Ambiguite signature. Many Payload candidates: " + method);
}
}
}
}
}
/**
*
* @param message
* @return
*/
@SuppressWarnings("unchecked")
private boolean payloadLooksLikeMapOrProperties(Message<?> message){
Object payload = message.getPayload();
if (payload instanceof String && ((String)payload).indexOf("=") > -1){
return true;
private boolean isConvertable(Object value, Class targetType){
return conversionService.canConvert(value.getClass(), targetType) ||
(value instanceof String && ((String)value).indexOf("=") > 1); // to address payload String "foo=bar"
// mapping to Properties/Map
}
/**
*
*/
@SuppressWarnings("unchecked")
public void validateMessageMapppings(Message<?> message){ // validate against maps with no annotations
int counterOfUnnamedMapAttributes = 0;
if (message.getPayload() instanceof Map){
for (MethodParameter parameter : parameterList) {
if ( parameter.getParameterAnnotations().length == 0 &&
(parameter.getParameterType().isAssignableFrom(Properties.class) || parameter.getParameterType().isAssignableFrom(Map.class)) &&
!(parameter.getParameterName().equals("payload") || parameter.getParameterName().equals("headers")) ) {
counterOfUnnamedMapAttributes++;
}
}
if (counterOfUnnamedMapAttributes > 1){
throw new IllegalArgumentException("Ambiguate parameters. Can not determine parameter mappings between Method: " + method +
" and Message: " + message +
". Try annotating individual parameters with @Headers, @Header or @MessageMapping");
}
}
return (message.getPayload() instanceof Map || message.getPayload() instanceof Properties);
}
}