diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/annotation/Transformer.java b/org.springframework.integration/src/main/java/org/springframework/integration/annotation/Transformer.java new file mode 100644 index 0000000000..9adfbfe0cb --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/annotation/Transformer.java @@ -0,0 +1,39 @@ +/* + * 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that a method is capable of transforming a message, message header, + * or message payload. + * + * @author Mark Fisher + */ +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Handler +public @interface Transformer { + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/HandlerAnnotationPostProcessor.java b/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/HandlerAnnotationPostProcessor.java index 92d3b47d0e..2e1d8e893e 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/HandlerAnnotationPostProcessor.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/config/annotation/HandlerAnnotationPostProcessor.java @@ -18,12 +18,16 @@ package org.springframework.integration.config.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.OrderComparator; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; import org.springframework.integration.ConfigurationException; import org.springframework.integration.annotation.Aggregator; import org.springframework.integration.annotation.Concurrency; @@ -31,6 +35,7 @@ import org.springframework.integration.annotation.Handler; import org.springframework.integration.annotation.Polled; import org.springframework.integration.annotation.Router; import org.springframework.integration.annotation.Splitter; +import org.springframework.integration.annotation.Transformer; import org.springframework.integration.bus.MessageBus; import org.springframework.integration.channel.ChannelRegistryAware; import org.springframework.integration.endpoint.ConcurrencyPolicy; @@ -44,6 +49,7 @@ import org.springframework.integration.router.config.AggregatorMessageHandlerCre import org.springframework.integration.router.config.RouterMessageHandlerCreator; import org.springframework.integration.router.config.SplitterMessageHandlerCreator; import org.springframework.integration.scheduling.Subscription; +import org.springframework.integration.transformer.config.TransformerMessageHandlerCreator; import org.springframework.util.StringUtils; /** @@ -64,6 +70,7 @@ public class HandlerAnnotationPostProcessor extends AbstractAnnotationMethodPost this.handlerCreators.put(Router.class, new RouterMessageHandlerCreator()); this.handlerCreators.put(Splitter.class, new SplitterMessageHandlerCreator()); this.handlerCreators.put(Aggregator.class, new AggregatorMessageHandlerCreator(messageBus)); + this.handlerCreators.put(Transformer.class, new TransformerMessageHandlerCreator()); } @@ -82,7 +89,12 @@ public class HandlerAnnotationPostProcessor extends AbstractAnnotationMethodPost + annotation.annotationType() + "', using DefaultMessageHandlerCreator."); } } - MessageHandler handler = handlerCreator.createHandler(bean, method, AnnotationUtils.getAnnotationAttributes(annotation)); + Map attributes = AnnotationUtils.getAnnotationAttributes(annotation); + Order order = AnnotationUtils.findAnnotation(method, Order.class); + if (order != null) { + attributes.put("order", order.value()); + } + MessageHandler handler = handlerCreator.createHandler(bean, method, attributes); if (handler != null) { if (handler instanceof ChannelRegistryAware) { ((ChannelRegistryAware) handler).setChannelRegistry(this.getMessageBus()); @@ -110,6 +122,9 @@ public class HandlerAnnotationPostProcessor extends AbstractAnnotationMethodPost if (handlerChain.getHandlers().size() == 1) { return handlerChain.getHandlers().get(0); } + List handlers = new ArrayList(handlerChain.getHandlers()); + Collections.sort(handlers, new OrderComparator()); + handlerChain.setHandlers(handlers); return handlerChain; } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/handler/AbstractMessageHandlerAdapter.java b/org.springframework.integration/src/main/java/org/springframework/integration/handler/AbstractMessageHandlerAdapter.java index 42aabf33bc..fe9c8062bf 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/handler/AbstractMessageHandlerAdapter.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/handler/AbstractMessageHandlerAdapter.java @@ -19,20 +19,14 @@ 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.integration.ConfigurationException; +import org.springframework.core.Ordered; 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.MessageHandlingException; import org.springframework.integration.message.MessageMapper; -import org.springframework.integration.util.DefaultMethodInvoker; -import org.springframework.integration.util.MethodInvoker; -import org.springframework.integration.util.NameResolvingMethodInvoker; +import org.springframework.integration.util.AbstractMethodInvokingAdapter; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -50,18 +44,12 @@ import org.springframework.util.ObjectUtils; * * @author Mark Fisher */ -public abstract class AbstractMessageHandlerAdapter implements MessageHandler, InitializingBean { +public abstract class AbstractMessageHandlerAdapter extends AbstractMethodInvokingAdapter implements MessageHandler { public static final String OUTPUT_CHANNEL_NAME_KEY = "outputChannelName"; - protected final Log logger = LogFactory.getLog(this.getClass()); - - private volatile Object object; - - private volatile Method method; - - private volatile String methodName; + private volatile int order; private volatile boolean methodExpectsMessage; @@ -69,32 +57,6 @@ public abstract class AbstractMessageHandlerAdapter implements MessageHandler, I private volatile MessageCreator messageCreator = new DefaultMessageCreator(); - protected volatile MethodInvoker invoker; - - private volatile boolean initialized; - - private final Object initializationMonitor = new Object(); - - - public void setObject(Object object) { - this.object = object; - } - - protected Object getObject() { - return this.object; - } - - public void setMethod(Method method) { - this.method = method; - } - - protected Method getMethod() { - return this.method; - } - - public void setMethodName(String methodName) { - this.methodName = methodName; - } public void setMethodExpectsMessage(boolean methodExpectsMessage) { this.methodExpectsMessage = methodExpectsMessage; @@ -110,45 +72,13 @@ public abstract class AbstractMessageHandlerAdapter implements MessageHandler, I this.messageCreator = messageCreator; } - public void afterPropertiesSet() { - synchronized (this.initializationMonitor) { - if (this.initialized) { - return; - } - 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(); - } - - /** - * Subclasses may override this method for custom initialization requirements. - */ - protected void initialize() { - } - public Message handle(Message message) { + if (!this.isInitialized()) { + this.afterPropertiesSet(); + } 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 != null && mappingResult.getClass().isArray() @@ -161,10 +91,10 @@ public abstract class AbstractMessageHandlerAdapter implements MessageHandler, I try { Object result = null; try { - result = this.invoker.invokeMethod(args); + result = this.invokeMethod(args); } catch (NoSuchMethodException e) { - result = this.invoker.invokeMethod(message); + result = this.invokeMethod(message); this.methodExpectsMessage = true; } if (result == null) { @@ -173,12 +103,12 @@ public abstract class AbstractMessageHandlerAdapter implements MessageHandler, I return this.handleReturnValue(result, message); } catch (InvocationTargetException e) { - throw new MessageHandlingException(message, - "Handler method '" + this.method + "' threw an Exception.", e.getTargetException()); + throw new MessageHandlingException(message, "Handler method '" + + this.getMethod() + "' threw an Exception.", e.getTargetException()); } catch (Throwable e) { - throw new MessageHandlingException(message, "Failed to invoke handler method '" + this.method + - "' with arguments: " + ObjectUtils.nullSafeToString(args), e); + throw new MessageHandlingException(message, "Failed to invoke handler method '" + + this.getMethod() + "' with arguments: " + ObjectUtils.nullSafeToString(args), e); } } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/transformer/MessageFilter.java b/org.springframework.integration/src/main/java/org/springframework/integration/handler/MessageFilter.java similarity index 91% rename from org.springframework.integration/src/main/java/org/springframework/integration/transformer/MessageFilter.java rename to org.springframework.integration/src/main/java/org/springframework/integration/handler/MessageFilter.java index b0b4f566b4..dbf40cdaa0 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/transformer/MessageFilter.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/handler/MessageFilter.java @@ -14,9 +14,8 @@ * limitations under the License. */ -package org.springframework.integration.transformer; +package org.springframework.integration.handler; -import org.springframework.integration.handler.MessageHandler; import org.springframework.integration.message.Message; import org.springframework.integration.message.selector.MessageSelector; diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/handler/config/AbstractMessageHandlerCreator.java b/org.springframework.integration/src/main/java/org/springframework/integration/handler/config/AbstractMessageHandlerCreator.java index 067028482d..02d0b5b701 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/handler/config/AbstractMessageHandlerCreator.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/handler/config/AbstractMessageHandlerCreator.java @@ -22,6 +22,7 @@ import java.util.Map; import org.springframework.beans.factory.InitializingBean; import org.springframework.integration.ConfigurationException; import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.util.AbstractMethodInvokingAdapter; /** * Base class for handler creators that generate a {@link MessageHandler} @@ -33,6 +34,12 @@ public abstract class AbstractMessageHandlerCreator implements MessageHandlerCre public final MessageHandler createHandler(Object object, Method method, Map attributes) { MessageHandler handler = this.doCreateHandler(object, method, attributes); + if (attributes != null && handler instanceof AbstractMethodInvokingAdapter) { + Object order = attributes.get("order"); + if (order != null && order instanceof Integer) { + ((AbstractMethodInvokingAdapter) handler).setOrder(((Integer) order).intValue()); + } + } if (handler instanceof InitializingBean) { try { ((InitializingBean) handler).afterPropertiesSet(); diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/message/GenericMessage.java b/org.springframework.integration/src/main/java/org/springframework/integration/message/GenericMessage.java index 1d7da6589c..43544af848 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/message/GenericMessage.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/message/GenericMessage.java @@ -34,7 +34,7 @@ public class GenericMessage implements Message { private final MessageHeader header = new MessageHeader(); - private final T payload; + private volatile T payload; private transient final IdGenerator defaultIdGenerator = new RandomUuidGenerator(); @@ -91,6 +91,11 @@ public class GenericMessage implements Message { return this.payload; } + public void setPayload(T payload) { + Assert.notNull(payload, "payload must not be null"); + this.payload = payload; + } + public boolean isExpired() { Date expiration = this.header.getExpiration(); return (expiration != null) ? expiration.getTime() < System.currentTimeMillis() : false; diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/message/Message.java b/org.springframework.integration/src/main/java/org/springframework/integration/message/Message.java index fe916f09e0..d8902e5172 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/message/Message.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/message/Message.java @@ -31,6 +31,8 @@ public interface Message extends Serializable { T getPayload(); + void setPayload(T payload); + boolean isExpired(); void copyHeader(MessageHeader header, boolean overwriteExistingValues); diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/transformer/AnnotationMethodTransformerAdapter.java b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/AnnotationMethodTransformerAdapter.java new file mode 100644 index 0000000000..53da059b3c --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/AnnotationMethodTransformerAdapter.java @@ -0,0 +1,110 @@ +/* + * 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.transformer; + +import java.util.Map; +import java.util.Properties; + +import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.handler.annotation.AnnotationMethodMessageMapper; +import org.springframework.integration.message.DefaultMessageMapper; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessageMapper; +import org.springframework.integration.message.MessagingException; +import org.springframework.integration.util.AbstractMethodInvokingAdapter; + +/** + * @author Mark Fisher + */ +public class AnnotationMethodTransformerAdapter extends AbstractMethodInvokingAdapter + implements MessageTransformer, MessageHandler { + + private volatile MessageMapper mapper; + + private volatile boolean methodExpectsMessage; + + + public void setMethodExpectsMessage(boolean methodExpectsMessage) { + this.methodExpectsMessage = methodExpectsMessage; + } + + protected void initialize() { + this.mapper = (this.getMethod() != null) + ? new AnnotationMethodMessageMapper(this.getMethod()) + : new DefaultMessageMapper(); + } + + @SuppressWarnings("unchecked") + public void transform(Message message) { + if (!this.isInitialized()) { + this.afterPropertiesSet(); + } + if (message.getPayload() == null) { + return; + } + Object param = (this.methodExpectsMessage) ? message : this.mapper.mapMessage(message); + try { + Object args[] = null; + if (param != null && param.getClass().isArray() + && (Object.class.isAssignableFrom(param.getClass().getComponentType()))) { + args = (Object[]) param; + } + else { + args = new Object[] { param }; + } + Object result = null; + try { + result = this.invokeMethod(args); + } + catch (NoSuchMethodException e) { + result = this.invokeMethod(message); + this.methodExpectsMessage = true; + } + if (result == null) { + if (logger.isDebugEnabled()) { + logger.debug("MessageTransformer returned a null result"); + } + return; + } + if (result instanceof Properties && !(message.getPayload() instanceof Properties)) { + Properties propertiesToSet = (Properties) result; + for (Object keyObject : propertiesToSet.keySet()) { + String key = (String) keyObject; + message.getHeader().setProperty(key, propertiesToSet.getProperty(key)); + } + } + else if (result instanceof Map && !(message.getPayload() instanceof Map)) { + Map attributesToSet = (Map) result; + for (String key : attributesToSet.keySet()) { + message.getHeader().setAttribute(key, attributesToSet.get(key)); + } + } + else { + message.setPayload(result); + } + } + catch (Exception e) { + throw new MessagingException(message, "failed to transform message payload", e); + } + } + + public Message handle(Message message) { + this.transform(message); + return message; + } + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/transformer/MessageTransformer.java b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/MessageTransformer.java new file mode 100644 index 0000000000..dcfa79160d --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/MessageTransformer.java @@ -0,0 +1,30 @@ +/* + * 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.transformer; + +import org.springframework.integration.message.Message; + +/** + * Base interface for message transformers. + * + * @author Mark Fisher + */ +public interface MessageTransformer { + + void transform(Message message); + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/transformer/MessageTransformerChain.java b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/MessageTransformerChain.java new file mode 100644 index 0000000000..5751222795 --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/MessageTransformerChain.java @@ -0,0 +1,57 @@ +/* + * 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.transformer; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.springframework.integration.message.Message; + +/** + * @author Mark Fisher + */ +public class MessageTransformerChain implements MessageTransformer { + + private final List transformers = new CopyOnWriteArrayList(); + + + /** + * Add a transformer to the end of the chain. + */ + public void add(MessageTransformer transformer) { + this.transformers.add(transformer); + } + + /** + * Add a transformer to the chain at the specified index. + */ + public void add(int index, MessageTransformer transformer) { + this.transformers.add(index, transformer); + } + + public void setTransformers(List transformers) { + this.transformers.clear(); + this.transformers.addAll(transformers); + } + + public void transform(Message message) { + for (MessageTransformer next : transformers) { + next.transform(message); + } + } + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/transformer/PayloadTransformerAdapter.java b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/PayloadTransformerAdapter.java new file mode 100644 index 0000000000..2cce0efc46 --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/PayloadTransformerAdapter.java @@ -0,0 +1,37 @@ +/* + * 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.transformer; + +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessagingException; +import org.springframework.integration.util.AbstractMethodInvokingAdapter; + +/** + * @author Mark Fisher + */ +public class PayloadTransformerAdapter extends AbstractMethodInvokingAdapter implements MessageTransformer { + + @SuppressWarnings("unchecked") + public void transform(Message message) { + try { + message.setPayload(this.invokeMethod(message.getPayload())); + } catch (Exception e) { + throw new MessagingException(message, "failed to transform message payload", e); + } + } + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/transformer/TransformerMessageHandlerAdapter.java b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/TransformerMessageHandlerAdapter.java new file mode 100644 index 0000000000..f0d92daded --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/TransformerMessageHandlerAdapter.java @@ -0,0 +1,40 @@ +/* + * 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.transformer; + +import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.message.Message; + +/** + * @author Mark Fisher + */ +public class TransformerMessageHandlerAdapter implements MessageHandler { + + private final MessageTransformer transformer; + + + public TransformerMessageHandlerAdapter(MessageTransformer transformer) { + this.transformer = transformer; + } + + + public Message handle(Message message) { + this.transformer.transform(message); + return message; + } + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/transformer/config/TransformerMessageHandlerCreator.java b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/config/TransformerMessageHandlerCreator.java new file mode 100644 index 0000000000..182e8723d0 --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/transformer/config/TransformerMessageHandlerCreator.java @@ -0,0 +1,39 @@ +/* + * 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.transformer.config; + +import java.lang.reflect.Method; +import java.util.Map; + +import org.springframework.integration.handler.MessageHandler; +import org.springframework.integration.handler.config.AbstractMessageHandlerCreator; +import org.springframework.integration.transformer.AnnotationMethodTransformerAdapter; + +/** + * @author Mark Fisher + */ +public class TransformerMessageHandlerCreator extends AbstractMessageHandlerCreator { + + public MessageHandler doCreateHandler(Object object, Method method, Map attributes) { + AnnotationMethodTransformerAdapter adapter = new AnnotationMethodTransformerAdapter(); + adapter.setObject(object); + adapter.setMethodName(method.getName()); + adapter.afterPropertiesSet(); + return adapter; + } + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/util/AbstractMethodInvokingAdapter.java b/org.springframework.integration/src/main/java/org/springframework/integration/util/AbstractMethodInvokingAdapter.java new file mode 100644 index 0000000000..8b9a7314d8 --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/util/AbstractMethodInvokingAdapter.java @@ -0,0 +1,128 @@ +/* + * 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.factory.InitializingBean; +import org.springframework.core.Ordered; +import org.springframework.integration.ConfigurationException; + +/** + * An base class for adapters that invoke a 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. + * + * @author Mark Fisher + */ +public abstract class AbstractMethodInvokingAdapter implements MethodInvoker, InitializingBean, Ordered { + + protected final Log logger = LogFactory.getLog(this.getClass()); + + private volatile Object object; + + private volatile Method method; + + private volatile String methodName; + + private volatile int order; + + private volatile MethodInvoker invoker; + + private volatile boolean initialized; + + private final Object initializationMonitor = new Object(); + + + public void setObject(Object object) { + this.object = object; + } + + protected Object getObject() { + return this.object; + } + + public void setMethod(Method method) { + this.method = method; + } + + protected Method getMethod() { + return this.method; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + public void setOrder(int order) { + this.order = order; + } + + public int getOrder() { + return order; + } + + protected boolean isInitialized() { + return this.initialized; + } + + public void afterPropertiesSet() { + synchronized (this.initializationMonitor) { + if (this.initialized) { + return; + } + 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(); + } + + /** + * Subclasses may override this method for custom initialization requirements. + */ + protected void initialize() { + } + + public Object invokeMethod(Object ... args) throws Exception { + if (!this.initialized) { + this.afterPropertiesSet(); + } + return this.invoker.invokeMethod(args); + } + +} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java index f2d636ea1b..2213dade1f 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/MessagingAnnotationPostProcessorTests.java @@ -34,6 +34,7 @@ import org.springframework.aop.framework.ProxyFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.core.annotation.Order; import org.springframework.integration.ConfigurationException; import org.springframework.integration.annotation.Concurrency; import org.springframework.integration.annotation.Handler; @@ -42,6 +43,7 @@ import org.springframework.integration.annotation.MessageSource; import org.springframework.integration.annotation.MessageTarget; import org.springframework.integration.annotation.Polled; import org.springframework.integration.annotation.Splitter; +import org.springframework.integration.annotation.Transformer; import org.springframework.integration.bus.MessageBus; import org.springframework.integration.channel.ChannelRegistry; import org.springframework.integration.channel.ChannelRegistryAware; @@ -372,6 +374,17 @@ public class MessagingAnnotationPostProcessorTests { messageBus.stop(); } + @Test + public void testHandlerWithTransformers() { + MessageBus messageBus = new MessageBus(); + MessagingAnnotationPostProcessor postProcessor = new MessagingAnnotationPostProcessor(messageBus); + postProcessor.afterPropertiesSet(); + HandlerWithTransformers testBean = new HandlerWithTransformers(); + MessageHandler handler = (MessageHandler) postProcessor.postProcessAfterInitialization(testBean, "testBean"); + Message reply = handler.handle(new StringMessage("foo")); + assertEquals("PRE.FOO.post", reply.getPayload()); + } + @MessageEndpoint(input="testChannel") private static class TargetAnnotationTestBean { @@ -508,7 +521,28 @@ public class MessagingAnnotationPostProcessorTests { public String test() { return "test"; } + } + + private static class HandlerWithTransformers { + + @Transformer + @Order(-1) + public String transformBefore(String input) { + return "pre." + input; + } + + @Handler + @Order(0) + public String handle(String input) { + return input.toUpperCase(); + } + + @Transformer + @Order(1) + public String transformAfter(String input) { + return input + ".post"; + } } } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/handler/annotation/SimpleHandlerTestBean.java b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/SimpleHandlerTestBean.java similarity index 93% rename from org.springframework.integration/src/test/java/org/springframework/integration/handler/annotation/SimpleHandlerTestBean.java rename to org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/SimpleHandlerTestBean.java index 9a9c10db84..d60fc91105 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/handler/annotation/SimpleHandlerTestBean.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/SimpleHandlerTestBean.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.integration.handler.annotation; +package org.springframework.integration.config.annotation; import org.springframework.integration.annotation.Handler; diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/handlerAnnotationPostProcessorTests.xml b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/handlerAnnotationPostProcessorTests.xml index 8a17fdc4be..0201c658c5 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/handlerAnnotationPostProcessorTests.xml +++ b/org.springframework.integration/src/test/java/org/springframework/integration/config/annotation/handlerAnnotationPostProcessorTests.xml @@ -18,6 +18,6 @@ - + diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/transformer/MessageFilterTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/handler/MessageFilterTests.java similarity index 95% rename from org.springframework.integration/src/test/java/org/springframework/integration/transformer/MessageFilterTests.java rename to org.springframework.integration/src/test/java/org/springframework/integration/handler/MessageFilterTests.java index f9af9e42f5..32fc6ed470 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/transformer/MessageFilterTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/handler/MessageFilterTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.integration.transformer; +package org.springframework.integration.handler; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; +import org.springframework.integration.handler.MessageFilter; import org.springframework.integration.handler.MessageHandler; import org.springframework.integration.handler.MessageHandlerChain; import org.springframework.integration.message.Message; diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/transformer/AnnotationMethodTransformerAdapterTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/transformer/AnnotationMethodTransformerAdapterTests.java new file mode 100644 index 0000000000..8f6e135f96 --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/transformer/AnnotationMethodTransformerAdapterTests.java @@ -0,0 +1,190 @@ +/* + * 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.transformer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.Date; +import java.util.Properties; + +import org.junit.Test; + +import org.springframework.integration.annotation.Transformer; +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.MessageHandlingException; +import org.springframework.integration.message.MessagingException; +import org.springframework.integration.message.StringMessage; + +/** + * @author Mark Fisher + */ +public class AnnotationMethodTransformerAdapterTests { + + @Test + public void testSimplePayload() throws Exception { + TestBean testBean = new TestBean(); + AnnotationMethodTransformerAdapter adapter = new AnnotationMethodTransformerAdapter(); + adapter.setObject(testBean); + adapter.setMethod(testBean.getClass().getMethod("exclaim", String.class)); + Message message = new StringMessage("foo"); + adapter.transform(message); + assertEquals("FOO!", message.getPayload()); + } + + @Test + public void testTypeConversion() throws Exception { + TestBean testBean = new TestBean(); + AnnotationMethodTransformerAdapter adapter = new AnnotationMethodTransformerAdapter(); + adapter.setObject(testBean); + adapter.setMethod(testBean.getClass().getMethod("exclaim", String.class)); + Message message = new GenericMessage(123); + adapter.transform(message); + assertEquals("123!", message.getPayload()); + } + + @Test(expected=MessagingException.class) + public void testTypeConversionFailure() throws Exception { + TestBean testBean = new TestBean(); + AnnotationMethodTransformerAdapter adapter = new AnnotationMethodTransformerAdapter(); + adapter.setObject(testBean); + adapter.setMethod(testBean.getClass().getMethod("exclaim", String.class)); + Message message = new GenericMessage(new Date()); + adapter.transform(message); + } + + @Test + public void testHeaderAttributeAnnotation() throws Exception { + TestBean testBean = new TestBean(); + AnnotationMethodTransformerAdapter adapter = new AnnotationMethodTransformerAdapter(); + adapter.setObject(testBean); + adapter.setMethod(testBean.getClass().getMethod("attributeTest", String.class, Integer.class)); + Message message = new StringMessage("foo"); + message.getHeader().setAttribute("number", 123); + adapter.transform(message); + assertEquals("foo123", message.getPayload()); + } + + @Test(expected=MessageHandlingException.class) + public void testHeaderAttributeNotProvided() throws Exception { + TestBean testBean = new TestBean(); + AnnotationMethodTransformerAdapter adapter = new AnnotationMethodTransformerAdapter(); + adapter.setObject(testBean); + adapter.setMethod(testBean.getClass().getMethod("attributeTest", String.class, Integer.class)); + Message message = new StringMessage("foo"); + message.getHeader().setAttribute("wrong", 123); + adapter.transform(message); + } + + @Test + public void testHeaderPropertyAnnotation() throws Exception { + TestBean testBean = new TestBean(); + AnnotationMethodTransformerAdapter adapter = new AnnotationMethodTransformerAdapter(); + adapter.setObject(testBean); + adapter.setMethod(testBean.getClass().getMethod("propertyTest", String.class, String.class)); + Message message = new StringMessage("foo"); + message.getHeader().setProperty("suffix", "bar"); + adapter.transform(message); + assertEquals("foobar", message.getPayload()); + } + + @Test(expected=MessageHandlingException.class) + public void testHeaderPropertyNotAvailable() throws Exception { + TestBean testBean = new TestBean(); + AnnotationMethodTransformerAdapter adapter = new AnnotationMethodTransformerAdapter(); + adapter.setObject(testBean); + adapter.setMethod(testBean.getClass().getMethod("propertyTest", String.class, String.class)); + Message message = new StringMessage("foo"); + message.getHeader().setProperty("wrong", "bar"); + adapter.transform(message); + } + + @Test + public void testPropertyEnricher() throws Exception { + TestBean testBean = new TestBean(); + AnnotationMethodTransformerAdapter adapter = new AnnotationMethodTransformerAdapter(); + adapter.setObject(testBean); + adapter.setMethod(testBean.getClass().getMethod("propertyEnricherTest", String.class)); + Message message = new StringMessage("test"); + message.getHeader().setProperty("prop1", "bad"); + message.getHeader().setProperty("prop3", "baz"); + adapter.transform(message); + assertEquals("test", message.getPayload()); + assertEquals("foo", message.getHeader().getProperty("prop1")); + assertEquals("bar", message.getHeader().getProperty("prop2")); + assertEquals("baz", message.getHeader().getProperty("prop3")); + } + + @Test + public void testPropertyPayload() throws Exception { + TestBean testBean = new TestBean(); + AnnotationMethodTransformerAdapter adapter = new AnnotationMethodTransformerAdapter(); + adapter.setObject(testBean); + adapter.setMethod(testBean.getClass().getMethod("propertyPayloadTest", Properties.class)); + Properties props = new Properties(); + props.setProperty("prop1", "bad"); + props.setProperty("prop3", "baz"); + Message message = new GenericMessage(props); + adapter.transform(message); + assertEquals(Properties.class, message.getPayload().getClass()); + Properties payload = message.getPayload(); + assertEquals("foo", payload.getProperty("prop1")); + assertEquals("bar", payload.getProperty("prop2")); + assertEquals("baz", payload.getProperty("prop3")); + assertNull(message.getHeader().getProperty("prop1")); + assertNull(message.getHeader().getProperty("prop2")); + assertNull(message.getHeader().getProperty("prop3")); + } + + + private static class TestBean { + + @Transformer + public String exclaim(String s) { + return s.toUpperCase() + "!"; + } + + @Transformer + public String attributeTest(String s, @HeaderAttribute("number") Integer num) { + return s + num; + } + + @Transformer + public String propertyTest(String s, @HeaderProperty("suffix") String suffix) { + return s + suffix; + } + + @Transformer + public Properties propertyEnricherTest(String s) { + Properties properties = new Properties(); + properties.setProperty("prop1", "foo"); + properties.setProperty("prop2", "bar"); + return properties; + } + + @Transformer + public Properties propertyPayloadTest(Properties properties) { + properties.setProperty("prop1", "foo"); + properties.setProperty("prop2", "bar"); + return properties; + } + } + +} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/transformer/PayloadTransformerAdapterTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/transformer/PayloadTransformerAdapterTests.java new file mode 100644 index 0000000000..951cb8442a --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/transformer/PayloadTransformerAdapterTests.java @@ -0,0 +1,72 @@ +/* + * 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.transformer; + +import static org.junit.Assert.assertEquals; + +import java.util.Date; + +import org.junit.Test; + +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.message.Message; +import org.springframework.integration.message.MessagingException; +import org.springframework.integration.message.StringMessage; + +/** + * @author Mark Fisher + */ +public class PayloadTransformerAdapterTests { + + @Test + public void testSimpleMethod() { + PayloadTransformerAdapter adapter = new PayloadTransformerAdapter(); + adapter.setObject(new TestBean()); + adapter.setMethodName("exclaim"); + Message message = new StringMessage("foo"); + adapter.transform(message); + assertEquals("FOO!", message.getPayload()); + } + + @Test + public void testTypeConversion() { + PayloadTransformerAdapter adapter = new PayloadTransformerAdapter(); + adapter.setObject(new TestBean()); + adapter.setMethodName("exclaim"); + Message message = new GenericMessage(123); + adapter.transform(message); + assertEquals("123!", message.getPayload()); + } + + @Test(expected=MessagingException.class) + public void testTypeConversionFailure() { + PayloadTransformerAdapter adapter = new PayloadTransformerAdapter(); + adapter.setObject(new TestBean()); + adapter.setMethodName("exclaim"); + Message message = new GenericMessage(new Date()); + adapter.transform(message); + } + + + private static class TestBean { + + String exclaim(String input) { + return input.toUpperCase() + "!"; + } + } + +}