diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aop/ExpressionBinding.java b/org.springframework.integration/src/main/java/org/springframework/integration/aop/ExpressionBinding.java new file mode 100644 index 0000000000..af37f0c4d4 --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aop/ExpressionBinding.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.aop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that provides the variable names to use when constructing the + * evaluation context for a MessagePublishingInterceptor. + * + * @author Mark Fisher + * @since 2.0 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExpressionBinding { + + /** + * Names of the arguments as a comma-separated list. If not provided, the + * names will be discovered automatically if enabled by the compiler settings. + * These names will be used as the keys in the argument Map. + */ + String argNames() default ""; + + /** + * Name of the variable in the context that refers to the Map of arguments. + *

The default is "args". + */ + String argumentMapName() default ExpressionSource.DEFAULT_ARGUMENT_MAP_NAME; + + /** + * Name of the variable in the context that refers to the return value, if any. + *

The default is "return". + */ + String returnValueName() default ExpressionSource.DEFAULT_RETURN_VALUE_NAME; + + /** + * Name of the variable in the context that refers to any exception thrown + * by the method invocation that is being intercepted. + *

The default is "exception". + */ + String exceptionName() default ExpressionSource.DEFAULT_EXCEPTION_NAME; + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aop/ExpressionSource.java b/org.springframework.integration/src/main/java/org/springframework/integration/aop/ExpressionSource.java new file mode 100644 index 0000000000..10503ef3de --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aop/ExpressionSource.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.aop; + +import java.lang.reflect.Method; + +/** + * Strategy for determining the expression string and evaluation context + * variable names from a Method. + * + * @author Mark Fisher + * @since 2.0 + */ +interface ExpressionSource { + + static final String DEFAULT_ARGUMENT_MAP_NAME = "args"; + + static final String DEFAULT_RETURN_VALUE_NAME = "return"; + + static final String DEFAULT_EXCEPTION_NAME = "exception"; + + + /** + * Returns the expression string to be evaluated. + */ + String getExpressionString(Method method); + + /** + * Returns the variable names to be associated with the intercepted method + * invocation's argument array. + */ + String[] getArgumentNames(Method method); + + /** + * Returns the variable name to use in the evaluation context for the Map + * of arguments. The keys in this map will be determined by the result of + * the {@link #getArgumentNames(Method)} method. + */ + String getArgumentMapName(Method method); + + /** + * Returns the variable name to use in the evaluation context for any + * return value resulting from the method invocation. + */ + String getReturnValueName(Method method); + + /** + * Returns the variable name to use in the evaluation context for any + * exception thrown from the method invocation. + */ + String getExceptionName(Method method); + + /** + * Returns the channel name to which Messages should be published + * for this particular method invocation. + */ + String getChannelName(Method method); + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java b/org.springframework.integration/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java new file mode 100644 index 0000000000..ad3b49db1e --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aop/MessagePublishingInterceptor.java @@ -0,0 +1,131 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.aop; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +import org.springframework.aop.support.AopUtils; +import org.springframework.context.expression.MapAccessor; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.SpelExpressionParserFactory; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.integration.channel.ChannelResolver; +import org.springframework.integration.channel.MessageChannelTemplate; +import org.springframework.integration.core.Message; +import org.springframework.integration.core.MessageChannel; +import org.springframework.integration.gateway.SimpleMessageMapper; +import org.springframework.integration.message.InboundMessageMapper; +import org.springframework.util.Assert; + +/** + * A {@link MethodInterceptor} that publishes Messages to a channel. The + * payload of the published Message can be derived from arguments or any return + * value or exception resulting from the method invocation. That mapping is the + * responsibility of the EL expression provided by the ExpressionSource. + * + * @author Mark Fisher + * @since 2.0 + */ +public class MessagePublishingInterceptor implements MethodInterceptor { + + private final MessageChannelTemplate channelTemplate = new MessageChannelTemplate(); + + private final ExpressionSource expressionSource; + + private final ExpressionParser parser = SpelExpressionParserFactory.getParser(); + + private final InboundMessageMapper messageMapper = new SimpleMessageMapper(); + + private volatile ChannelResolver channelResolver; + + + public MessagePublishingInterceptor(ExpressionSource expressionSource) { + Assert.notNull(expressionSource, "expressionSource must not be null"); + this.expressionSource = expressionSource; + } + + + public void setDefaultChannel(MessageChannel defaultChannel) { + this.channelTemplate.setDefaultChannel(defaultChannel); + } + + public void setChannelResolver(ChannelResolver channelResolver) { + this.channelResolver = channelResolver; + } + + public final Object invoke(final MethodInvocation invocation) throws Throwable { + StandardEvaluationContext context = new StandardEvaluationContext(); + context.addPropertyAccessor(new MapAccessor()); + Class targetClass = AopUtils.getTargetClass(invocation.getThis()); + Method method = AopUtils.getMostSpecificMethod(invocation.getMethod(), targetClass); + String[] argumentNames = this.expressionSource.getArgumentNames(method); + if (invocation.getArguments().length > 0 && argumentNames != null) { + int index = 0; + Map argumentMap = new HashMap(); + for (String argumentName : argumentNames) { + if (invocation.getArguments().length <= ++index) { + break; + } + argumentMap.put(argumentName, invocation.getArguments()[index]); + } + context.setVariable(this.expressionSource.getArgumentMapName(method), argumentMap); + } + try { + Object returnValue = invocation.proceed(); + context.setVariable(this.expressionSource.getReturnValueName(method), returnValue); + return returnValue; + } + catch (Throwable t) { + context.setVariable(this.expressionSource.getExceptionName(method), t); + throw t; + } + finally { + publishMessage(method, context); + } + } + + private void publishMessage(Method method, EvaluationContext context) throws Exception { + String expressionString = this.expressionSource.getExpressionString(method); + if (expressionString != null) { + Expression expression = this.parser.parseExpression(expressionString); + Object result = expression.getValue(context); + if (result != null) { + Message message = this.messageMapper.toMessage(result); + String channelName = this.expressionSource.getChannelName(method); + MessageChannel channel = null; + if (channelName != null) { + Assert.state(this.channelResolver != null, "ChannelResolver is required to resolve channel names."); + channel = this.channelResolver.resolveChannelName(channelName); + } + if (channel != null) { + this.channelTemplate.send(message, channel); + } + else { + this.channelTemplate.send(message); + } + } + } + } + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aop/MethodAnnotationExpressionSource.java b/org.springframework.integration/src/main/java/org/springframework/integration/aop/MethodAnnotationExpressionSource.java new file mode 100644 index 0000000000..e276855ee1 --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aop/MethodAnnotationExpressionSource.java @@ -0,0 +1,132 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.aop; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Set; + +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * An {@link ExpressionSource} implementation that retrieves the expression + * string and evaluation context variable names from an annotation. + * + * @author Mark Fisher + * @since 2.0 + */ +public class MethodAnnotationExpressionSource implements ExpressionSource { + + private final Set> annotationTypes; + + private volatile String channelAttributeName = "channel"; + + private final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); + + + public MethodAnnotationExpressionSource() { + this(Collections.>singleton(Publisher.class)); + } + + public MethodAnnotationExpressionSource(Set> annotationTypes) { + Assert.notEmpty(annotationTypes, "annotationTypes must not be empty"); + this.annotationTypes = annotationTypes; + } + + + public void setChannelAttributeName(String channelAttributeName) { + Assert.hasText(channelAttributeName, "channelAttributeName must not be empty"); + this.channelAttributeName = channelAttributeName; + } + + public String getExpressionString(Method method) { + return this.getAnnotationValue(method, null, String.class); + } + + public String[] getArgumentNames(Method method) { + ExpressionBinding annotation = AnnotationUtils.findAnnotation(method, ExpressionBinding.class); + if (annotation != null) { + String name = annotation.argNames(); + if (StringUtils.hasText(name)) { + return StringUtils.tokenizeToStringArray(name, ","); + } + } + return this.parameterNameDiscoverer.getParameterNames(method); + } + + public String getArgumentMapName(Method method) { + ExpressionBinding annotation = AnnotationUtils.findAnnotation(method, ExpressionBinding.class); + if (annotation != null) { + return annotation.argumentMapName(); + } + return ExpressionSource.DEFAULT_ARGUMENT_MAP_NAME; + } + + public String getReturnValueName(Method method) { + ExpressionBinding annotation = AnnotationUtils.findAnnotation(method, ExpressionBinding.class); + if (annotation != null) { + return annotation.returnValueName(); + } + return ExpressionSource.DEFAULT_RETURN_VALUE_NAME; + } + + public String getExceptionName(Method method) { + ExpressionBinding annotation = AnnotationUtils.findAnnotation(method, ExpressionBinding.class); + if (annotation != null) { + return annotation.exceptionName(); + } + return ExpressionSource.DEFAULT_EXCEPTION_NAME; + } + + public String getChannelName(Method method) { + String channelName = this.getAnnotationValue(method, this.channelAttributeName, String.class); + return (StringUtils.hasText(channelName) ? channelName : null); + } + + @SuppressWarnings("unchecked") + private T getAnnotationValue(Method method, String attributeName, Class expectedType) { + T value = null; + for (Class annotationType : this.annotationTypes) { + Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType); + if (annotation != null) { + if (value != null) { + throw new IllegalStateException( + "method [" + method + "] contains more than one publisher annotation"); + } + Object valueAsObject = (attributeName == null) ? AnnotationUtils.getValue(annotation) + : AnnotationUtils.getValue(annotation, attributeName); + if (valueAsObject != null) { + if (expectedType.isAssignableFrom(valueAsObject.getClass())) { + value = (T) valueAsObject; + } + else { + throw new IllegalArgumentException("expected type [" + expectedType.getName() + + "] for attribute '" + attributeName + "' on publisher annotation [" + + annotationType + "], but actual type was [" + valueAsObject.getClass() + "]"); + } + } + } + } + return value; + } + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aop/Publisher.java b/org.springframework.integration/src/main/java/org/springframework/integration/aop/Publisher.java new file mode 100644 index 0000000000..9aaa72ea4c --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aop/Publisher.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.aop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to indicate that a method, or all public methods if applied at + * class-level, should publish Messages whose payloads will be determined by + * the provided EL expression. + * + * @author Mark Fisher + * @since 2.0 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Publisher { + + /** + * String representation of a Spel Expression. Required. + */ + String value(); + + /** + * Name of the Message Channel to which Messages will be published. + */ + String channel() default ""; + +} diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java b/org.springframework.integration/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java new file mode 100644 index 0000000000..0066fb677e --- /dev/null +++ b/org.springframework.integration/src/main/java/org/springframework/integration/aop/PublisherAnnotationAdvisor.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.aop; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.Pointcut; +import org.springframework.aop.support.AbstractPointcutAdvisor; +import org.springframework.aop.support.ComposablePointcut; +import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.integration.channel.BeanFactoryChannelResolver; +import org.springframework.integration.core.MessageChannel; + +/** + * An advisor that will apply the {@link MessagePublishingInterceptor} to any + * methods containing the provided annotations. If no annotations are provided, + * the default will be {@link Publisher @Publisher}. + * + * @author Mark Fisher + * @since 2.0 + */ +@SuppressWarnings("serial") +public class PublisherAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware { + + private final Set> publisherAnnotationTypes; + + private final MessagePublishingInterceptor interceptor; + + + public PublisherAnnotationAdvisor(Class ... publisherAnnotationTypes) { + this.publisherAnnotationTypes = new HashSet>(Arrays.asList(publisherAnnotationTypes)); + ExpressionSource source = new MethodAnnotationExpressionSource(this.publisherAnnotationTypes); + this.interceptor = new MessagePublishingInterceptor(source); + } + + @SuppressWarnings("unchecked") + public PublisherAnnotationAdvisor() { + this(Publisher.class); + } + + + public void setDefaultChannel(MessageChannel defaultChannel) { + this.interceptor.setDefaultChannel(defaultChannel); + } + + public void setBeanFactory(BeanFactory beanFactory) { + this.interceptor.setChannelResolver(new BeanFactoryChannelResolver(beanFactory)); + } + + public Advice getAdvice() { + return this.interceptor; + } + + public Pointcut getPointcut() { + return this.buildPointcut(); + } + + private Pointcut buildPointcut() { + ComposablePointcut result = null; + for (Class publisherAnnotationType : this.publisherAnnotationTypes) { + Pointcut cpc = new AnnotationMatchingPointcut(publisherAnnotationType, true); + Pointcut mpc = new AnnotationMatchingPointcut(null, publisherAnnotationType); + if (result == null) { + result = new ComposablePointcut(cpc).union(mpc); + } + else { + result.union(cpc).union(mpc); + } + } + return result; + } + +} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorTests.java new file mode 100644 index 0000000000..89486c527a --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/aop/MessagePublishingInterceptorTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.aop; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.lang.reflect.Method; +import java.util.Collections; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.integration.channel.MapBasedChannelResolver; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.core.Message; + +/** + * @author Mark Fisher + * @since 2.0 + */ +public class MessagePublishingInterceptorTests { + + private final ExpressionSource source = new TestExpressionSource(); + + private final MapBasedChannelResolver channelResolver = new MapBasedChannelResolver(); + + private final QueueChannel testChannel = new QueueChannel(); + + + @Before + public void setup() { + channelResolver.setChannelMap(Collections.singletonMap("c", testChannel)); + } + + @Test + public void returnValue() { + MessagePublishingInterceptor interceptor = new MessagePublishingInterceptor(source); + interceptor.setChannelResolver(channelResolver); + ProxyFactory pf = new ProxyFactory(new TestBeanImpl()); + pf.addAdvice(interceptor); + TestBean proxy = (TestBean) pf.getProxy(); + proxy.test(); + Message message = testChannel.receive(0); + assertNotNull(message); + assertEquals("foo", message.getPayload()); + } + + + static interface TestBean { + + String test(); + + } + + + static class TestBeanImpl implements TestBean { + + public String test() { + return "foo"; + } + + } + + + private static class TestExpressionSource implements ExpressionSource { + + public String getArgumentMapName(Method method) { + return "m"; + } + + public String[] getArgumentNames(Method method) { + return new String[] { "a1", "a2" }; + } + + public String getChannelName(Method method) { + return "c"; + } + + public String getExceptionName(Method method) { + return "x"; + } + + public String getExpressionString(Method method) { + return "#r"; + } + + public String getReturnValueName(Method method) { + return "r"; + } + + } + +} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aop/MethodAnnotationExpressionSourceTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aop/MethodAnnotationExpressionSourceTests.java new file mode 100644 index 0000000000..2eb2c1393b --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/aop/MethodAnnotationExpressionSourceTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.aop; + +import static org.junit.Assert.assertEquals; + +import java.lang.reflect.Method; + +import org.junit.Test; + +/** + * @author Mark Fisher + * @since 2.0 + */ +public class MethodAnnotationExpressionSourceTests { + + private final MethodAnnotationExpressionSource source = new MethodAnnotationExpressionSource(); + + @Test + public void defaultBindings() { + Method method = getMethod("methodWithExpressionAnnotationOnly", String.class, int.class); + String expressionString = source.getExpressionString(method); + assertEquals("testExpression1", expressionString); + assertEquals(2, source.getArgumentNames(method).length); + assertEquals("arg1", source.getArgumentNames(method)[0]); + assertEquals("arg2", source.getArgumentNames(method)[1]); + assertEquals(ExpressionSource.DEFAULT_ARGUMENT_MAP_NAME, source.getArgumentMapName(method)); + assertEquals(ExpressionSource.DEFAULT_EXCEPTION_NAME, source.getExceptionName(method)); + assertEquals(ExpressionSource.DEFAULT_RETURN_VALUE_NAME, source.getReturnValueName(method)); + } + + @Test + public void annotationBindings() { + Method method = getMethod("methodWithExpressionBinding", String.class, int.class); + String expressionString = source.getExpressionString(method); + assertEquals("testExpression2", expressionString); + assertEquals(2, source.getArgumentNames(method).length); + assertEquals("s", source.getArgumentNames(method)[0]); + assertEquals("i", source.getArgumentNames(method)[1]); + assertEquals("argz", source.getArgumentMapName(method)); + assertEquals("x", source.getExceptionName(method)); + assertEquals("result", source.getReturnValueName(method)); + } + + @Test + public void channelName() { + Method method = getMethod("methodWithChannelAndReturnAsPayload"); + String channelName = source.getChannelName(method); + assertEquals("foo", channelName); + } + + + private static Method getMethod(String name, Class ... params) { + try { + return MethodAnnotationExpressionSourceTests.class.getMethod(name, params); + } + catch (Exception e) { + throw new RuntimeException("failed to resolve method", e); + } + } + + + @Publisher("testExpression1") + public void methodWithExpressionAnnotationOnly(String arg1, int arg2) { + } + + @Publisher(value="#return", channel="foo") + public void methodWithChannelAndReturnAsPayload() { + } + + @Publisher("testExpression2") + @ExpressionBinding(argNames="s, i", argumentMapName="argz", exceptionName="x", returnValueName="result") + public void methodWithExpressionBinding(String arg1, int arg2) { + } + +} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/aop/PublisherAnnotationAdvisorTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/aop/PublisherAnnotationAdvisorTests.java new file mode 100644 index 0000000000..d04fd32c54 --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/aop/PublisherAnnotationAdvisorTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.integration.aop; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.integration.channel.QueueChannel; +import org.springframework.integration.core.Message; + +/** + * @author Mark Fisher + * @since 2.0 + */ +public class PublisherAnnotationAdvisorTests { + + private final StaticApplicationContext context = new StaticApplicationContext(); + + + @Before + public void setup() { + context.registerSingleton("testChannel", QueueChannel.class); + } + + @Test + public void returnValue() { + PublisherAnnotationAdvisor advisor = new PublisherAnnotationAdvisor(); + advisor.setBeanFactory(context); + QueueChannel testChannel = context.getBean("testChannel", QueueChannel.class); + advisor.setDefaultChannel(testChannel); + ProxyFactory pf = new ProxyFactory(new TestBeanImpl()); + pf.addAdvisor(advisor); + TestBean proxy = (TestBean) pf.getProxy(); + proxy.test(); + Message message = testChannel.receive(0); + assertNotNull(message); + assertEquals("foo", message.getPayload()); + } + + + static interface TestBean { + + String test(); + + } + + + static class TestBeanImpl implements TestBean { + + @Publisher("#return") + public String test() { + return "foo"; + } + + } + +}