diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingHandler.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingHandler.java deleted file mode 100644 index 456280f9b4..0000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingHandler.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2002-2008 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.adapter; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.integration.handler.HandlerMethodInvoker; -import org.springframework.integration.handler.MessageHandler; -import org.springframework.integration.message.DefaultMessageCreator; -import org.springframework.integration.message.DefaultMessageMapper; -import org.springframework.integration.message.Message; -import org.springframework.integration.message.MessageCreator; -import org.springframework.integration.message.MessageMapper; -import org.springframework.util.Assert; - -/** - * A {@link MessageHandler} that invokes the specified method on the provided object. - * - * @author Mark Fisher - */ -public class MethodInvokingHandler implements MessageHandler, InitializingBean { - - private volatile Object object; - - private volatile String methodName; - - private volatile MessageMapper messageMapper = new DefaultMessageMapper(); - - private volatile MessageCreator messageCreator = new DefaultMessageCreator(); - - protected volatile HandlerMethodInvoker invoker; - - private volatile boolean initialized; - - private final Object initMonitor = new Object(); - - - public void setObject(Object object) { - Assert.notNull(object, "'object' must not be null"); - this.object = object; - } - - public void setMethodName(String methodName) { - Assert.notNull(methodName, "'methodName' must not be null"); - this.methodName = methodName; - } - - public void setMessageMapper(MessageMapper messageMapper) { - Assert.notNull(messageMapper, "'messageMapper' must not be null"); - this.messageMapper = messageMapper; - } - - public void setMessageCreator(MessageCreator messageCreator) { - Assert.notNull(messageCreator, "'messageCreator' must not be null"); - this.messageCreator = messageCreator; - } - - public void afterPropertiesSet() { - synchronized (this.initMonitor) { - if (this.initialized) { - return; - } - this.invoker = new HandlerMethodInvoker(this.object, this.methodName); - this.initialized = true; - } - } - - public Message handle(Message message) { - if (!this.initialized) { - this.afterPropertiesSet(); - } - Object args = this.messageMapper.mapMessage(message); - Object result = this.invoker.invokeMethod(args); - if (result == null) { - return null; - } - return this.messageCreator.createMessage(result); - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingSource.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingSource.java index 3bcbdded22..fe8805c46b 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingSource.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingSource.java @@ -16,15 +16,17 @@ package org.springframework.integration.adapter; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.springframework.beans.factory.InitializingBean; import org.springframework.integration.ConfigurationException; -import org.springframework.integration.handler.HandlerMethodInvoker; import org.springframework.integration.message.GenericMessage; import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessagingException; import org.springframework.integration.message.Source; import org.springframework.integration.util.MethodValidator; +import org.springframework.integration.util.NameResolvingMethodInvoker; import org.springframework.util.Assert; /** @@ -33,16 +35,16 @@ import org.springframework.util.Assert; * * @author Mark Fisher */ -public class MethodInvokingSource implements Source, InitializingBean { +public class MethodInvokingSource implements Source, InitializingBean { - private T object; + private Object object; private String method; - private HandlerMethodInvoker invoker; + private NameResolvingMethodInvoker invoker; - public void setObject(T object) { + public void setObject(Object object) { Assert.notNull(object, "'object' must not be null"); this.object = object; } @@ -53,7 +55,7 @@ public class MethodInvokingSource implements Source, InitializingBean } public void afterPropertiesSet() { - this.invoker = new HandlerMethodInvoker(this.object, this.method); + this.invoker = new NameResolvingMethodInvoker(this.object, this.method); this.invoker.setMethodValidator(new MessageReceivingMethodValidator()); } @@ -61,7 +63,15 @@ public class MethodInvokingSource implements Source, InitializingBean if (this.invoker == null) { this.afterPropertiesSet(); } - return new GenericMessage(this.invoker.invokeMethod(new Object[] {})); + try { + return new GenericMessage(this.invoker.invokeMethod(new Object[] {})); + } catch (InvocationTargetException e) { + throw new MessagingException( + "Source method '" + this.method + "' threw an Exception.", e.getTargetException()); + } + catch (Throwable e) { + throw new MessagingException("Failed to invoke source method '" + this.method + "'."); + } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingTarget.java b/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingTarget.java index 6cd60ec2a1..45f685ca5e 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingTarget.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/adapter/MethodInvokingTarget.java @@ -16,35 +16,30 @@ package org.springframework.integration.adapter; -import java.lang.reflect.Method; - -import org.springframework.integration.ConfigurationException; +import org.springframework.integration.handler.AbstractMessageHandlerAdapter; import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessagingException; import org.springframework.integration.message.Target; -import org.springframework.integration.util.MethodValidator; /** * A messaging target that invokes the specified method on the provided object. * * @author Mark Fisher */ -public class MethodInvokingTarget extends MethodInvokingHandler implements Target { - - @Override - public void afterPropertiesSet() { - super.afterPropertiesSet(); - this.invoker.setMethodValidator(new MethodValidator() { - public void validate(Method method) throws Exception { - if (!method.getReturnType().equals(void.class)) { - throw new ConfigurationException("target method must have a void return"); - } - } - }); - } +public class MethodInvokingTarget extends AbstractMessageHandlerAdapter implements Target { public boolean send(Message message) { this.handle(message); return true; } + @Override + protected Message handleReturnValue(Object returnValue, Message originalMessage) { + if (returnValue != null) { + throw new MessagingException(originalMessage, "The target method returned a non-null Object. " + + "MethodInvokingTarget should only be used for methods that return no value (preferably void)."); + } + return null; + } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/MessageEndpointAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/MessageEndpointAnnotationPostProcessor.java index 9b3ca080c9..a9244d3303 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/MessageEndpointAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/MessageEndpointAnnotationPostProcessor.java @@ -156,7 +156,7 @@ public class MessageEndpointAnnotationPostProcessor implements BeanPostProcessor int period = polledAnnotation.period(); long initialDelay = polledAnnotation.initialDelay(); boolean fixedRate = polledAnnotation.fixedRate(); - MethodInvokingSource source = new MethodInvokingSource(); + MethodInvokingSource source = new MethodInvokingSource(); source.setObject(bean); source.setMethod(method.getName()); SynchronousChannel channel = new SynchronousChannel(); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractMessageHandlerAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractMessageHandlerAdapter.java index 080482425d..c8901e47e8 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractMessageHandlerAdapter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractMessageHandlerAdapter.java @@ -16,48 +16,66 @@ package org.springframework.integration.handler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.InitializingBean; -import org.springframework.core.Ordered; -import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.ConfigurationException; +import org.springframework.integration.message.DefaultMessageCreator; +import org.springframework.integration.message.DefaultMessageMapper; import org.springframework.integration.message.Message; -import org.springframework.integration.message.MessageHeader; +import org.springframework.integration.message.MessageCreator; +import org.springframework.integration.message.MessageMapper; +import org.springframework.integration.message.MessagingException; +import org.springframework.integration.util.DefaultMethodInvoker; +import org.springframework.integration.util.MethodInvoker; +import org.springframework.integration.util.NameResolvingMethodInvoker; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; /** - * Base implementation of the {@link MessageHandler} interface that creates an - * invoker for the specified method and target object. It also accepts an - * implementation of the {@link MessageMapper} strategy which it exposes to - * subclasses for converting the {@link Message} to an object. Likewise, if the - * method has a non-null return value, a reply message will be generated by the - * mapper. + * An implementation of the {@link MessageHandler} interface that invokes the specified method and target object. Either + * a {@link Method} reference or a 'methodName' may be provided, but both are not necessary. In fact, while preference + * is given to a {@link Method} reference if available, an Exception will be thrown if a non-matching 'methodName' has + * also been provided. Therefore, to avoid such ambiguity, it is recommended to provide just one or the other. + *

+ * This handler also accepts an implementation of the {@link MessageMapper} strategy interface which it uses for + * converting from the {@link Message} being handled to an Object prior to invoking the method. Likewise, if the method + * has a non-null return value, a reply message will be generated by the configured implementation of the + * {@link MessageCreator} strategy interface. In both cases, the default implementations will simply consider the + * message's payload. * * @author Mark Fisher */ -public abstract class AbstractMessageHandlerAdapter implements MessageHandler, Ordered, InitializingBean { +public abstract class AbstractMessageHandlerAdapter implements MessageHandler, InitializingBean { public static final String OUTPUT_CHANNEL_NAME_KEY = "outputChannelName"; protected final Log logger = LogFactory.getLog(this.getClass()); - private volatile T object; + private volatile Object object; + + private volatile Method method; private volatile String methodName; - private volatile HandlerMethodInvoker invoker; + private volatile boolean methodExpectsMessage; - private volatile int order = Integer.MAX_VALUE; + private volatile MessageMapper messageMapper = new DefaultMessageMapper(); + + private volatile MessageCreator messageCreator = new DefaultMessageCreator(); + + protected volatile MethodInvoker invoker; private volatile boolean initialized; - private final Object lifecycleMonitor = new Object(); + private final Object initializationMonitor = new Object(); - public void setObject(T object) { - Assert.notNull(object, "'object' must not be null"); + public void setObject(Object object) { this.object = object; } @@ -65,64 +83,112 @@ public abstract class AbstractMessageHandlerAdapter implements MessageHandler return this.object; } + public void setMethod(Method method) { + this.method = method; + } + + protected Method getMethod() { + return this.method; + } + public void setMethodName(String methodName) { - Assert.notNull(methodName, "'methodName' must not be null"); this.methodName = methodName; } - public void setOrder(int order) { - this.order = order; + public void setMethodExpectsMessage(boolean methodExpectsMessage) { + this.methodExpectsMessage = methodExpectsMessage; } - public int getOrder() { - return this.order; + public void setMessageMapper(MessageMapper messageMapper) { + Assert.notNull(messageMapper, "'messageMapper' must not be null"); + this.messageMapper = messageMapper; } - public final void afterPropertiesSet() { - synchronized (this.lifecycleMonitor) { + public void setMessageCreator(MessageCreator messageCreator) { + Assert.notNull(messageCreator, "'messageCreator' must not be null"); + this.messageCreator = messageCreator; + } + + public void afterPropertiesSet() { + synchronized (this.initializationMonitor) { if (this.initialized) { return; } - this.invoker = new HandlerMethodInvoker(this.object, this.methodName); + if (this.object == null) { + throw new ConfigurationException("The target 'object' must not be null."); + } + if (this.method == null && this.methodName == null) { + throw new ConfigurationException("Either a 'method' or 'methodName' is required."); + } + if (this.method != null) { + if (this.methodName != null && !this.methodName.equals(this.method.getName())) { + throw new ConfigurationException("An ambiguity exists between the 'method' and 'methodName' properties. " + + "Note that only one of them is required, but if both are provided they must match."); + } + this.invoker = new DefaultMethodInvoker(this.object, this.method); + } + else { + this.invoker = new NameResolvingMethodInvoker(this.object, this.methodName); + } this.initialized = true; } this.initialize(); } - public final Message handle(Message message) { - if (!this.initialized) { - this.afterPropertiesSet(); - } - Object result = this.doHandle(message, invoker); - if (result != null) { - Message reply = (result instanceof Message) ? (Message) result : - this.createReplyMessage(result, message.getHeader()); - Object correlationId = reply.getHeader().getCorrelationId(); - if (correlationId == null) { - Object orginalCorrelationId = message.getHeader().getCorrelationId(); - reply.getHeader().setCorrelationId((orginalCorrelationId != null) ? - orginalCorrelationId : message.getId()); - } - return reply; - } - return null; - } - /** - * Subclasses may override this method to provide additional initialization. + * Subclasses may override this method for custom initialization requirements. */ protected void initialize() { } - protected Message createReplyMessage(Object payload, MessageHeader originalMessageHeader) { - return new GenericMessage(payload, originalMessageHeader); + public Message handle(Message message) { + if (message == null) { + throw new IllegalArgumentException("message must not be null"); + } + if (!this.initialized) { + this.afterPropertiesSet(); + } + Object args[] = null; + Object mappingResult = (this.methodExpectsMessage) ? message : this.messageMapper.mapMessage(message); + if (mappingResult.getClass().isArray()) { + args = (Object[]) mappingResult; + } + else { + args = new Object[] { mappingResult }; + } + try { + Object result = this.invoker.invokeMethod(args); + if (result == null) { + return null; + } + return this.handleReturnValue(result, message); + } catch (InvocationTargetException e) { + throw new MessagingException( + "Handler method '" + this.method + "' threw an Exception.", e.getTargetException()); + } + catch (Throwable e) { + throw new MessagingException("Failed to invoke handler method '" + this.method + + "' with arguments: " + ObjectUtils.nullSafeToString(args), e); + } + } + + protected Message createReplyMessage(Object returnValue, Message originalMessage) { + Message reply = this.messageCreator.createMessage(returnValue); + if (reply != null) { + reply.copyHeader(originalMessage.getHeader(), false); + Object correlationId = reply.getHeader().getCorrelationId(); + if (correlationId == null) { + Object orginalCorrelationId = originalMessage.getHeader().getCorrelationId(); + reply.getHeader().setCorrelationId((orginalCorrelationId != null) ? + orginalCorrelationId : originalMessage.getId()); + } + } + return reply; } /** - * Subclasses must implement this method. The invoker has been created for - * the provided target object and method. May return an object of type - * {@link Message}, else rely on the message mapper to convert. + * Subclasses must implement this method to handle the return value. */ - protected abstract Object doHandle(Message message, HandlerMethodInvoker invoker); + protected abstract Message handleReturnValue(Object returnValue, Message originalMessage); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/DefaultMessageHandlerAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/DefaultMessageHandlerAdapter.java index cbe01c9956..10fb1b1826 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/DefaultMessageHandlerAdapter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/DefaultMessageHandlerAdapter.java @@ -16,39 +16,20 @@ package org.springframework.integration.handler; -import org.springframework.core.Ordered; import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageCreator; /** - * An implementation of {@link MessageHandler} that invokes the specified method - * on the provided target object. If {@link #shouldUseMapperOnInvocation} is set - * to true (the default), it will use the provided - * {@link org.springframework.integration.message.MessageMapper} strategy to - * convert the inbound {@link Message} to an object that will be passed as the - * method parameter. If the method has a non-null return value, a reply message - * will be generated by the mapper. + * An implementation of {@link MessageHandler} that invokes the specified method and target object. + * It will use the provided implementation of the {@link MessageCreator} strategy interface to convert + * the method invocation's return value to a reply Message. * * @author Mark Fisher */ -public class DefaultMessageHandlerAdapter extends AbstractMessageHandlerAdapter implements Ordered { +public class DefaultMessageHandlerAdapter extends AbstractMessageHandlerAdapter { - private boolean expectsMessage = false; - - /** - * Specify whether the handler should pass the message when invoking the - * target method. The default is false indicating that the - * message's payload should be passed as the argument. To force - * passing the {@link Message} directly, set this to true. - */ - public void setExpectsMessage(boolean expectsMessage) { - this.expectsMessage = expectsMessage; - } - - public Object doHandle(Message message, HandlerMethodInvoker invoker) { - if (this.expectsMessage) { - return invoker.invokeMethod(message); - } - return invoker.invokeMethod(message.getPayload()); + protected Message handleReturnValue(Object returnValue, Message originalMessage) { + return this.createReplyMessage(returnValue, originalMessage); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/HandlerMethodInvoker.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/HandlerMethodInvoker.java deleted file mode 100644 index dbe69b88c6..0000000000 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/HandlerMethodInvoker.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2002-2008 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.handler; - -import java.lang.reflect.InvocationTargetException; - -import org.springframework.beans.support.ArgumentConvertingMethodInvoker; -import org.springframework.integration.message.MessagingException; -import org.springframework.integration.util.MethodValidator; -import org.springframework.util.Assert; -import org.springframework.util.MethodInvoker; -import org.springframework.util.ObjectUtils; - -/** - * A simple wrapper for {@link MethodInvoker}. - * - * @author Mark Fisher - */ -public class HandlerMethodInvoker { - - private T object; - - private String method; - - private MethodValidator methodValidator; - - - public HandlerMethodInvoker(T object, String method) { - Assert.notNull(object, "'object' must not be null"); - Assert.notNull(method, "'method' must not be null"); - this.object = object; - this.method = method; - } - - public void setMethodValidator(MethodValidator methodValidator) { - this.methodValidator = methodValidator; - } - - public Object invokeMethod(Object ... args) { - try { - MethodInvoker methodInvoker = new ArgumentConvertingMethodInvoker(); - methodInvoker.setTargetObject(this.object); - methodInvoker.setTargetMethod(this.method); - methodInvoker.setArguments(args); - methodInvoker.prepare(); - methodInvoker.getPreparedMethod().setAccessible(true); - if (this.methodValidator != null) { - this.methodValidator.validate(methodInvoker.getPreparedMethod()); - } - return methodInvoker.invoke(); - } - catch (InvocationTargetException e) { - throw new MessagingException( - "Method '" + this.method + "' threw an Exception.", e.getTargetException()); - } - catch (Throwable e) { - throw new MessagingException("Failed to invoke method '" + this.method + - "' with arguments: " + ObjectUtils.nullSafeToString(args), e); - } - } - -} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/annotation/AnnotationMethodMessageMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/annotation/AnnotationMethodMessageMapper.java new file mode 100644 index 0000000000..82dddacee8 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/annotation/AnnotationMethodMessageMapper.java @@ -0,0 +1,189 @@ +/* + * Copyright 2002-2008 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.handler.annotation; + +import java.lang.reflect.Method; +import java.util.HashMap; +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.ConfigurationException; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageHandlingException; +import org.springframework.integration.message.MessageHeader; +import org.springframework.integration.message.MessageMapper; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * @author Mark Fisher + */ +public class AnnotationMethodMessageMapper implements MessageMapper { + + private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); + + private final Method method; + + private MethodParameterMetadata[] parameterMetadata; + + private volatile boolean initialized; + + private final Object initializationMonitor = new Object(); + + + public AnnotationMethodMessageMapper(Method method) { + Assert.notNull(method, "method must not be null"); + this.method = method; + } + + + public void initialize() { + synchronized (this.initializationMonitor) { + if (this.initialized) { + return; + } + Class[] paramTypes = this.method.getParameterTypes(); + this.parameterMetadata = new MethodParameterMetadata[paramTypes.length]; + for (int i = 0; i < parameterMetadata.length; i++) { + MethodParameter methodParam = new MethodParameter(this.method, i); + methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer); + GenericTypeResolver.resolveParameterType(methodParam, this.method.getDeclaringClass()); + Object[] paramAnns = methodParam.getParameterAnnotations(); + String attributeName = null; + String propertyName = null; + for (int j = 0; j < paramAnns.length; j++) { + Object paramAnn = paramAnns[j]; + if (HeaderAttribute.class.isInstance(paramAnn)) { + HeaderAttribute headerAttribute = (HeaderAttribute) paramAnn; + attributeName = this.resolveParameterNameIfNecessary(headerAttribute.value(), methodParam); + parameterMetadata[i] = new MethodParameterMetadata(HeaderAttribute.class, attributeName, headerAttribute.required()); + } + else if (HeaderProperty.class.isInstance(paramAnn)) { + HeaderProperty headerProperty = (HeaderProperty) paramAnn; + propertyName = this.resolveParameterNameIfNecessary(headerProperty.value(), methodParam); + parameterMetadata[i] = new MethodParameterMetadata(HeaderProperty.class, propertyName, headerProperty.required()); + } + } + if (attributeName != null && propertyName != null) { + throw new ConfigurationException("The @HeaderAttribute and @HeaderProperty annotations " + + "are mutually exclusive. They should not both be provided on the same parameter."); + } + if (attributeName == null && propertyName == null) { + parameterMetadata[i] = new MethodParameterMetadata(methodParam.getParameterType(), null, false); + } + } + this.initialized = true; + } + } + + public Object[] mapMessage(Message message) { + if (message == null) { + return null; + } + if (!this.initialized) { + this.initialize(); + } + Object[] args = new Object[this.parameterMetadata.length]; + for (int i = 0; i < this.parameterMetadata.length; i++) { + MethodParameterMetadata metadata = this.parameterMetadata[i]; + Class type = metadata.type; + if (type.equals(HeaderAttribute.class)) { + Object value = message.getHeader().getAttribute(metadata.key); + if (value == null && metadata.required) { + throw new MessageHandlingException(message, + "required attribute '" + metadata.key + "' not available"); + } + args[i] = value; + } + else if (type.equals(HeaderProperty.class)) { + Object value = message.getHeader().getProperty(metadata.key); + if (value == null && metadata.required) { + throw new MessageHandlingException(message, + "required property '" + metadata.key + "' not available"); + } + args[i] = value; + } + else if (Message.class.isAssignableFrom(type)) { + args[i] = message; + } + else if (Map.class.isAssignableFrom(type)) { + args[i] = this.getHeaderAttributes(message); + } + else if (Properties.class.isAssignableFrom(type)) { + args[i] = this.getHeaderProperties(message); + } + else { + args[i] = message.getPayload(); + } + } + return args; + } + + private Map getHeaderAttributes(Message message) { + Map attributes = new HashMap(); + MessageHeader header = message.getHeader(); + Set attributeNames = header.getAttributeNames(); + for (String name : attributeNames) { + attributes.put(name, header.getAttribute(name)); + } + return attributes; + } + + private Properties getHeaderProperties(Message message) { + Properties properties = new Properties(); + MessageHeader header = message.getHeader(); + Set propertyNames = header.getPropertyNames(); + for (String name : propertyNames) { + properties.setProperty(name, header.getProperty(name)); + } + return properties; + } + + private String resolveParameterNameIfNecessary(String paramName, MethodParameter methodParam) { + if (!StringUtils.hasText(paramName)) { + paramName = methodParam.getParameterName(); + if (paramName == null) { + throw new IllegalStateException("No parameter name specified and not available in class file."); + } + } + return paramName; + } + + + private static class MethodParameterMetadata { + + private final Class type; + + private final String key; + + private final boolean required; + + + MethodParameterMetadata(Class type, String key, boolean required) { + this.type = type; + this.key = key; + this.required = required; + } + + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/annotation/HeaderAttribute.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/annotation/HeaderAttribute.java new file mode 100644 index 0000000000..31b8004036 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/annotation/HeaderAttribute.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2008 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.handler.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation indicating that a method parameter's value should be + * retrieved from an attribute in the message header. The value of + * the annotation provides the attribute key, and the optional + * 'required' property specifies whether the attribute value must + * be available within the header. + * + * @author Mark Fisher + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface HeaderAttribute { + + String value() default ""; + + boolean required() default true; + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/annotation/HeaderProperty.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/annotation/HeaderProperty.java new file mode 100644 index 0000000000..d650eff6a6 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/annotation/HeaderProperty.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2008 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.handler.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation indicating that a method parameter's value should be + * retrieved from a property in the message header. The value of + * the annotation provides the property key, and the optional + * 'required' property specifies whether the property value must + * be available within the header. + * + * @author Mark Fisher + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface HeaderProperty { + + String value() default ""; + + boolean required() default true; + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/config/AbstractMessageHandlerCreator.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/config/AbstractMessageHandlerCreator.java index a1eaa7b6ec..f2d250d398 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/config/AbstractMessageHandlerCreator.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/config/AbstractMessageHandlerCreator.java @@ -20,8 +20,6 @@ import java.lang.reflect.Method; import java.util.Map; import org.springframework.beans.factory.InitializingBean; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.annotation.Order; import org.springframework.integration.ConfigurationException; import org.springframework.integration.handler.AbstractMessageHandlerAdapter; import org.springframework.integration.handler.MessageHandler; @@ -36,14 +34,10 @@ public abstract class AbstractMessageHandlerCreator implements MessageHandlerCre public final MessageHandler createHandler(Object object, Method method, Map attributes) { MessageHandler handler = this.doCreateHandler(object, method, attributes); - if (handler instanceof AbstractMessageHandlerAdapter) { + if (handler instanceof AbstractMessageHandlerAdapter) { AbstractMessageHandlerAdapter adapter = ((AbstractMessageHandlerAdapter) handler); adapter.setObject(object); adapter.setMethodName(method.getName()); - Order orderAnnotation = (Order) AnnotationUtils.getAnnotation(method, Order.class); - if (orderAnnotation != null) { - adapter.setOrder(orderAnnotation.value()); - } } if (handler instanceof InitializingBean) { try { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/config/DefaultMessageHandlerCreator.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/config/DefaultMessageHandlerCreator.java index a8907bbd56..318f24082b 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/config/DefaultMessageHandlerCreator.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/config/DefaultMessageHandlerCreator.java @@ -40,8 +40,8 @@ public class DefaultMessageHandlerCreator extends AbstractMessageHandlerCreator if (types.length != 1) { throw new ConfigurationException("exactly one method parameter is required"); } - DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); - adapter.setExpectsMessage(Message.class.isAssignableFrom(types[0])); + DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); + adapter.setMethodExpectsMessage(Message.class.isAssignableFrom(types[0])); return adapter; } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/message/GenericMessage.java b/spring-integration-core/src/main/java/org/springframework/integration/message/GenericMessage.java index 5fa1033ca3..1d7da6589c 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/message/GenericMessage.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/message/GenericMessage.java @@ -75,7 +75,7 @@ public class GenericMessage implements Message { */ public GenericMessage(T payload, MessageHeader headerToCopy) { this(payload); - this.copyHeader(headerToCopy); + this.copyHeader(headerToCopy, true); } @@ -100,18 +100,39 @@ public class GenericMessage implements Message { return "[ID=" + this.id + "][Header=" + this.header + "][Payload='" + this.payload + "']"; } - private void copyHeader(final MessageHeader headerToCopy) { + public void copyHeader(final MessageHeader headerToCopy, boolean overrideExistingValues) { Set propertyNames = headerToCopy.getPropertyNames(); for (String key : propertyNames) { - this.header.setProperty(key, headerToCopy.getProperty(key)); + if (overrideExistingValues) { + this.header.setProperty(key, headerToCopy.getProperty(key)); + } + else if (this.header.getProperty(key) == null) { + this.header.setProperty(key, headerToCopy.getProperty(key)); + } } Set attributeNames = headerToCopy.getAttributeNames(); for (String key : attributeNames) { - this.header.setAttribute(key, headerToCopy.getAttribute(key)); + if (overrideExistingValues) { + this.header.setAttribute(key, headerToCopy.getAttribute(key)); + } + else { + this.header.setAttributeIfAbsent(key, headerToCopy.getAttribute(key)); + } + } + if (overrideExistingValues) { + this.header.setSequenceNumber(headerToCopy.getSequenceNumber()); + this.header.setSequenceSize(headerToCopy.getSequenceSize()); + this.header.setReturnAddress(headerToCopy.getReturnAddress()); + } + else { + if (headerToCopy.getSequenceSize() > 1 && this.header.getSequenceSize() == 1) { + this.header.setSequenceSize(headerToCopy.getSequenceSize()); + this.header.setSequenceNumber(headerToCopy.getSequenceNumber()); + } + if (headerToCopy.getReturnAddress() != null && this.header.getReturnAddress() == null) { + this.header.setReturnAddress(headerToCopy.getReturnAddress()); + } } - this.header.setSequenceNumber(headerToCopy.getSequenceNumber()); - this.header.setSequenceSize(headerToCopy.getSequenceSize()); - this.header.setReturnAddress(headerToCopy.getReturnAddress()); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/message/Message.java b/spring-integration-core/src/main/java/org/springframework/integration/message/Message.java index 378c225b6b..fe916f09e0 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/message/Message.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/message/Message.java @@ -33,4 +33,6 @@ public interface Message extends Serializable { boolean isExpired(); + void copyHeader(MessageHeader header, boolean overwriteExistingValues); + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/AggregatorAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/AggregatorAdapter.java index 1cdcc6c4a7..2026bee24a 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/AggregatorAdapter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/AggregatorAdapter.java @@ -17,18 +17,10 @@ package org.springframework.integration.router; import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; -import org.springframework.integration.ConfigurationException; -import org.springframework.integration.handler.HandlerMethodInvoker; import org.springframework.integration.message.GenericMessage; import org.springframework.integration.message.Message; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; /** * Aggregator adapter for methods annotated with {@link org.springframework.integration.annotation.Aggregator @Aggregator} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/MessageListMethodAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/MessageListMethodAdapter.java index f1509ac52a..5fec27ba45 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/MessageListMethodAdapter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/MessageListMethodAdapter.java @@ -16,6 +16,7 @@ package org.springframework.integration.router; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -23,8 +24,9 @@ import java.util.ArrayList; import java.util.List; import org.springframework.integration.ConfigurationException; -import org.springframework.integration.handler.HandlerMethodInvoker; import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessagingException; +import org.springframework.integration.util.DefaultMethodInvoker; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -36,7 +38,7 @@ import org.springframework.util.ReflectionUtils; */ public abstract class MessageListMethodAdapter { - private final HandlerMethodInvoker invoker; + private final DefaultMethodInvoker invoker; protected volatile Method method; @@ -49,31 +51,39 @@ public abstract class MessageListMethodAdapter { throw new ConfigurationException("Method '" + methodName + "(List args)' not found on '" + object.getClass().getName() + "'."); } - this.invoker = new HandlerMethodInvoker(object, this.method.getName()); + this.invoker = new DefaultMethodInvoker(object, this.method); } public MessageListMethodAdapter(Object object, Method method) { Assert.notNull(object, "'object' must not be null"); Assert.notNull(method, "'method' must not be null"); if (method.getParameterTypes().length != 1 || !method.getParameterTypes()[0].equals(List.class)) { - throw new ConfigurationException( - "Method must accept exactly one parameter, and it must be a List."); + throw new ConfigurationException("Method must accept exactly one parameter, and it must be a List."); } this.method = method; - this.invoker = new HandlerMethodInvoker(object, this.method.getName()); + this.invoker = new DefaultMethodInvoker(object, this.method); } + private static boolean isActualTypeParameterizedMessage(Method method) { - return getCollectionActualType(method) instanceof ParameterizedType + return (getCollectionActualType(method) instanceof ParameterizedType) && Message.class.isAssignableFrom((Class) ((ParameterizedType) getCollectionActualType(method)).getRawType()); } protected final Object executeMethod(List> messages) { - if (isMethodParameterParameterized(this.method) && isHavingActualTypeArguments(this.method) - && (isActualTypeRawMessage(this.method) || isActualTypeParameterizedMessage(this.method))) { - return this.invoker.invokeMethod(messages); + try { + if (isMethodParameterParameterized(this.method) && isHavingActualTypeArguments(this.method) + && (isActualTypeRawMessage(this.method) || isActualTypeParameterizedMessage(this.method))) { + return this.invoker.invokeMethod(messages); + } + return this.invoker.invokeMethod(extractPayloadsFromMessages(messages)); + } catch (InvocationTargetException e) { + throw new MessagingException( + "Method '" + this.method + "' threw an Exception.", e.getTargetException()); + } + catch (Throwable e) { + throw new MessagingException("Failed to invoke method '" + this.method + "'."); } - return this.invoker.invokeMethod(extractPayloadsFromMessages(messages)); } private List extractPayloadsFromMessages(List> messages) { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/RouterMessageHandlerAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/RouterMessageHandlerAdapter.java index 7eeea57d42..d7937c0888 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/RouterMessageHandlerAdapter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/RouterMessageHandlerAdapter.java @@ -18,7 +18,6 @@ package org.springframework.integration.router; import java.lang.reflect.Method; import java.util.Collection; -import java.util.Map; import org.springframework.integration.ConfigurationException; import org.springframework.integration.annotation.Router; @@ -26,11 +25,8 @@ import org.springframework.integration.channel.ChannelRegistry; import org.springframework.integration.channel.ChannelRegistryAware; import org.springframework.integration.channel.MessageChannel; import org.springframework.integration.handler.AbstractMessageHandlerAdapter; -import org.springframework.integration.handler.HandlerMethodInvoker; +import org.springframework.integration.handler.annotation.AnnotationMethodMessageMapper; import org.springframework.integration.message.Message; -import org.springframework.integration.message.MessageHandlingException; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * MessageHandler adapter for methods annotated with {@link Router @Router}. @@ -39,28 +35,21 @@ import org.springframework.util.StringUtils; */ public class RouterMessageHandlerAdapter extends AbstractMessageHandlerAdapter implements ChannelRegistryAware { - private static final String PROPERTY_KEY = "property"; - - private static final String ATTRIBUTE_KEY = "attribute"; - - - private final Method method; - - private final Map attributes; - private volatile ChannelRegistry channelRegistry; - public RouterMessageHandlerAdapter(Object object, Method method, Map attributes) { - Assert.notNull(object, "'object' must not be null"); - Assert.notNull(method, "'method' must not be null"); - Assert.notNull(attributes, "'attributes' must not be null"); + public RouterMessageHandlerAdapter(Object object, Method method) { this.setObject(object); - this.setMethodName(method.getName()); - this.method = method; - this.attributes = attributes; + this.setMethod(method); + if (method.getParameterTypes().length < 1) { + throw new ConfigurationException("The router method must accept at least one argument."); + } + if (method.getParameterTypes()[0].equals(Message.class)) { + this.setMethodExpectsMessage(true); + } } + public void setChannelRegistry(ChannelRegistry channelRegistry) { this.channelRegistry = channelRegistry; } @@ -71,55 +60,20 @@ public class RouterMessageHandlerAdapter extends AbstractMessageHandlerAdapter i if (target != null && this.channelRegistry != null && (target instanceof ChannelRegistryAware)) { ((ChannelRegistryAware) target).setChannelRegistry(this.channelRegistry); } + this.setMessageMapper(new AnnotationMethodMessageMapper(this.getMethod())); } @Override - protected Object doHandle(Message message, HandlerMethodInvoker invoker) { - if (method.getParameterTypes().length != 1) { - throw new ConfigurationException( - "method must accept exactly one parameter"); - } - String propertyName = (String) attributes.get(PROPERTY_KEY); - String attributeName = (String) attributes.get(ATTRIBUTE_KEY); - Object retval = null; - if (StringUtils.hasText(propertyName)) { - if (StringUtils.hasText(attributeName)) { - throw new ConfigurationException( - "cannot accept both 'property' and 'attribute'"); - } - String property = message.getHeader().getProperty(propertyName); - if (!StringUtils.hasText(property)) { - throw new MessageHandlingException(message, - "no '" + propertyName + "' property available for router method"); - } - retval = this.invokeMethod(invoker, property); - } - else if (StringUtils.hasText(attributeName)) { - Object attribute = message.getHeader().getAttribute(attributeName); - if (attribute == null) { - throw new MessageHandlingException(message, - "no '" + attributeName + "' attribute available for router method"); - } - retval = this.invokeMethod(invoker, attribute); - } - else { - Class type = method.getParameterTypes()[0]; - if (type.equals(Message.class)) { - retval = this.invokeMethod(invoker, message); - } - else { - retval = this.invokeMethod(invoker, message.getPayload()); - } - } - if (retval != null) { - if (retval instanceof Collection) { - Collection channels = (Collection) retval; + protected Message handleReturnValue(Object returnValue, Message originalMessage) { + if (returnValue != null) { + if (returnValue instanceof Collection) { + Collection channels = (Collection) returnValue; for (Object channel : channels) { if (channel instanceof MessageChannel) { - this.sendMessage(message, (MessageChannel) channel); + this.sendMessage(originalMessage, (MessageChannel) channel); } else if (channel instanceof String) { - this.sendMessage(message, (String) channel); + this.sendMessage(originalMessage, (String) channel); } else { throw new ConfigurationException( @@ -127,21 +81,21 @@ public class RouterMessageHandlerAdapter extends AbstractMessageHandlerAdapter i } } } - else if (retval instanceof MessageChannel[]) { - for (MessageChannel channel : (MessageChannel[]) retval) { - this.sendMessage(message, channel); + else if (returnValue instanceof MessageChannel[]) { + for (MessageChannel channel : (MessageChannel[]) returnValue) { + this.sendMessage(originalMessage, channel); } } - else if (retval instanceof String[]) { - for (String channelName : (String[]) retval) { - this.sendMessage(message, channelName); + else if (returnValue instanceof String[]) { + for (String channelName : (String[]) returnValue) { + this.sendMessage(originalMessage, channelName); } } - else if (retval instanceof MessageChannel) { - this.sendMessage(message, (MessageChannel) retval); + else if (returnValue instanceof MessageChannel) { + this.sendMessage(originalMessage, (MessageChannel) returnValue); } - else if (retval instanceof String) { - this.sendMessage(message, (String) retval); + else if (returnValue instanceof String) { + this.sendMessage(originalMessage, (String) returnValue); } else { throw new ConfigurationException( @@ -151,14 +105,6 @@ public class RouterMessageHandlerAdapter extends AbstractMessageHandlerAdapter i return null; } - private Object invokeMethod(HandlerMethodInvoker invoker, Object parameter) { - if (this.logger.isDebugEnabled()) { - logger.debug("invoking method '" + method.getName() + "' with parameter of type '" + - parameter.getClass().getName() + "'"); - } - return invoker.invokeMethod(parameter); - } - private boolean sendMessage(Message message, String channelName) { MessageChannel channel = this.channelRegistry.lookupChannel(channelName); if (channel == null) { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/SplitterMessageHandlerAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/router/SplitterMessageHandlerAdapter.java index 0a96fe881e..0dcc7c2fb6 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/SplitterMessageHandlerAdapter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/SplitterMessageHandlerAdapter.java @@ -18,7 +18,6 @@ package org.springframework.integration.router; import java.lang.reflect.Method; import java.util.Collection; -import java.util.Map; import org.springframework.integration.ConfigurationException; import org.springframework.integration.annotation.Splitter; @@ -26,9 +25,7 @@ import org.springframework.integration.channel.ChannelRegistry; import org.springframework.integration.channel.ChannelRegistryAware; import org.springframework.integration.channel.MessageChannel; import org.springframework.integration.handler.AbstractMessageHandlerAdapter; -import org.springframework.integration.handler.HandlerMethodInvoker; import org.springframework.integration.message.Message; -import org.springframework.integration.message.MessageHeader; import org.springframework.util.Assert; /** @@ -37,9 +34,7 @@ import org.springframework.util.Assert; * @author Mark Fisher * @author Marius Bogoevici */ -public class SplitterMessageHandlerAdapter extends AbstractMessageHandlerAdapter implements ChannelRegistryAware { - - private final Method method; +public class SplitterMessageHandlerAdapter extends AbstractMessageHandlerAdapter implements ChannelRegistryAware { private final String outputChannelName; @@ -48,17 +43,20 @@ public class SplitterMessageHandlerAdapter extends AbstractMessageHandlerAdap private volatile long sendTimeout = -1; - public SplitterMessageHandlerAdapter(T object, Method method, Map attributes) { - Assert.notNull(object, "'object' must not be null"); - Assert.notNull(method, "'method' must not be null"); - Assert.isTrue(attributes != null && attributes.get(OUTPUT_CHANNEL_NAME_KEY) != null, - "The '" + OUTPUT_CHANNEL_NAME_KEY + "' attribute is required."); + public SplitterMessageHandlerAdapter(Object object, Method method, String outputChannelName) { + Assert.hasText(outputChannelName, "output channel name is required"); this.setObject(object); - this.setMethodName(method.getName()); - this.method = method; - this.outputChannelName = (String) attributes.get(OUTPUT_CHANNEL_NAME_KEY); + this.setMethod(method); + this.outputChannelName = outputChannelName; + if (method.getParameterTypes().length < 1) { + throw new ConfigurationException("The splitter method must accept at least one argument."); + } + if (method.getParameterTypes()[0].equals(Message.class)) { + this.setMethodExpectsMessage(true); + } } + public void setChannelRegistry(ChannelRegistry channelRegistry) { this.channelRegistry = channelRegistry; } @@ -68,45 +66,32 @@ public class SplitterMessageHandlerAdapter extends AbstractMessageHandlerAdap } @Override - protected final Object doHandle(Message message, HandlerMethodInvoker invoker) { - final MessageHeader originalMessageHeader = message.getHeader(); - if (method.getParameterTypes().length != 1) { - throw new ConfigurationException( - "Splitter method must accept exactly one parameter"); - } - Object retval = null; - Class type = method.getParameterTypes()[0]; - if (type.equals(Message.class)) { - retval = invoker.invokeMethod(message); - } - else { - retval = invoker.invokeMethod(message.getPayload()); - } - if (retval == null) { + protected final Message handleReturnValue(Object returnValue, Message originalMessage) { + if (returnValue == null) { if (logger.isWarnEnabled()) { - logger.warn("Splitter method '" + this.method.getName() + "' returned null"); + logger.warn("Splitter method returned null."); } return null; } - if (retval instanceof Collection) { - Collection items = (Collection) retval; + if (returnValue instanceof Collection) { + Collection items = (Collection) returnValue; int sequenceNumber = 0; int sequenceSize = items.size(); for (Object item : items) { - Message splitMessage = (item instanceof Message) ? (Message) item : - this.createReplyMessage(item, originalMessageHeader); - this.prepareMessage(splitMessage, message.getId(), ++sequenceNumber, sequenceSize); + Message splitMessage = (item instanceof Message) ? + (Message) item : this.createReplyMessage(item, originalMessage); + this.prepareMessage(splitMessage, originalMessage.getId(), ++sequenceNumber, sequenceSize); this.sendMessage(splitMessage, this.outputChannelName); } } - else if (retval.getClass().isArray()) { - Object[] array = (Object[]) retval; + else if (returnValue.getClass().isArray()) { + Object[] array = (Object[]) returnValue; int sequenceNumber = 0; int sequenceSize = array.length; for (Object item : array) { - Message splitMessage = (item instanceof Message) ? (Message) item : - this.createReplyMessage(item, originalMessageHeader); - this.prepareMessage(splitMessage, message.getId(), ++sequenceNumber, sequenceSize); + Message splitMessage = (item instanceof Message) ? + (Message) item : this.createReplyMessage(item, originalMessage); + this.prepareMessage(splitMessage, originalMessage.getId(), ++sequenceNumber, sequenceSize); this.sendMessage(splitMessage, this.outputChannelName); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/config/RouterMessageHandlerCreator.java b/spring-integration-core/src/main/java/org/springframework/integration/router/config/RouterMessageHandlerCreator.java index 596f61a191..4204bfd9ac 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/config/RouterMessageHandlerCreator.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/config/RouterMessageHandlerCreator.java @@ -31,7 +31,7 @@ import org.springframework.integration.router.RouterMessageHandlerAdapter; public class RouterMessageHandlerCreator extends AbstractMessageHandlerCreator { public MessageHandler doCreateHandler(Object object, Method method, Map attributes) { - return new RouterMessageHandlerAdapter(object, method, attributes); + return new RouterMessageHandlerAdapter(object, method); } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/router/config/SplitterMessageHandlerCreator.java b/spring-integration-core/src/main/java/org/springframework/integration/router/config/SplitterMessageHandlerCreator.java index 3ddfdba875..5ee25a83e9 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/router/config/SplitterMessageHandlerCreator.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/router/config/SplitterMessageHandlerCreator.java @@ -19,6 +19,7 @@ package org.springframework.integration.router.config; import java.lang.reflect.Method; import java.util.Map; +import org.springframework.integration.handler.AbstractMessageHandlerAdapter; import org.springframework.integration.handler.MessageHandler; import org.springframework.integration.handler.config.AbstractMessageHandlerCreator; import org.springframework.integration.router.SplitterMessageHandlerAdapter; @@ -31,6 +32,8 @@ import org.springframework.integration.router.SplitterMessageHandlerAdapter; public class SplitterMessageHandlerCreator extends AbstractMessageHandlerCreator { public MessageHandler doCreateHandler(Object object, Method method, Map attributes) { - return new SplitterMessageHandlerAdapter(object, method, attributes); + String outputChannelName = (String) attributes.get(AbstractMessageHandlerAdapter.OUTPUT_CHANNEL_NAME_KEY); + return new SplitterMessageHandlerAdapter(object, method, outputChannelName); } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/DefaultMethodInvoker.java b/spring-integration-core/src/main/java/org/springframework/integration/util/DefaultMethodInvoker.java new file mode 100644 index 0000000000..a5f0b92192 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/DefaultMethodInvoker.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2008 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.util; + +import java.lang.reflect.Method; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.SimpleTypeConverter; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.TypeMismatchException; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Implementation of {@link MethodInvoker} to be used when the actual {@link Method} reference is known. + * + * @author Mark Fisher + */ +public class DefaultMethodInvoker implements MethodInvoker { + + private final Log logger = LogFactory.getLog(this.getClass()); + + private final Object object; + + private final Method method; + + private volatile TypeConverter typeConverter; + + + public DefaultMethodInvoker(Object object, Method method) { + Assert.notNull(object, "object must not be null"); + Assert.notNull(method, "method must not be null"); + this.object = object; + this.method = method; + } + + + public void setTypeConverter(TypeConverter typeConverter) { + this.typeConverter = typeConverter; + } + + protected TypeConverter getTypeConverter() { + if (this.typeConverter == null) { + this.typeConverter = new SimpleTypeConverter(); + } + return this.typeConverter; + } + + public Object invokeMethod(Object ... args) throws Exception { + TypeConverter converter = getTypeConverter(); + int argCount = args.length; + Class[] paramTypes = this.method.getParameterTypes(); + if (paramTypes.length != argCount) { + throw new IllegalArgumentException("Wrong number of arguments. Expected types " + + ObjectUtils.nullSafeToString(paramTypes) + ", but received values " + + ObjectUtils.nullSafeToString(args) + "."); + } + Object[] convertedArgs = new Object[argCount]; + boolean match = true; + for (int i = 0; i < argCount && match; i++) { + try { + convertedArgs[i] = converter.convertIfNecessary(args[i], paramTypes[i]); + } + catch (TypeMismatchException e) { + throw new IllegalArgumentException("Failed to convert argument type.", e); + } + } + this.method.setAccessible(true); + if (this.logger.isDebugEnabled()) { + logger.debug("invoking method '" + this.method.getName() + "' with arguments " + ObjectUtils.nullSafeToString(convertedArgs)); + } + return this.method.invoke(this.object, convertedArgs); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/MethodInvoker.java b/spring-integration-core/src/main/java/org/springframework/integration/util/MethodInvoker.java new file mode 100644 index 0000000000..013b2409f1 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/MethodInvoker.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2008 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.util; + +/** + * A strategy interface for invoking a method. + * Typically used by adapters. + * + * @author Mark Fisher + */ +public interface MethodInvoker { + + Object invokeMethod(Object ... args) throws Exception; + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/util/NameResolvingMethodInvoker.java b/spring-integration-core/src/main/java/org/springframework/integration/util/NameResolvingMethodInvoker.java new file mode 100644 index 0000000000..f0329277f8 --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/util/NameResolvingMethodInvoker.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2008 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.util; + +import org.springframework.beans.support.ArgumentConvertingMethodInvoker; +import org.springframework.util.Assert; + +/** + * Implementation of {@link MethodInvoker} to be used when only the method name is known. + * + * @author Mark Fisher + */ +public class NameResolvingMethodInvoker implements MethodInvoker { + + private final Object object; + + private final String methodName; + + private volatile MethodValidator methodValidator; + + + public NameResolvingMethodInvoker(Object object, String methodName) { + Assert.notNull(object, "'object' must not be null"); + Assert.notNull(methodName, "'methodName' must not be null"); + this.object = object; + this.methodName = methodName; + } + + + public void setMethodValidator(MethodValidator methodValidator) { + this.methodValidator = methodValidator; + } + + public Object invokeMethod(Object ... args) throws Exception { + ArgumentConvertingMethodInvoker invoker = new ArgumentConvertingMethodInvoker(); + invoker.setTargetObject(this.object); + invoker.setTargetMethod(this.methodName); + invoker.setArguments(args); + invoker.prepare(); + invoker.getPreparedMethod().setAccessible(true); + if (this.methodValidator != null) { + this.methodValidator.validate(invoker.getPreparedMethod()); + } + return invoker.invoke(); + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/adapter/MethodInvokingSourceTests.java b/spring-integration-core/src/test/java/org/springframework/integration/adapter/MethodInvokingSourceTests.java index ae0d99ec61..eca72652a5 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/adapter/MethodInvokingSourceTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/adapter/MethodInvokingSourceTests.java @@ -31,7 +31,7 @@ public class MethodInvokingSourceTests { @Test public void testValidMethod() { - MethodInvokingSource source = new MethodInvokingSource(); + MethodInvokingSource source = new MethodInvokingSource(); source.setObject(new TestBean()); source.setMethod("validMethod"); Message result = source.receive(); @@ -42,7 +42,7 @@ public class MethodInvokingSourceTests { @Test(expected=MessagingException.class) public void testNoMatchingMethodName() { - MethodInvokingSource source = new MethodInvokingSource(); + MethodInvokingSource source = new MethodInvokingSource(); source.setObject(new TestBean()); source.setMethod("noMatchingMethod"); source.receive(); @@ -50,7 +50,7 @@ public class MethodInvokingSourceTests { @Test(expected=MessagingException.class) public void testInvalidMethodWithArg() { - MethodInvokingSource source = new MethodInvokingSource(); + MethodInvokingSource source = new MethodInvokingSource(); source.setObject(new TestBean()); source.setMethod("invalidMethodWithArg"); source.receive(); @@ -58,7 +58,7 @@ public class MethodInvokingSourceTests { @Test(expected=MessagingException.class) public void testInvalidMethodWithNoReturnValue() { - MethodInvokingSource source = new MethodInvokingSource(); + MethodInvokingSource source = new MethodInvokingSource(); source.setObject(new TestBean()); source.setMethod("invalidMethodWithNoReturnValue"); source.receive(); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java index c9b50c2e48..b6b6ba4290 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/AggregatorParserTests.java @@ -23,18 +23,19 @@ import java.util.List; import org.junit.Assert; import org.junit.Before; import org.junit.Test; + import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.integration.channel.MessageChannel; -import org.springframework.integration.handler.HandlerMethodInvoker; import org.springframework.integration.message.GenericMessage; import org.springframework.integration.message.Message; import org.springframework.integration.router.AggregatingMessageHandler; import org.springframework.integration.router.CompletionStrategy; import org.springframework.integration.router.CompletionStrategyAdapter; +import org.springframework.integration.util.MethodInvoker; /** * @author Marius Bogoevici @@ -43,9 +44,10 @@ public class AggregatorParserTests { private ApplicationContext context; + @Before public void setUp() { - context = new ClassPathXmlApplicationContext("aggregatorParserTests.xml", this.getClass()); + this.context = new ClassPathXmlApplicationContext("aggregatorParserTests.xml", this.getClass()); } @Test @@ -110,27 +112,27 @@ public class AggregatorParserTests { addingAggregator.handle(message); } MessageChannel replyChannel = (MessageChannel) context.getBean("replyChannel"); - Message response = replyChannel.receive(); + Message response = replyChannel.receive(); Assert.assertEquals(6l, response.getPayload()); } - + @Test(expected=BeanCreationException.class) public void testMissingMethodOnAggregator() { context = new ClassPathXmlApplicationContext("invalidMethodNameAggregator.xml", this.getClass()); } - + @Test(expected=BeanDefinitionParsingException.class) public void testDuplicateCompletionStrategyDefinition() { context = new ClassPathXmlApplicationContext("completionStrategyMethodWithMissingReference.xml", this.getClass()); } - + @Test public void testAggregatorWithPojoCompletionStrategy(){ AggregatingMessageHandler aggregatorWithPojoCompletionStrategy = (AggregatingMessageHandler) context.getBean("aggregatorWithPojoCompletionStrategy"); CompletionStrategy completionStrategy = (CompletionStrategy)new DirectFieldAccessor(aggregatorWithPojoCompletionStrategy).getPropertyValue("completionStrategy"); Assert.assertTrue(completionStrategy instanceof CompletionStrategyAdapter); DirectFieldAccessor completionStrategyAccessor = new DirectFieldAccessor(completionStrategy); - HandlerMethodInvoker invoker = (HandlerMethodInvoker)completionStrategyAccessor.getPropertyValue("invoker"); + MethodInvoker invoker = (MethodInvoker) completionStrategyAccessor.getPropertyValue("invoker"); Assert.assertTrue(new DirectFieldAccessor(invoker).getPropertyValue("object") instanceof MaxValueCompletionStrategy); Assert.assertTrue(((Method)completionStrategyAccessor.getPropertyValue("method")).getName().equals("checkCompleteness")); @@ -145,12 +147,13 @@ public class AggregatorParserTests { Assert.assertNotNull(reply); Assert.assertEquals(11l, reply.getPayload()); } - + @Test(expected=BeanDefinitionParsingException.class) public void testAggregatorWithDuplicateCompletionStrategy() { context = new ClassPathXmlApplicationContext("duplicateCompletionStrategy.xml", this.getClass()); } - + + private static Message createMessage(T payload, Object correlationId, int sequenceSize, int sequenceNumber, MessageChannel replyChannel) { GenericMessage message = new GenericMessage(payload); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/CompletionStrategyAnnotationTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/CompletionStrategyAnnotationTests.java index 0a2b0aa6b5..8eb56a1dc2 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/CompletionStrategyAnnotationTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/CompletionStrategyAnnotationTests.java @@ -16,6 +16,7 @@ package org.springframework.integration.config; +import java.lang.reflect.Method; import java.util.List; import org.junit.Assert; @@ -31,6 +32,9 @@ import org.springframework.integration.handler.MessageHandlerChain; import org.springframework.integration.router.AggregatingMessageHandler; import org.springframework.integration.router.CompletionStrategyAdapter; +/** + * @author Marius Bogoevici + */ public class CompletionStrategyAnnotationTests { @Test @@ -44,7 +48,8 @@ public class CompletionStrategyAnnotationTests { DirectFieldAccessor invokerAccessor = new DirectFieldAccessor(new DirectFieldAccessor( aggregatingMessageHandlerAccessor.getPropertyValue("completionStrategy")).getPropertyValue("invoker")); Assert.assertSame(context.getBean(endpointName), invokerAccessor.getPropertyValue("object")); - Assert.assertEquals("completionChecker", invokerAccessor.getPropertyValue("method")); + Method completionCheckerMethod = (Method) invokerAccessor.getPropertyValue("method"); + Assert.assertEquals("completionChecker", completionCheckerMethod.getName()); } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/CorrelationIdTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/CorrelationIdTests.java index f6a5bec264..20db84b229 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/handler/CorrelationIdTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/CorrelationIdTests.java @@ -19,16 +19,12 @@ package org.springframework.integration.handler; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import java.util.HashMap; -import java.util.Map; - import org.junit.Test; import org.springframework.integration.channel.ChannelRegistry; import org.springframework.integration.channel.DefaultChannelRegistry; import org.springframework.integration.channel.MessageChannel; import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.message.GenericMessage; import org.springframework.integration.message.Message; import org.springframework.integration.message.StringMessage; import org.springframework.integration.router.SplitterMessageHandlerAdapter; @@ -44,7 +40,7 @@ public class CorrelationIdTests { Object correlationId = "123-ABC"; Message message = new StringMessage("test"); message.getHeader().setCorrelationId(correlationId); - DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); + DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); adapter.setObject(new TestBean()); adapter.setMethodName("upperCase"); adapter.afterPropertiesSet(); @@ -55,7 +51,7 @@ public class CorrelationIdTests { @Test public void testCorrelationIdCopiedFromMessageIdByDefault() { Message message = new StringMessage("test"); - DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); + DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); adapter.setObject(new TestBean()); adapter.setMethodName("upperCase"); adapter.afterPropertiesSet(); @@ -67,7 +63,7 @@ public class CorrelationIdTests { public void testCorrelationIdCopiedFromMessageCorrelationIdIfAvailable() { Message message = new StringMessage("messageId","test"); message.getHeader().setCorrelationId("correlationId"); - DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); + DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); adapter.setObject(new TestBean()); adapter.setMethodName("upperCase"); adapter.afterPropertiesSet(); @@ -81,14 +77,13 @@ public class CorrelationIdTests { Object correlationId = "123-ABC"; Message message = new StringMessage("test"); message.getHeader().setCorrelationId(correlationId); - AbstractMessageHandlerAdapter adapter = new AbstractMessageHandlerAdapter() { + AbstractMessageHandlerAdapter adapter = new AbstractMessageHandlerAdapter() { @Override - protected Object doHandle(Message message, HandlerMethodInvoker invoker) { - Object result = invoker.invokeMethod(message.getPayload()); - Message resultMessage = new GenericMessage(result); + protected Message handleReturnValue(Object returnValue, Message originalMessage) { + Message resultMessage = this.createReplyMessage(returnValue, originalMessage); resultMessage.getHeader().setCorrelationId("456-XYZ"); - return resultMessage; - } + return resultMessage; + } }; adapter.setObject(new TestBean()); adapter.setMethodName("upperCase"); @@ -100,14 +95,13 @@ public class CorrelationIdTests { @Test public void testCorrelationNotCopiedIfAlreadySetByHandler() throws Exception { Message message = new StringMessage("test"); - AbstractMessageHandlerAdapter adapter = new AbstractMessageHandlerAdapter() { + AbstractMessageHandlerAdapter adapter = new AbstractMessageHandlerAdapter() { @Override - protected Object doHandle(Message message, HandlerMethodInvoker invoker) { - Object result = invoker.invokeMethod(message.getPayload()); - Message resultMessage = new GenericMessage(result); + protected Message handleReturnValue(Object returnValue, Message originalMessage) { + Message resultMessage = this.createReplyMessage(returnValue, originalMessage); resultMessage.getHeader().setCorrelationId("456-XYZ"); - return resultMessage; - } + return resultMessage; + } }; adapter.setObject(new TestBean()); adapter.setMethodName("upperCase"); @@ -119,17 +113,15 @@ public class CorrelationIdTests { @Test public void testCorrelationIdWithSplitter() throws Exception { Message message = new StringMessage("test1,test2"); - DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); + DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); adapter.setObject(new TestBean()); adapter.setMethodName("upperCase"); adapter.afterPropertiesSet(); MessageChannel testChannel = new QueueChannel(); ChannelRegistry channelRegistry = new DefaultChannelRegistry(); channelRegistry.registerChannel("testChannel", testChannel); - Map attributes = new HashMap(); - attributes.put(AbstractMessageHandlerAdapter.OUTPUT_CHANNEL_NAME_KEY, "testChannel"); - SplitterMessageHandlerAdapter splitter = new SplitterMessageHandlerAdapter( - new TestBean(), TestBean.class.getMethod("split", String.class), attributes); + SplitterMessageHandlerAdapter splitter = new SplitterMessageHandlerAdapter( + new TestBean(), TestBean.class.getMethod("split", String.class), "testChannel"); splitter.setChannelRegistry(channelRegistry); splitter.afterPropertiesSet(); splitter.handle(message); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/DefaultMessageHandlerAdapterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/DefaultMessageHandlerAdapterTests.java index 2132549506..bd75b4b61b 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/handler/DefaultMessageHandlerAdapterTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/DefaultMessageHandlerAdapterTests.java @@ -30,7 +30,7 @@ public class DefaultMessageHandlerAdapterTests { @Test public void testPayloadAsMethodParameterAndObjectAsReturnValue() { - DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); + DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); adapter.setObject(new TestHandler()); adapter.setMethodName("acceptPayloadAndReturnObject"); adapter.afterPropertiesSet(); @@ -40,7 +40,7 @@ public class DefaultMessageHandlerAdapterTests { @Test public void testPayloadAsMethodParameterAndMessageAsReturnValue() { - DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); + DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); adapter.setObject(new TestHandler()); adapter.setMethodName("acceptPayloadAndReturnMessage"); adapter.afterPropertiesSet(); @@ -50,8 +50,8 @@ public class DefaultMessageHandlerAdapterTests { @Test public void testMessageAsMethodParameterAndObjectAsReturnValue() { - DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); - adapter.setExpectsMessage(true); + DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); + adapter.setMethodExpectsMessage(true); adapter.setObject(new TestHandler()); adapter.setMethodName("acceptMessageAndReturnObject"); adapter.afterPropertiesSet(); @@ -61,8 +61,8 @@ public class DefaultMessageHandlerAdapterTests { @Test public void testMessageAsMethodParameterAndMessageAsReturnValue() { - DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); - adapter.setExpectsMessage(true); + DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); + adapter.setMethodExpectsMessage(true); adapter.setObject(new TestHandler()); adapter.setMethodName("acceptMessageAndReturnMessage"); adapter.afterPropertiesSet(); @@ -72,8 +72,8 @@ public class DefaultMessageHandlerAdapterTests { @Test public void testMessageSubclassAsMethodParameterAndMessageAsReturnValue() { - DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); - adapter.setExpectsMessage(true); + DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); + adapter.setMethodExpectsMessage(true); adapter.setObject(new TestHandler()); adapter.setMethodName("acceptMessageSubclassAndReturnMessage"); adapter.afterPropertiesSet(); @@ -83,8 +83,8 @@ public class DefaultMessageHandlerAdapterTests { @Test public void testMessageSubclassAsMethodParameterAndMessageSubclassAsReturnValue() { - DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); - adapter.setExpectsMessage(true); + DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); + adapter.setMethodExpectsMessage(true); adapter.setObject(new TestHandler()); adapter.setMethodName("acceptMessageSubclassAndReturnMessageSubclass"); adapter.afterPropertiesSet(); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/handler/annotation/AnnotationMethodMessageMapperTests.java b/spring-integration-core/src/test/java/org/springframework/integration/handler/annotation/AnnotationMethodMessageMapperTests.java new file mode 100644 index 0000000000..cb2ed72551 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/handler/annotation/AnnotationMethodMessageMapperTests.java @@ -0,0 +1,175 @@ +/* + * Copyright 2002-2008 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.handler.annotation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.lang.reflect.Method; + +import org.junit.Test; + +import org.springframework.integration.annotation.Handler; +import org.springframework.integration.handler.DefaultMessageHandlerAdapter; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageHandlingException; +import org.springframework.integration.message.StringMessage; + +/** + * @author Mark Fisher + */ +public class AnnotationMethodMessageMapperTests { + + @Test + public void testOptionalAttribute() throws Exception { + Method method = TestHandler.class.getMethod("optionalAttribute", Integer.class); + AnnotationMethodMessageMapper mapper = new AnnotationMethodMessageMapper(method); + Object[] args = (Object[]) mapper.mapMessage(new StringMessage("foo")); + assertEquals(1, args.length); + assertNull(args[0]); + } + + @Test(expected=MessageHandlingException.class) + public void testRequiredAttributeNotProvided() throws Exception { + Method method = TestHandler.class.getMethod("requiredAttribute", Integer.class); + AnnotationMethodMessageMapper mapper = new AnnotationMethodMessageMapper(method); + mapper.mapMessage(new StringMessage("foo")); + } + + @Test + public void testRequiredAttributeProvided() throws Exception { + Method method = TestHandler.class.getMethod("requiredAttribute", Integer.class); + AnnotationMethodMessageMapper mapper = new AnnotationMethodMessageMapper(method); + Message message = new StringMessage("foo"); + message.getHeader().setAttribute("num", new Integer(123)); + Object[] args = (Object[]) mapper.mapMessage(message); + assertEquals(1, args.length); + assertEquals(new Integer(123), args[0]); + } + + @Test + public void testOptionalProperty() throws Exception { + Method method = TestHandler.class.getMethod("optionalProperty", String.class); + AnnotationMethodMessageMapper mapper = new AnnotationMethodMessageMapper(method); + Object[] args = (Object[]) mapper.mapMessage(new StringMessage("foo")); + assertEquals(1, args.length); + assertNull(args[0]); + } + + @Test(expected=MessageHandlingException.class) + public void testRequiredPropertyNotProvided() throws Exception { + Method method = TestHandler.class.getMethod("requiredProperty", String.class); + AnnotationMethodMessageMapper mapper = new AnnotationMethodMessageMapper(method); + mapper.mapMessage(new StringMessage("foo")); + } + + @Test + public void testRequiredPropertyProvided() throws Exception { + Method method = TestHandler.class.getMethod("requiredProperty", String.class); + AnnotationMethodMessageMapper mapper = new AnnotationMethodMessageMapper(method); + Message message = new StringMessage("foo"); + message.getHeader().setProperty("prop", "bar"); + Object[] args = (Object[]) mapper.mapMessage(message); + assertEquals(1, args.length); + assertEquals("bar", args[0]); + } + + @Test + public void testMessageOnlyWithAdapter() throws Exception { + TestHandler handler = new TestHandler(); + Method method = handler.getClass().getMethod("messageOnly", Message.class); + AnnotationMethodMessageMapper mapper = new AnnotationMethodMessageMapper(method); + DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); + adapter.setObject(handler); + adapter.setMethod(method); + adapter.setMessageMapper(mapper); + Message result = adapter.handle(new StringMessage("foo")); + assertEquals("foo", result.getPayload()); + } + + @Test + public void testMessageAndHeaderWithAdapter() throws Exception { + TestHandler handler = new TestHandler(); + Method method = handler.getClass().getMethod("messageAndAttribute", Message.class, Integer.class); + AnnotationMethodMessageMapper mapper = new AnnotationMethodMessageMapper(method); + DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); + adapter.setObject(handler); + adapter.setMethod(method); + adapter.setMessageMapper(mapper); + Message message = new StringMessage("foo"); + message.getHeader().setAttribute("number", 42); + Message result = adapter.handle(message); + assertEquals("foo-42", result.getPayload()); + } + + @Test + public void testHeaderAndPropertyWithAdapter() throws Exception { + TestHandler handler = new TestHandler(); + Method method = handler.getClass().getMethod("propertyAndAttribute", String.class, Integer.class); + AnnotationMethodMessageMapper mapper = new AnnotationMethodMessageMapper(method); + DefaultMessageHandlerAdapter adapter = new DefaultMessageHandlerAdapter(); + adapter.setObject(handler); + adapter.setMethod(method); + adapter.setMessageMapper(mapper); + Message message = new StringMessage("foo"); + message.getHeader().setProperty("prop", "bar"); + message.getHeader().setAttribute("number", 42); + Message result = adapter.handle(message); + assertEquals("bar-42", result.getPayload()); + } + + + private static class TestHandler { + + @Handler + public String messageOnly(Message message) { + return (String) message.getPayload(); + } + + @Handler + public String messageAndAttribute(Message message, @HeaderAttribute("number") Integer num) { + return (String) message.getPayload() + "-" + num.toString(); + } + + @Handler + public String propertyAndAttribute(@HeaderProperty String prop, @HeaderAttribute("number") Integer num) { + return prop + "-" + num.toString(); + } + + @Handler + public Integer optionalAttribute(@HeaderAttribute(required=false) Integer num) { + return num; + } + + @Handler + public Integer requiredAttribute(@HeaderAttribute(value="num", required=true) Integer num) { + return num; + } + + @Handler + public String optionalProperty(@HeaderProperty(required=false) String prop) { + return prop; + } + + @Handler + public String requiredProperty(@HeaderProperty(value="prop", required=true) String prop) { + return prop; + } + + } + +} diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/RouterMessageHandlerAdapterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/RouterMessageHandlerAdapterTests.java index 3d5d3500bc..132f6faa29 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/RouterMessageHandlerAdapterTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/RouterMessageHandlerAdapterTests.java @@ -34,6 +34,8 @@ import org.springframework.integration.channel.ChannelRegistryAware; import org.springframework.integration.channel.DefaultChannelRegistry; import org.springframework.integration.channel.MessageChannel; import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.handler.annotation.HeaderAttribute; +import org.springframework.integration.handler.annotation.HeaderProperty; import org.springframework.integration.message.GenericMessage; import org.springframework.integration.message.Message; import org.springframework.integration.message.StringMessage; @@ -47,8 +49,7 @@ public class RouterMessageHandlerAdapterTests { public void testChannelNameResolutionByPayload() throws Exception { SingleChannelNameRoutingTestBean testBean = new SingleChannelNameRoutingTestBean(); Method routingMethod = testBean.getClass().getMethod("routePayload", String.class); - Map attribs = new ConcurrentHashMap(); - RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod, attribs); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod); Message message = new GenericMessage("123", "bar"); QueueChannel barChannel = new QueueChannel(); ChannelRegistry channelRegistry = new DefaultChannelRegistry(); @@ -64,10 +65,8 @@ public class RouterMessageHandlerAdapterTests { @Test public void testChannelNameResolutionByProperty() throws Exception { SingleChannelNameRoutingTestBean testBean = new SingleChannelNameRoutingTestBean(); - Method routingMethod = testBean.getClass().getMethod("routePayload", String.class); - Map attribs = new ConcurrentHashMap(); - attribs.put("property", "returnAddress"); - RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod, attribs); + Method routingMethod = testBean.getClass().getMethod("routeByProperty", String.class); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod); Message message = new GenericMessage("123", "bar"); message.getHeader().setProperty("returnAddress", "baz"); QueueChannel barChannel = new QueueChannel(); @@ -88,10 +87,8 @@ public class RouterMessageHandlerAdapterTests { @Test public void testChannelNameResolutionByAttribute() throws Exception { SingleChannelNameRoutingTestBean testBean = new SingleChannelNameRoutingTestBean(); - Method routingMethod = testBean.getClass().getMethod("routePayload", String.class); - Map attribs = new ConcurrentHashMap(); - attribs.put("attribute", "returnAddress"); - RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod, attribs); + Method routingMethod = testBean.getClass().getMethod("routeByAttribute", String.class); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod); Message message = new GenericMessage("123", "bar"); message.getHeader().setProperty("returnAddress", "bad"); message.getHeader().setAttribute("returnAddress", "baz"); @@ -116,12 +113,9 @@ public class RouterMessageHandlerAdapterTests { @Test(expected=ConfigurationException.class) public void testFailsWhenPropertyAndAttributeAreBothProvided() throws Exception { - SingleChannelNameRoutingTestBean testBean = new SingleChannelNameRoutingTestBean(); - Method routingMethod = testBean.getClass().getMethod("routePayload", String.class); - Map attribs = new ConcurrentHashMap(); - attribs.put("property", "targetChannel"); - attribs.put("attribute", "returnAddress"); - RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod, attribs); + InvalidRoutingTestBean testBean = new InvalidRoutingTestBean(); + Method routingMethod = testBean.getClass().getMethod("tooManyAnnotations", String.class); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod); adapter.afterPropertiesSet(); adapter.handle(new GenericMessage("123", "testing")); } @@ -130,8 +124,7 @@ public class RouterMessageHandlerAdapterTests { public void testChannelNameResolutionByMessage() throws Exception { SingleChannelNameRoutingTestBean testBean = new SingleChannelNameRoutingTestBean(); Method routingMethod = testBean.getClass().getMethod("routeMessage", Message.class); - Map attribs = new ConcurrentHashMap(); - RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod, attribs); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod); Message fooMessage = new StringMessage("foo"); Message barMessage = new StringMessage("bar"); Message badMessage = new StringMessage("bad"); @@ -166,8 +159,7 @@ public class RouterMessageHandlerAdapterTests { channelRegistry.registerChannel("bar-channel", barChannel); SingleChannelInstanceRoutingTestBean testBean = new SingleChannelInstanceRoutingTestBean(channelRegistry); Method routingMethod = testBean.getClass().getMethod("routePayload", String.class); - Map attribs = new ConcurrentHashMap(); - RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod, attribs); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod); Message fooMessage = new StringMessage("foo"); Message barMessage = new StringMessage("bar"); Message badMessage = new StringMessage("bad"); @@ -197,8 +189,7 @@ public class RouterMessageHandlerAdapterTests { channelRegistry.registerChannel("bar-channel", barChannel); SingleChannelInstanceRoutingTestBean testBean = new SingleChannelInstanceRoutingTestBean(channelRegistry); Method routingMethod = testBean.getClass().getMethod("routeMessage", Message.class); - Map attribs = new ConcurrentHashMap(); - RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod, attribs); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod); Message fooMessage = new StringMessage("foo"); Message barMessage = new StringMessage("bar"); Message badMessage = new StringMessage("bad"); @@ -228,8 +219,7 @@ public class RouterMessageHandlerAdapterTests { channelRegistry.registerChannel("bar-channel", barChannel); MultiChannelNameRoutingTestBean testBean = new MultiChannelNameRoutingTestBean(); Method routingMethod = testBean.getClass().getMethod("routePayload", String.class); - Map attribs = new ConcurrentHashMap(); - RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod, attribs); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod); Message fooMessage = new StringMessage("foo"); Message barMessage = new StringMessage("bar"); Message badMessage = new StringMessage("bad"); @@ -265,8 +255,7 @@ public class RouterMessageHandlerAdapterTests { channelRegistry.registerChannel("bar-channel", barChannel); MultiChannelNameRoutingTestBean testBean = new MultiChannelNameRoutingTestBean(); Method routingMethod = testBean.getClass().getMethod("routeMessage", Message.class); - Map attribs = new ConcurrentHashMap(); - RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod, attribs); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod); Message fooMessage = new StringMessage("foo"); Message barMessage = new StringMessage("bar"); Message badMessage = new StringMessage("bad"); @@ -302,8 +291,7 @@ public class RouterMessageHandlerAdapterTests { channelRegistry.registerChannel("bar-channel", barChannel); MultiChannelNameRoutingTestBean testBean = new MultiChannelNameRoutingTestBean(); Method routingMethod = testBean.getClass().getMethod("routeMessageToArray", Message.class); - Map attribs = new ConcurrentHashMap(); - RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod, attribs); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod); Message fooMessage = new StringMessage("foo"); Message barMessage = new StringMessage("bar"); Message badMessage = new StringMessage("bad"); @@ -339,8 +327,7 @@ public class RouterMessageHandlerAdapterTests { channelRegistry.registerChannel("bar-channel", barChannel); MultiChannelInstanceRoutingTestBean testBean = new MultiChannelInstanceRoutingTestBean(channelRegistry); Method routingMethod = testBean.getClass().getMethod("routePayload", String.class); - Map attribs = new ConcurrentHashMap(); - RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod, attribs); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod); Message fooMessage = new StringMessage("foo"); Message barMessage = new StringMessage("bar"); Message badMessage = new StringMessage("bad"); @@ -376,8 +363,7 @@ public class RouterMessageHandlerAdapterTests { channelRegistry.registerChannel("bar-channel", barChannel); MultiChannelInstanceRoutingTestBean testBean = new MultiChannelInstanceRoutingTestBean(channelRegistry); Method routingMethod = testBean.getClass().getMethod("routeMessage", Message.class); - Map attribs = new ConcurrentHashMap(); - RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod, attribs); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod); Message fooMessage = new StringMessage("foo"); Message barMessage = new StringMessage("bar"); Message badMessage = new StringMessage("bad"); @@ -413,8 +399,7 @@ public class RouterMessageHandlerAdapterTests { channelRegistry.registerChannel("bar-channel", barChannel); MultiChannelInstanceRoutingTestBean testBean = new MultiChannelInstanceRoutingTestBean(channelRegistry); Method routingMethod = testBean.getClass().getMethod("routeMessageToArray", Message.class); - Map attribs = new ConcurrentHashMap(); - RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod, attribs); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod); Message fooMessage = new StringMessage("foo"); Message barMessage = new StringMessage("bar"); Message badMessage = new StringMessage("bad"); @@ -448,8 +433,7 @@ public class RouterMessageHandlerAdapterTests { channelRegistry.registerChannel("foo-channel", fooChannel); ChannelRegistryAwareTestBean testBean = new ChannelRegistryAwareTestBean(); Method routingMethod = testBean.getClass().getMethod("route", String.class); - Map attribs = new ConcurrentHashMap(); - RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod, attribs); + RouterMessageHandlerAdapter adapter = new RouterMessageHandlerAdapter(testBean, routingMethod); adapter.setChannelRegistry(channelRegistry); assertNull(testBean.getChannelRegistry()); adapter.afterPropertiesSet(); @@ -468,6 +452,14 @@ public class RouterMessageHandlerAdapterTests { return name + "-channel"; } + public String routeByProperty(@HeaderProperty("returnAddress") String name) { + return name + "-channel"; + } + + public String routeByAttribute(@HeaderAttribute("returnAddress") String name) { + return name + "-channel"; + } + public String routeMessage(Message message) { if (message.getPayload().equals("foo")) { return "foo-channel"; @@ -591,4 +583,12 @@ public class RouterMessageHandlerAdapterTests { } } + public static class InvalidRoutingTestBean { + + public String tooManyAnnotations(@HeaderProperty("foo") @HeaderAttribute("bar") String name) { + return name + "-channel"; + } + + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/router/SplitterMessageHandlerAdapterTests.java b/spring-integration-core/src/test/java/org/springframework/integration/router/SplitterMessageHandlerAdapterTests.java index 3ecceb5ef3..08b975e1bb 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/router/SplitterMessageHandlerAdapterTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/router/SplitterMessageHandlerAdapterTests.java @@ -23,17 +23,14 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import org.junit.Test; -import org.springframework.integration.ConfigurationException; import org.springframework.integration.channel.ChannelRegistry; import org.springframework.integration.channel.DefaultChannelRegistry; import org.springframework.integration.channel.QueueChannel; -import org.springframework.integration.handler.AbstractMessageHandlerAdapter; import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessagingException; import org.springframework.integration.message.StringMessage; /** @@ -47,12 +44,9 @@ public class SplitterMessageHandlerAdapterTests { private SplitterTestBean testBean = new SplitterTestBean(); - private Map attribs = new ConcurrentHashMap(); - public SplitterMessageHandlerAdapterTests() { this.channelRegistry.registerChannel("testChannel", testChannel); - this.attribs.put(AbstractMessageHandlerAdapter.OUTPUT_CHANNEL_NAME_KEY, "testChannel"); } @@ -160,10 +154,10 @@ public class SplitterMessageHandlerAdapterTests { assertEquals("bar", reply2.getPayload()); } - @Test(expected=ConfigurationException.class) + @Test(expected=MessagingException.class) public void testInvalidReturnType() throws Exception { Method splittingMethod = this.testBean.getClass().getMethod("invalidParameterCount", String.class, String.class); - SplitterMessageHandlerAdapter adapter = new SplitterMessageHandlerAdapter(testBean, splittingMethod, attribs); + SplitterMessageHandlerAdapter adapter = new SplitterMessageHandlerAdapter(testBean, splittingMethod, "testChannel"); adapter.setChannelRegistry(channelRegistry); adapter.afterPropertiesSet(); StringMessage message = new StringMessage("foo.bar"); @@ -208,7 +202,7 @@ public class SplitterMessageHandlerAdapterTests { private SplitterMessageHandlerAdapter getAdapter(String methodName) throws Exception { Class paramType = methodName.startsWith("message") ? Message.class : String.class; Method splittingMethod = this.testBean.getClass().getMethod(methodName, paramType); - SplitterMessageHandlerAdapter adapter = new SplitterMessageHandlerAdapter(testBean, splittingMethod, attribs); + SplitterMessageHandlerAdapter adapter = new SplitterMessageHandlerAdapter(testBean, splittingMethod, "testChannel"); adapter.setChannelRegistry(channelRegistry); adapter.afterPropertiesSet(); return adapter; diff --git a/spring-integration-core/src/test/java/org/springframework/integration/util/DefaultMethodInvokerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/util/DefaultMethodInvokerTests.java new file mode 100644 index 0000000000..fe411cac72 --- /dev/null +++ b/spring-integration-core/src/test/java/org/springframework/integration/util/DefaultMethodInvokerTests.java @@ -0,0 +1,138 @@ +/* + * Copyright 2002-2008 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.util; + +import static org.junit.Assert.assertEquals; + +import java.lang.reflect.Method; + +import org.junit.Test; + +import org.springframework.beans.TypeMismatchException; +import org.springframework.integration.util.DefaultMethodInvoker; + +/** + * @author Mark Fisher + */ +public class DefaultMethodInvokerTests { + + @Test + public void testStringArgumentWithVoidReturnAndNoConversionNecessary() throws Exception { + TestBean testBean = new TestBean(); + String methodName = "stringArgumentWithVoidReturn"; + Method method = testBean.getClass().getMethod(methodName, String.class); + DefaultMethodInvoker invoker = new DefaultMethodInvoker(testBean, method); + invoker.invokeMethod("test"); + assertEquals("test", testBean.lastStringArgument); + } + + @Test + public void testStringArgumentWithVoidReturnAndSuccessfulConversion() throws Exception { + TestBean testBean = new TestBean(); + String methodName = "stringArgumentWithVoidReturn"; + Method method = testBean.getClass().getMethod(methodName, String.class); + DefaultMethodInvoker invoker = new DefaultMethodInvoker(testBean, method); + invoker.invokeMethod(new Integer(123)); + assertEquals("123", testBean.lastStringArgument); + } + + @Test + public void testIntegerArgumentWithVoidReturnAndSuccessfulConversion() throws Exception { + TestBean testBean = new TestBean(); + String methodName = "integerArgumentWithVoidReturn"; + Method method = testBean.getClass().getMethod(methodName, Integer.class); + DefaultMethodInvoker invoker = new DefaultMethodInvoker(testBean, method); + invoker.invokeMethod("123"); + assertEquals(new Integer(123), testBean.lastIntegerArgument); + } + + @Test + public void testIntegerArgumentWithVoidReturnAndFailedConversion() throws Exception { + TestBean testBean = new TestBean(); + String methodName = "integerArgumentWithVoidReturn"; + Method method = testBean.getClass().getMethod(methodName, Integer.class); + DefaultMethodInvoker invoker = new DefaultMethodInvoker(testBean, method); + try { + invoker.invokeMethod("ABC"); + throw new IllegalStateException("method invocation should have failed with TypeMismatchException"); + } + catch (Exception e) { + assertEquals(TypeMismatchException.class, e.getCause().getClass()); + } + } + + @Test + public void testTwoArgumentsAndNoConversionRequired() throws Exception { + TestBean testBean = new TestBean(); + String methodName = "stringAndIntegerArgumentMethod"; + Method method = testBean.getClass().getMethod(methodName, String.class, Integer.class); + DefaultMethodInvoker invoker = new DefaultMethodInvoker(testBean, method); + Object result = invoker.invokeMethod("ABC", new Integer(456)); + assertEquals(result, "ABC:456"); + } + + @Test + public void testTwoArgumentsAndSuccessfulConversion() throws Exception { + TestBean testBean = new TestBean(); + String methodName = "stringAndIntegerArgumentMethod"; + Method method = testBean.getClass().getMethod(methodName, String.class, Integer.class); + DefaultMethodInvoker invoker = new DefaultMethodInvoker(testBean, method); + Object result = invoker.invokeMethod("ABC", "789"); + assertEquals(result, "ABC:789"); + } + + @Test(expected=IllegalArgumentException.class) + public void testTwoArgumentMethodWithOnlyOneArgumentProvided() throws Exception { + TestBean testBean = new TestBean(); + String methodName = "stringAndIntegerArgumentMethod"; + Method method = testBean.getClass().getMethod(methodName, String.class, Integer.class); + DefaultMethodInvoker invoker = new DefaultMethodInvoker(testBean, method); + invoker.invokeMethod("ABC"); + } + + @Test(expected=IllegalArgumentException.class) + public void testTwoArgumentMethodWithOnlyThreeArgumentsProvided() throws Exception { + TestBean testBean = new TestBean(); + String methodName = "stringAndIntegerArgumentMethod"; + Method method = testBean.getClass().getMethod(methodName, String.class, Integer.class); + DefaultMethodInvoker invoker = new DefaultMethodInvoker(testBean, method); + invoker.invokeMethod("ABC", new Integer(123), new Integer(456)); + } + + + private static class TestBean { + + String lastStringArgument; + + Integer lastIntegerArgument; + + + public void stringArgumentWithVoidReturn(String s) { + this.lastStringArgument = s; + } + + public void integerArgumentWithVoidReturn(Integer i) { + this.lastIntegerArgument = i; + } + + public String stringAndIntegerArgumentMethod(String s, Integer i) { + return s + ":" + i; + } + + } + +}