/*
* Copyright 2002-2017 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 static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.AdditionalAnswers.returnsFirstArg;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.mock;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.aopalliance.intercept.MethodInterceptor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hamcrest.Description;
import org.hamcrest.Matchers;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.expression.spel.SpelCompilerMode;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.annotation.UseSpelInvoker;
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
import org.springframework.integration.gateway.RequestReplyExchanger;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.util.MessagingMethodInvokerHelper;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.util.StopWatch;
/**
* @author Mark Fisher
* @author Marius Bogoevici
* @author Oleg Zhurakousky
* @author Dave Syer
* @author Gary Russell
* @author Gunnar Hillert
* @author Artem Bilan
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public class MethodInvokingMessageProcessorTests {
private static final Log logger = LogFactory.getLog(MethodInvokingMessageProcessorTests.class);
@Rule
public ExpectedException expected = ExpectedException.none();
@Test
public void testHandlerInheritanceMethodImplInSuper() {
class A {
@SuppressWarnings("unused")
public Message<String> myMethod(final Message<String> msg) {
return MessageBuilder.fromMessage(msg).setHeader("A", "A").build();
}
}
class B extends A {
}
@SuppressWarnings("unused")
class C extends B {
}
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new B(), "myMethod");
Message<?> message = (Message<?>) processor.processMessage(new GenericMessage<String>(""));
assertEquals("A", message.getHeaders().get("A"));
}
@Test
public void testHandlerInheritanceMethodImplInLatestSuper() {
class A {
@SuppressWarnings("unused")
public Message<String> myMethod(Message<String> msg) {
return MessageBuilder.fromMessage(msg).setHeader("A", "A").build();
}
}
class B extends A {
@Override
public Message<String> myMethod(Message<String> msg) {
return MessageBuilder.fromMessage(msg).setHeader("B", "B").build();
}
}
@SuppressWarnings("unused")
class C extends B {
}
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new B(), "myMethod");
Message<?> message = (Message<?>) processor.processMessage(new GenericMessage<String>(""));
assertEquals("B", message.getHeaders().get("B"));
}
public void testHandlerInheritanceMethodImplInSubClass() {
class A {
@SuppressWarnings("unused")
public Message<String> myMethod(Message<String> msg) {
return MessageBuilder.fromMessage(msg).setHeader("A", "A").build();
}
}
class B extends A {
@Override
public Message<String> myMethod(Message<String> msg) {
return MessageBuilder.fromMessage(msg).setHeader("B", "B").build();
}
}
class C extends B {
@Override
public Message<String> myMethod(Message<String> msg) {
return MessageBuilder.fromMessage(msg).setHeader("C", "C").build();
}
}
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new C(), "myMethod");
Message<?> message = (Message<?>) processor.processMessage(new GenericMessage<String>(""));
assertEquals("C", message.getHeaders().get("C"));
}
public void testHandlerInheritanceMethodImplInSubClassAndSuper() {
class A {
@SuppressWarnings("unused")
public Message<String> myMethod(Message<String> msg) {
return MessageBuilder.fromMessage(msg).setHeader("A", "A").build();
}
}
class B extends A {
}
class C extends B {
@Override
public Message<String> myMethod(Message<String> msg) {
return MessageBuilder.fromMessage(msg).setHeader("C", "C").build();
}
}
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new C(), "myMethod");
Message<?> message = (Message<?>) processor.processMessage(new GenericMessage<String>(""));
assertEquals("C", message.getHeaders().get("C"));
}
@Test
public void payloadAsMethodParameterAndObjectAsReturnValue() {
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(),
"acceptPayloadAndReturnObject");
Object result = processor.processMessage(new GenericMessage<String>("testing"));
assertEquals("testing-1", result);
}
@Test
public void testPayloadCoercedToString() {
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(),
"acceptPayloadAndReturnObject");
Object result = processor.processMessage(new GenericMessage<Integer>(123456789));
assertEquals("123456789-1", result);
}
@Test
public void payloadAsMethodParameterAndMessageAsReturnValue() {
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(),
"acceptPayloadAndReturnMessage");
Message<?> result = (Message<?>) processor.processMessage(new GenericMessage<String>("testing"));
assertEquals("testing-2", result.getPayload());
}
@Test
public void messageAsMethodParameterAndObjectAsReturnValue() {
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(),
"acceptMessageAndReturnObject");
Object result = processor.processMessage(new GenericMessage<String>("testing"));
assertEquals("testing-3", result);
}
@Test
public void messageAsMethodParameterAndMessageAsReturnValue() {
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(),
"acceptMessageAndReturnMessage");
Message<?> result = (Message<?>) processor.processMessage(new GenericMessage<String>("testing"));
assertEquals("testing-4", result.getPayload());
}
@Test
public void messageSubclassAsMethodParameterAndMessageAsReturnValue() {
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(),
"acceptMessageSubclassAndReturnMessage");
Message<?> result = (Message<?>) processor.processMessage(new GenericMessage<String>("testing"));
assertEquals("testing-5", result.getPayload());
}
@Test
public void messageSubclassAsMethodParameterAndMessageSubclassAsReturnValue() {
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(),
"acceptMessageSubclassAndReturnMessageSubclass");
Message<?> result = (Message<?>) processor.processMessage(new GenericMessage<String>("testing"));
assertEquals("testing-6", result.getPayload());
}
@Test
public void payloadAndHeaderAnnotationMethodParametersAndObjectAsReturnValue() {
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(),
"acceptPayloadAndHeaderAndReturnObject");
Message<?> request = MessageBuilder.withPayload("testing").setHeader("number", 123).build();
Object result = processor.processMessage(request);
assertEquals("testing-123", result);
}
@Test
public void testVoidMethodsIncludedByDefault() {
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new TestBean(),
"testVoidReturningMethods");
assertNull(processor.processMessage(MessageBuilder.withPayload("Something").build()));
assertEquals(12, processor.processMessage(MessageBuilder.withPayload(12).build()));
}
@Test
public void messageOnlyWithAnnotatedMethod() throws Exception {
AnnotatedTestService service = new AnnotatedTestService();
Method method = service.getClass().getMethod("messageOnly", Message.class);
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method);
Object result = processor.processMessage(new GenericMessage<String>("foo"));
assertEquals("foo", result);
}
@Test
public void payloadWithAnnotatedMethod() throws Exception {
AnnotatedTestService service = new AnnotatedTestService();
Method method = service.getClass().getMethod("integerMethod", Integer.class);
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method);
Object result = processor.processMessage(new GenericMessage<>(123));
assertEquals(123, result);
}
@Test
public void convertedPayloadWithAnnotatedMethod() throws Exception {
AnnotatedTestService service = new AnnotatedTestService();
Method method = service.getClass().getMethod("integerMethod", Integer.class);
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method);
Object result = processor.processMessage(new GenericMessage<String>("456"));
assertEquals(456, result);
}
@Test(expected = MessageHandlingException.class)
public void conversionFailureWithAnnotatedMethod() throws Exception {
AnnotatedTestService service = new AnnotatedTestService();
Method method = service.getClass().getMethod("integerMethod", Integer.class);
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method);
processor.processMessage(new GenericMessage<String>("foo"));
}
@Test
public void filterSelectsAnnotationMethodsOnly() {
OverloadedMethodBean bean = new OverloadedMethodBean();
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(bean, ServiceActivator.class);
processor.processMessage(MessageBuilder.withPayload(123).build());
assertNotNull(bean.lastArg);
assertEquals(String.class, bean.lastArg.getClass());
assertEquals("123", bean.lastArg);
}
@Test
public void testProcessMessageBadExpression() throws Exception {
expected.expect(MessageHandlingException.class);
AnnotatedTestService service = new AnnotatedTestService();
Method method = service.getClass().getMethod("integerMethod", Integer.class);
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method);
assertEquals("foo", processor.processMessage(new GenericMessage<String>("foo")));
}
@Test
public void testProcessMessageRuntimeException() throws Exception {
expected.expect(new ExceptionCauseMatcher(UnsupportedOperationException.class));
TestErrorService service = new TestErrorService();
Method method = service.getClass().getMethod("error", String.class);
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method);
assertEquals("foo", processor.processMessage(new GenericMessage<String>("foo")));
}
@Test
public void testProcessMessageCheckedException() throws Exception {
expected.expect(new ExceptionCauseMatcher(CheckedException.class));
TestErrorService service = new TestErrorService();
Method method = service.getClass().getMethod("checked", String.class);
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method);
assertEquals("foo", processor.processMessage(new GenericMessage<String>("foo")));
}
@Test
public void testProcessMessageMethodNotFound() throws Exception {
expected.expect(new ExceptionCauseMatcher(SpelEvaluationException.class));
TestDifferentErrorService service = new TestDifferentErrorService();
Method method = TestErrorService.class.getMethod("checked", String.class);
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method);
processor.setUseSpelInvoker(true);
processor.processMessage(new GenericMessage<String>("foo"));
}
@Test
public void messageAndHeaderWithAnnotatedMethod() throws Exception {
AnnotatedTestService service = new AnnotatedTestService();
Method method = service.getClass().getMethod("messageAndHeader", Message.class, Integer.class);
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method);
Message<String> message = MessageBuilder.withPayload("foo").setHeader("number", 42).build();
Object result = processor.processMessage(message);
assertEquals("foo-42", result);
}
@Test
public void multipleHeadersWithAnnotatedMethod() throws Exception {
AnnotatedTestService service = new AnnotatedTestService();
Method method = service.getClass().getMethod("twoHeaders", String.class, Integer.class);
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method);
Message<String> message = MessageBuilder.withPayload("foo").setHeader("prop", "bar").setHeader("number", 42)
.build();
Object result = processor.processMessage(message);
assertEquals("bar-42", result);
}
@Test
public void optionalAndRequiredWithAnnotatedMethod() throws Exception {
AnnotatedTestService service = new AnnotatedTestService();
Method method = service.getClass().getMethod("optionalAndRequiredHeader", String.class, Integer.class);
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method);
processor.setUseSpelInvoker(true);
optionalAndRequiredWithAnnotatedMethodGuts(processor, false);
}
@Test
public void compiledOptionalAndRequiredWithAnnotatedMethod() throws Exception {
AnnotatedTestService service = new AnnotatedTestService();
Method method = service.getClass().getMethod("optionalAndRequiredHeader", String.class, Integer.class);
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method);
processor.setUseSpelInvoker(true);
DirectFieldAccessor compilerConfigAccessor = compileImmediate(processor);
optionalAndRequiredWithAnnotatedMethodGuts(processor, true);
assertNotNull(TestUtils.getPropertyValue(processor, "delegate.handlerMethod.expression.compiledAst"));
optionalAndRequiredWithAnnotatedMethodGuts(processor, true);
compilerConfigAccessor.setPropertyValue("compilerMode", SpelCompilerMode.OFF);
}
private void optionalAndRequiredWithAnnotatedMethodGuts(MethodInvokingMessageProcessor processor,
boolean compiled) {
Message<String> message = MessageBuilder.withPayload("foo")
.setHeader("num", 42)
.build();
Object result = processor.processMessage(message);
assertEquals("null42", result);
message = MessageBuilder.withPayload("foo")
.setHeader("prop", "bar")
.setHeader("num", 42)
.build();
result = processor.processMessage(message);
assertEquals("bar42", result);
message = MessageBuilder.withPayload("foo")
.setHeader("prop", "bar")
.build();
try {
result = processor.processMessage(message);
fail("Expected MessageHandlingException");
}
catch (MessageHandlingException e) {
if (compiled) {
assertThat(e.getCause().getMessage(), equalTo("required header not available: num"));
}
else {
assertThat(e.getCause().getCause().getMessage(), equalTo("required header not available: num"));
}
}
}
@Test
public void optionalAndRequiredDottedWithAnnotatedMethod() throws Exception {
AnnotatedTestService service = new AnnotatedTestService();
Method method = service.getClass().getMethod("optionalAndRequiredDottedHeader", String.class, Integer.class);
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method);
processor.setUseSpelInvoker(true);
optionalAndRequiredDottedWithAnnotatedMethodGuts(processor, false);
}
@Test
public void compiledOptionalAndRequiredDottedWithAnnotatedMethod() throws Exception {
AnnotatedTestService service = new AnnotatedTestService();
Method method = service.getClass().getMethod("optionalAndRequiredDottedHeader", String.class, Integer.class);
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method);
processor.setUseSpelInvoker(true);
DirectFieldAccessor compilerConfigAccessor = compileImmediate(processor);
optionalAndRequiredDottedWithAnnotatedMethodGuts(processor, true);
assertNotNull(TestUtils.getPropertyValue(processor, "delegate.handlerMethod.expression.compiledAst"));
optionalAndRequiredDottedWithAnnotatedMethodGuts(processor, true);
compilerConfigAccessor.setPropertyValue("compilerMode", SpelCompilerMode.OFF);
}
private void optionalAndRequiredDottedWithAnnotatedMethodGuts(MethodInvokingMessageProcessor processor,
boolean compiled) {
Message<String> message = MessageBuilder.withPayload("hello")
.setHeader("dot2", new DotBean())
.build();
Object result = processor.processMessage(message);
assertEquals("null42", result);
message = MessageBuilder.withPayload("hello")
.setHeader("dot1", new DotBean())
.setHeader("dot2", new DotBean())
.build();
result = processor.processMessage(message);
assertEquals("bar42", result);
message = MessageBuilder.withPayload("hello")
.setHeader("dot1", new DotBean())
.build();
try {
result = processor.processMessage(message);
fail("Expected MessageHandlingException");
}
catch (MessageHandlingException e) {
if (compiled) {
assertThat(e.getCause().getMessage(), equalTo("required header not available: dot2"));
}
else { // interpreted
assertThat(e.getCause().getCause().getMessage(), equalTo("required header not available: dot2"));
}
}
}
@Test
public void testOverloadedNonVoidReturningMethodsWithExactMatchForType() {
AmbiguousMethodBean bean = new AmbiguousMethodBean();
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(bean, "foo");
processor.processMessage(MessageBuilder.withPayload("true").build());
assertNotNull(bean.lastArg);
assertEquals(String.class, bean.lastArg.getClass());
assertEquals("true", bean.lastArg);
}
@Test
public void gatewayTest() throws Exception {
GatewayProxyFactoryBean gwFactoryBean = new GatewayProxyFactoryBean();
gwFactoryBean.setBeanFactory(mock(BeanFactory.class));
gwFactoryBean.afterPropertiesSet();
Object target = gwFactoryBean.getObject();
// just instantiate a helper with a simple target; we're going to invoke getTargetClass with reflection
MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(new TestErrorService(), "error", true);
Method method = MessagingMethodInvokerHelper.class.getDeclaredMethod("getTargetClass", Object.class);
method.setAccessible(true);
Object result = method.invoke(helper, target);
assertSame(RequestReplyExchanger.class, result);
}
@Test
public void testInt3199GenericTypeResolvingAndObjectMethod() throws Exception {
class Foo {
@SuppressWarnings("unused")
public String handleMessage(Message<Number> message) {
return "" + (message.getPayload().intValue() * 2);
}
@SuppressWarnings("unused")
public String objectMethod(Integer foo) {
return foo.toString();
}
@SuppressWarnings("unused")
public String voidMethod() {
return "foo";
}
}
MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(new Foo(), (String) null, false);
assertEquals("4", helper.process(new GenericMessage<Object>(2L)));
assertEquals("1", helper.process(new GenericMessage<Object>(1)));
assertEquals("foo", helper.process(new GenericMessage<Object>(new Date())));
}
@Test
public void testInt3199GettersAmbiguity() throws Exception {
class Foo {
@SuppressWarnings("unused")
public String getFoo() {
return "foo";
}
@SuppressWarnings("unused")
public String getBar() {
return "foo";
}
}
try {
new MessagingMethodInvokerHelper(new Foo(), (String) null, false);
fail("IllegalArgumentException expected");
}
catch (Exception e) {
assertThat(e, Matchers.instanceOf(IllegalArgumentException.class));
assertEquals("Found more than one method match for empty parameter for 'payload'", e.getMessage());
}
}
@Test
public void testInt3199MessageMethods() throws Exception {
class Foo {
@SuppressWarnings("unused")
public String m1(Message<String> message) {
return message.getPayload();
}
@SuppressWarnings("unused")
public Integer m2(Message<Integer> message) {
return message.getPayload();
}
@SuppressWarnings("unused")
public Object m3(Message<?> message) {
return message.getPayload();
}
}
Foo targetObject = new Foo();
MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(targetObject, (String) null, false);
assertEquals("foo", helper.process(new GenericMessage<Object>("foo")));
assertEquals(1, helper.process(new GenericMessage<Object>(1)));
assertEquals(targetObject, helper.process(new GenericMessage<Object>(targetObject)));
}
@Test
public void testInt3199TypedMethods() throws Exception {
class Foo {
@SuppressWarnings("unused")
public String m1(String payload) {
return payload;
}
@SuppressWarnings("unused")
public Integer m2(Integer payload) {
return payload;
}
@SuppressWarnings("unused")
public Object m3(Object payload) {
return payload;
}
}
Foo targetObject = new Foo();
MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(targetObject, (String) null, false);
assertEquals("foo", helper.process(new GenericMessage<Object>("foo")));
assertEquals(1, helper.process(new GenericMessage<Object>(1)));
assertEquals(targetObject, helper.process(new GenericMessage<Object>(targetObject)));
}
@Test
public void testInt3199PrecedenceOfCandidates() throws Exception {
class Foo {
@SuppressWarnings("unused")
public Object m1(Message<String> message) {
fail("This method must not be invoked");
return message;
}
@SuppressWarnings("unused")
public Object m2(String payload) {
return payload;
}
@SuppressWarnings("unused")
public Object m3() {
return "FOO";
}
}
Foo targetObject = new Foo();
MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(targetObject, (String) null, false);
assertEquals("foo", helper.process(new GenericMessage<Object>("foo")));
assertEquals("FOO", helper.process(new GenericMessage<Object>(targetObject)));
}
@Test
public void testIneligible() {
IneligibleMethodBean bean = new IneligibleMethodBean();
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(bean, "foo");
processor.processMessage(MessageBuilder.withPayload("true").build());
assertNotNull(bean.lastArg);
assertEquals(String.class, bean.lastArg.getClass());
assertEquals("true", bean.lastArg);
}
@Test
public void testOptionalArgs() throws Exception {
class Foo {
private final Map<String, Object> arguments = new LinkedHashMap<String, Object>();
@SuppressWarnings("unused")
public void optionalHeaders(Optional<String> foo, @Header(value = "foo", required = false) String foo1,
@Header("foo") Optional<String> foo2) {
this.arguments.put("foo", (foo.isPresent() ? foo.get() : null));
this.arguments.put("foo1", foo1);
this.arguments.put("foo2", (foo2.isPresent() ? foo2.get() : null));
}
}
Foo targetObject = new Foo();
MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(targetObject, (String) null, false);
helper.process(new GenericMessage<>(Optional.empty()));
assertNull(targetObject.arguments.get("foo"));
assertNull(targetObject.arguments.get("foo1"));
assertNull(targetObject.arguments.get("foo2"));
helper.process(MessageBuilder.withPayload("foo").setHeader("foo", "FOO").build());
assertEquals("foo", targetObject.arguments.get("foo"));
assertEquals("FOO", targetObject.arguments.get("foo1"));
assertEquals("FOO", targetObject.arguments.get("foo2"));
}
@Test
public void testPrivateMethod() throws Exception {
class Foo {
@ServiceActivator
private String service(String payload) {
return payload.toUpperCase();
}
}
MessagingMethodInvokerHelper helper = new MessagingMethodInvokerHelper(new Foo(), ServiceActivator.class, false);
assertEquals("FOO", helper.process(new GenericMessage<>("foo")));
assertEquals("BAR", helper.process(new GenericMessage<>("bar")));
}
@Test
public void testPerformanceSpelVersusInvocable() throws Exception {
AnnotatedTestService service = new AnnotatedTestService();
Method method = service.getClass().getMethod("integerMethod", Integer.class);
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method);
processor.setUseSpelInvoker(true);
Message<Integer> message = MessageBuilder.withPayload(42).build();
StopWatch stopWatch = new StopWatch("SpEL vs Invocable Performance");
int count = 20_000;
stopWatch.start("SpEL");
for (int i = 0; i < count; i++) {
processor.processMessage(message);
}
stopWatch.stop();
processor = new MethodInvokingMessageProcessor(service, method);
stopWatch.start("Invocable");
for (int i = 0; i < count; i++) {
processor.processMessage(message);
}
stopWatch.stop();
DirectFieldAccessor compilerConfigAccessor = compileImmediate(processor);
processor = new MethodInvokingMessageProcessor(service, method);
processor.setUseSpelInvoker(true);
stopWatch.start("Compiled SpEL");
for (int i = 0; i < count; i++) {
processor.processMessage(message);
}
stopWatch.stop();
logger.warn(stopWatch.prettyPrint());
compilerConfigAccessor.setPropertyValue("compilerMode", SpelCompilerMode.OFF);
}
@Test
public void testNoSpElFallbackWhenUserException() {
class A {
@SuppressWarnings("unused")
public void myMethod(Object payload) {
throw new IllegalStateException(new IllegalArgumentException("argument type mismatch"));
}
}
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(new A(), "myMethod");
try {
processor.processMessage(new GenericMessage<>("foo"));
}
catch (Exception e) {
assertThat(e.getCause(), instanceOf(IllegalStateException.class));
assertThat(e.getCause().getCause(), instanceOf(IllegalArgumentException.class));
assertEquals(A.class.getName(), e.getCause().getStackTrace()[0].getClassName());
}
assertEquals(0,
TestUtils.getPropertyValue(processor, "delegate.handlerMethod.failedAttempts"));
}
@Test
public void testProxyInvocation() {
final AtomicReference<Object> result = new AtomicReference<>();
class MyHandler implements MessageHandler {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
result.set(message.getPayload());
}
}
MessageHandler service = new MyHandler();
final AtomicBoolean adviceCalled = new AtomicBoolean();
ProxyFactory proxyFactory = new ProxyFactory(service);
proxyFactory.addAdvice((MethodInterceptor) i -> {
adviceCalled.set(true);
return i.proceed();
});
service = (MessageHandler) proxyFactory.getProxy(getClass().getClassLoader());
MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, "handleMessage");
processor.processMessage(new GenericMessage<>("foo"));
assertEquals("foo", result.get());
assertTrue(adviceCalled.get());
}
@Test
public void testUseSpelInvoker() throws Exception {
UseSpelInvokerBean bean = new UseSpelInvokerBean();
MessagingMethodInvokerHelper<?> helper = new MessagingMethodInvokerHelper<>(bean,
UseSpelInvokerBean.class.getDeclaredMethod("foo", String.class), false);
Message<?> message = new GenericMessage<>("Test");
helper.process(message);
assertEquals(SpelCompilerMode.OFF,
TestUtils.getPropertyValue(helper, "handlerMethod.expression.configuration.compilerMode"));
helper = new MessagingMethodInvokerHelper<>(bean,
UseSpelInvokerBean.class.getDeclaredMethod("bar", String.class), false);
helper.process(message);
assertEquals(SpelCompilerMode.IMMEDIATE,
TestUtils.getPropertyValue(helper, "handlerMethod.expression.configuration.compilerMode"));
helper = new MessagingMethodInvokerHelper<>(bean,
UseSpelInvokerBean.class.getDeclaredMethod("baz", String.class), false);
helper.process(message);
assertEquals(SpelCompilerMode.MIXED,
TestUtils.getPropertyValue(helper, "handlerMethod.expression.configuration.compilerMode"));
helper = new MessagingMethodInvokerHelper<>(bean,
UseSpelInvokerBean.class.getDeclaredMethod("qux", String.class), false);
helper.process(message);
assertEquals(SpelCompilerMode.OFF,
TestUtils.getPropertyValue(helper, "handlerMethod.expression.configuration.compilerMode"));
helper = new MessagingMethodInvokerHelper<>(bean,
UseSpelInvokerBean.class.getDeclaredMethod("fiz", String.class), false);
try {
helper.process(message);
}
catch (IllegalArgumentException e) {
assertThat(e.getMessage(),
equalTo("No enum constant org.springframework.expression.spel.SpelCompilerMode.JUNK"));
}
helper = new MessagingMethodInvokerHelper<>(bean,
UseSpelInvokerBean.class.getDeclaredMethod("buz", String.class), false);
ConfigurableListableBeanFactory bf = mock(ConfigurableListableBeanFactory.class);
willAnswer(returnsFirstArg()).given(bf).resolveEmbeddedValue(anyString());
helper.setBeanFactory(bf);
try {
helper.process(message);
}
catch (IllegalArgumentException e) {
assertThat(e.getMessage(), equalTo(
"UseSpelInvoker.compilerMode: Object of class [java.lang.Object] "
+ "must be an instance of class java.lang.String"));
}
// Check other CTORs
helper = new MessagingMethodInvokerHelper<>(bean, "bar", false);
helper.process(message);
assertEquals(SpelCompilerMode.IMMEDIATE,
TestUtils.getPropertyValue(helper, "handlerMethod.expression.configuration.compilerMode"));
helper = new MessagingMethodInvokerHelper<>(bean, ServiceActivator.class, false);
helper.process(message);
assertEquals(SpelCompilerMode.MIXED,
TestUtils.getPropertyValue(helper, "handlerMethod.expression.configuration.compilerMode"));
}
private DirectFieldAccessor compileImmediate(MethodInvokingMessageProcessor processor) {
// Update the parser configuration compiler mode
SpelParserConfiguration config = TestUtils.getPropertyValue(processor,
"delegate.EXPRESSION_PARSER.configuration", SpelParserConfiguration.class);
DirectFieldAccessor accessor = new DirectFieldAccessor(config);
accessor.setPropertyValue("compilerMode", SpelCompilerMode.IMMEDIATE);
return accessor;
}
private static class ExceptionCauseMatcher extends TypeSafeMatcher<Exception> {
private Throwable cause;
private final Class<? extends Exception> type;
ExceptionCauseMatcher(Class<? extends Exception> type) {
this.type = type;
}
@Override
public boolean matchesSafely(Exception item) {
cause = item.getCause();
assertNotNull("There is no cause for " + item, cause);
return type.isAssignableFrom(cause.getClass());
}
@Override
public void describeTo(Description description) {
description.appendText("cause to be ").appendValue(type).appendText("but was ").appendValue(cause);
}
}
@SuppressWarnings("unused")
private static class TestErrorService {
TestErrorService() {
super();
}
public String error(String input) {
throw new UnsupportedOperationException("Expected test exception");
}
public String checked(String input) throws Exception {
throw new CheckedException("Expected test exception");
}
}
@SuppressWarnings("unused")
private static class TestDifferentErrorService {
TestDifferentErrorService() {
super();
}
public String checked(String input) throws Exception {
throw new CheckedException("Expected test exception");
}
}
@SuppressWarnings("serial")
private static final class CheckedException extends Exception {
CheckedException(String string) {
super(string);
}
}
@SuppressWarnings("unused")
private static class TestBean {
TestBean() {
super();
}
public String acceptPayloadAndReturnObject(String s) {
return s + "-1";
}
public Message<?> acceptPayloadAndReturnMessage(String s) {
return new GenericMessage<String>(s + "-2");
}
public String acceptMessageAndReturnObject(Message<?> m) {
return m.getPayload() + "-3";
}
public Message<?> acceptMessageAndReturnMessage(Message<?> m) {
return new GenericMessage<String>(m.getPayload() + "-4");
}
public Message<?> acceptMessageSubclassAndReturnMessage(GenericMessage<String> m) {
return new GenericMessage<String>(m.getPayload() + "-5");
}
public GenericMessage<String> acceptMessageSubclassAndReturnMessageSubclass(GenericMessage<String> m) {
return new GenericMessage<String>(m.getPayload() + "-6");
}
public String acceptPayloadAndHeaderAndReturnObject(String s, @Header("number") Integer n) {
return s + "-" + n;
}
public void testVoidReturningMethods(String s) {
// do nothing
}
public int testVoidReturningMethods(int i) {
return i;
}
}
public static class AnnotatedTestService {
AnnotatedTestService() {
super();
}
public String messageOnly(Message<?> message) {
return (String) message.getPayload();
}
public String messageAndHeader(Message<?> message, @Header("number") Integer num) {
return (String) message.getPayload() + "-" + num.toString();
}
public String twoHeaders(@Header String prop, @Header("number") Integer num) {
return prop + "-" + num.toString();
}
public Integer optionalHeader(@Header(required = false) Integer num) {
return num;
}
public Integer requiredHeader(@Header("num") Integer num) {
return num;
}
public String optionalAndRequiredHeader(@Header(required = false) String prop,
@Header("num") Integer num) {
return prop + num;
}
public String optionalAndRequiredDottedHeader(@Header(name = "dot1.foo", required = false) String prop,
@Header(name = "dot2.baz") Integer num) {
return prop + num;
}
public Properties propertiesMethod(Properties properties) {
return properties;
}
public Map mapMethod(Map map) {
return map;
}
public Integer integerMethod(Integer i) {
return i;
}
}
/**
* Method names create ambiguities, but the MethodResolver implementation should filter out based on the annotation
* or the 'requiresReply' flag.
*/
@SuppressWarnings("unused")
private static class AmbiguousMethodBean {
private volatile Object lastArg = null;
AmbiguousMethodBean() {
super();
}
public void foo(boolean b) {
this.lastArg = b;
}
public String foo(String s) {
this.lastArg = s;
return s;
}
public String foo(int i) {
this.lastArg = i;
return Integer.valueOf(i).toString();
}
}
/**
* Method names create ambiguities, but the MethodResolver implementation should filter out based on the annotation
* or the 'requiresReply' flag.
*/
@SuppressWarnings("unused")
private static class OverloadedMethodBean {
private volatile Object lastArg = null;
OverloadedMethodBean() {
super();
}
public void foo(boolean b) {
this.lastArg = b;
}
@ServiceActivator
public String foo(String s) {
this.lastArg = s;
return s;
}
}
private static class IneligibleMethodBean {
private volatile Object lastArg = null;
IneligibleMethodBean() {
super();
}
@SuppressWarnings("unused")
public void foo(String s) {
this.lastArg = s;
}
@SuppressWarnings("unused")
public void foo(String s, int i) {
throw new RuntimeException("expected ineligible");
}
}
private static class UseSpelInvokerBean {
UseSpelInvokerBean() {
super();
}
@UseSpelInvoker
public void foo(String foo) {
// empty
}
@UseSpelInvoker("IMMEDIATE")
public void bar(String bar) {
// empty
}
@ServiceActivator
@UseSpelInvoker("mixed")
public void baz(String baz) {
// empty
}
@UseSpelInvoker("OfF")
public void qux(String qux) {
// empty
}
@UseSpelInvoker("JUNK")
public void fiz(String fiz) {
// empty
}
@UseSpelInvoker("#{new Object()}")
public void buz(String buz) {
// empty
}
}
/*
* Public for SpEL access.
*/
public static class DotBean {
private final String foo = "bar";
private final Integer baz = 42;
public String getFoo() {
return this.foo;
}
public Integer getBaz() {
return this.baz;
}
}
}