/*
* Copyright 2002-2016 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.context.event;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.PayloadApplicationEvent;
import org.springframework.core.ResolvableType;
import org.springframework.core.ResolvableTypeProvider;
import org.springframework.core.annotation.Order;
import org.springframework.util.ReflectionUtils;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* @author Stephane Nicoll
*/
public class ApplicationListenerMethodAdapterTests extends AbstractApplicationEventListenerTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final SampleEvents sampleEvents = spy(new SampleEvents());
private final ApplicationContext context = mock(ApplicationContext.class);
@Test
public void rawListener() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleRaw", ApplicationEvent.class);
supportsEventType(true, method, getGenericApplicationEventType("applicationEvent"));
}
@Test
public void rawListenerWithGenericEvent() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleRaw", ApplicationEvent.class);
supportsEventType(true, method, getGenericApplicationEventType("stringEvent"));
}
@Test
public void genericListener() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "handleGenericString", GenericTestEvent.class);
supportsEventType(true, method, getGenericApplicationEventType("stringEvent"));
}
@Test
public void genericListenerWrongParameterizedType() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "handleGenericString", GenericTestEvent.class);
supportsEventType(false, method, getGenericApplicationEventType("longEvent"));
}
@Test
public void listenerWithPayloadAndGenericInformation() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleString", String.class);
supportsEventType(true, method, createGenericEventType(String.class));
}
@Test
public void listenerWithInvalidPayloadAndGenericInformation() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleString", String.class);
supportsEventType(false, method, createGenericEventType(Integer.class));
}
@Test
public void listenerWithPayloadTypeErasure() { // Always accept such event when the type is unknown
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleString", String.class);
supportsEventType(true, method, ResolvableType.forClass(PayloadApplicationEvent.class));
}
@Test
public void listenerWithSubTypeSeveralGenerics() {
Method method = ReflectionUtils.findMethod(SampleEvents.class,
"handleString", String.class);
supportsEventType(true, method, ResolvableType.forClass(PayloadTestEvent.class));
}
@Test
public void listenerWithSubTypeSeveralGenericsResolved() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleString", String.class);
supportsEventType(true, method, ResolvableType.forClass(PayloadStringTestEvent.class));
}
@Test
public void listenerWithAnnotationValue() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleStringAnnotationValue");
supportsEventType(true, method, createGenericEventType(String.class));
}
@Test
public void listenerWithAnnotationClasses() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleStringAnnotationClasses");
supportsEventType(true, method, createGenericEventType(String.class));
}
@Test
public void listenerWithAnnotationValueAndParameter() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "handleStringAnnotationValueAndParameter", String.class);
supportsEventType(true, method, createGenericEventType(String.class));
}
@Test
public void listenerWithSeveralTypes() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleStringOrInteger");
supportsEventType(true, method, createGenericEventType(String.class));
supportsEventType(true, method, createGenericEventType(Integer.class));
supportsEventType(false, method, createGenericEventType(Double.class));
}
@Test
public void listenerWithTooManyParameters() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "tooManyParameters", String.class, String.class);
this.thrown.expect(IllegalStateException.class);
createTestInstance(method);
}
@Test
public void listenerWithNoParameter() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "noParameter");
this.thrown.expect(IllegalStateException.class);
createTestInstance(method);
}
@Test
public void listenerWithMoreThanOneParameter() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "moreThanOneParameter", String.class, Integer.class);
this.thrown.expect(IllegalStateException.class);
createTestInstance(method);
}
@Test
public void defaultOrder() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "handleGenericString", GenericTestEvent.class);
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
assertEquals(0, adapter.getOrder());
}
@Test
public void specifiedOrder() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "handleRaw", ApplicationEvent.class);
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
assertEquals(42, adapter.getOrder());
}
@Test
public void invokeListener() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "handleGenericString", GenericTestEvent.class);
GenericTestEvent<String> event = createGenericTestEvent("test");
invokeListener(method, event);
verify(this.sampleEvents, times(1)).handleGenericString(event);
}
@Test
public void invokeListenerWithGenericEvent() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "handleGenericString", GenericTestEvent.class);
GenericTestEvent<String> event = new SmartGenericTestEvent<>(this, "test");
invokeListener(method, event);
verify(this.sampleEvents, times(1)).handleGenericString(event);
}
@Test
public void invokeListenerWithGenericPayload() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "handleGenericStringPayload", EntityWrapper.class);
EntityWrapper<String> payload = new EntityWrapper<>("test");
invokeListener(method, new PayloadApplicationEvent<>(this, payload));
verify(this.sampleEvents, times(1)).handleGenericStringPayload(payload);
}
@Test
public void invokeListenerWithWrongGenericPayload() {
Method method = ReflectionUtils.findMethod
(SampleEvents.class, "handleGenericStringPayload", EntityWrapper.class);
EntityWrapper<Integer> payload = new EntityWrapper<>(123);
invokeListener(method, new PayloadApplicationEvent<>(this, payload));
verify(this.sampleEvents, times(0)).handleGenericStringPayload(any());
}
@Test
public void invokeListenerWithAnyGenericPayload() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "handleGenericAnyPayload", EntityWrapper.class);
EntityWrapper<String> payload = new EntityWrapper<>("test");
invokeListener(method, new PayloadApplicationEvent<>(this, payload));
verify(this.sampleEvents, times(1)).handleGenericAnyPayload(payload);
}
@Test
public void invokeListenerRuntimeException() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "generateRuntimeException", GenericTestEvent.class);
GenericTestEvent<String> event = createGenericTestEvent("fail");
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Test exception");
this.thrown.expectCause(is((Throwable) isNull()));
invokeListener(method, event);
}
@Test
public void invokeListenerCheckedException() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "generateCheckedException", GenericTestEvent.class);
GenericTestEvent<String> event = createGenericTestEvent("fail");
this.thrown.expect(UndeclaredThrowableException.class);
this.thrown.expectCause(is(instanceOf(IOException.class)));
invokeListener(method, event);
}
@Test
public void invokeListenerInvalidProxy() {
Object target = new InvalidProxyTestBean();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addInterface(SimpleService.class);
Object bean = proxyFactory.getProxy(getClass().getClassLoader());
Method method = ReflectionUtils.findMethod(
InvalidProxyTestBean.class, "handleIt2", ApplicationEvent.class);
StaticApplicationListenerMethodAdapter listener =
new StaticApplicationListenerMethodAdapter(method, bean);
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("handleIt2");
listener.onApplicationEvent(createGenericTestEvent("test"));
}
@Test
public void invokeListenerWithPayload() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleString", String.class);
PayloadApplicationEvent<String> event = new PayloadApplicationEvent<>(this, "test");
invokeListener(method, event);
verify(this.sampleEvents, times(1)).handleString("test");
}
@Test
public void invokeListenerWithPayloadWrongType() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleString", String.class);
PayloadApplicationEvent<Long> event = new PayloadApplicationEvent<>(this, 123L);
invokeListener(method, event);
verify(this.sampleEvents, never()).handleString(anyString());
}
@Test
public void invokeListenerWithAnnotationValue() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleStringAnnotationClasses");
PayloadApplicationEvent<String> event = new PayloadApplicationEvent<>(this, "test");
invokeListener(method, event);
verify(this.sampleEvents, times(1)).handleStringAnnotationClasses();
}
@Test
public void invokeListenerWithAnnotationValueAndParameter() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "handleStringAnnotationValueAndParameter", String.class);
PayloadApplicationEvent<String> event = new PayloadApplicationEvent<>(this, "test");
invokeListener(method, event);
verify(this.sampleEvents, times(1)).handleStringAnnotationValueAndParameter("test");
}
@Test
public void invokeListenerWithSeveralTypes() {
Method method = ReflectionUtils.findMethod(SampleEvents.class, "handleStringOrInteger");
PayloadApplicationEvent<String> event = new PayloadApplicationEvent<>(this, "test");
invokeListener(method, event);
verify(this.sampleEvents, times(1)).handleStringOrInteger();
PayloadApplicationEvent<Integer> event2 = new PayloadApplicationEvent<>(this, 123);
invokeListener(method, event2);
verify(this.sampleEvents, times(2)).handleStringOrInteger();
PayloadApplicationEvent<Double> event3 = new PayloadApplicationEvent<>(this, 23.2);
invokeListener(method, event3);
verify(this.sampleEvents, times(2)).handleStringOrInteger();
}
@Test
public void beanInstanceRetrievedAtEveryInvocation() {
Method method = ReflectionUtils.findMethod(
SampleEvents.class, "handleGenericString", GenericTestEvent.class);
when(this.context.getBean("testBean")).thenReturn(this.sampleEvents);
ApplicationListenerMethodAdapter listener = new ApplicationListenerMethodAdapter(
"testBean", GenericTestEvent.class, method);
listener.init(this.context, new EventExpressionEvaluator());
GenericTestEvent<String> event = createGenericTestEvent("test");
listener.onApplicationEvent(event);
verify(this.sampleEvents, times(1)).handleGenericString(event);
verify(this.context, times(1)).getBean("testBean");
listener.onApplicationEvent(event);
verify(this.sampleEvents, times(2)).handleGenericString(event);
verify(this.context, times(2)).getBean("testBean");
}
private void supportsEventType(boolean match, Method method, ResolvableType eventType) {
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
assertEquals("Wrong match for event '" + eventType + "' on " + method,
match, adapter.supportsEventType(eventType));
}
private void invokeListener(Method method, ApplicationEvent event) {
ApplicationListenerMethodAdapter adapter = createTestInstance(method);
adapter.onApplicationEvent(event);
}
private ApplicationListenerMethodAdapter createTestInstance(Method method) {
return new StaticApplicationListenerMethodAdapter(method, this.sampleEvents);
}
private ResolvableType createGenericEventType(Class<?> payloadType) {
return ResolvableType.forClassWithGenerics(PayloadApplicationEvent.class, payloadType);
}
private static class StaticApplicationListenerMethodAdapter extends ApplicationListenerMethodAdapter {
private final Object targetBean;
public StaticApplicationListenerMethodAdapter(Method method, Object targetBean) {
super("unused", targetBean.getClass(), method);
this.targetBean = targetBean;
}
@Override
public Object getTargetBean() {
return this.targetBean;
}
}
private static class SampleEvents {
@EventListener
@Order(42)
public void handleRaw(ApplicationEvent event) {
}
@EventListener
public void handleGenericString(GenericTestEvent<String> event) {
}
@EventListener
public void handleString(String payload) {
}
@EventListener(String.class)
public void handleStringAnnotationValue() {
}
@EventListener(classes = String.class)
public void handleStringAnnotationClasses() {
}
@EventListener(String.class)
public void handleStringAnnotationValueAndParameter(String payload) {
}
@EventListener({String.class, Integer.class})
public void handleStringOrInteger() {
}
@EventListener({String.class, Integer.class})
public void handleStringOrIntegerWithParam(String invalid) {
}
@EventListener
public void handleGenericStringPayload(EntityWrapper<String> event) {
}
@EventListener
public void handleGenericAnyPayload(EntityWrapper<?> event) {
}
@EventListener
public void tooManyParameters(String event, String whatIsThis) {
}
@EventListener
public void noParameter() {
}
@EventListener
public void moreThanOneParameter(String foo, Integer bar) {
}
@EventListener
public void generateRuntimeException(GenericTestEvent<String> event) {
if ("fail".equals(event.getPayload())) {
throw new IllegalStateException("Test exception");
}
}
@EventListener
public void generateCheckedException(GenericTestEvent<String> event) throws IOException {
if ("fail".equals(event.getPayload())) {
throw new IOException("Test exception");
}
}
}
interface SimpleService {
void handleIt(ApplicationEvent event);
}
private static class EntityWrapper<T> implements ResolvableTypeProvider {
private final T entity;
public EntityWrapper(T entity) {
this.entity = entity;
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(), this.entity.getClass());
}
}
static class InvalidProxyTestBean implements SimpleService {
@Override
public void handleIt(ApplicationEvent event) {
}
@EventListener
public void handleIt2(ApplicationEvent event) {
}
}
@SuppressWarnings({"unused", "serial"})
static class PayloadTestEvent<V, T> extends PayloadApplicationEvent<T> {
private final V something;
public PayloadTestEvent(Object source, T payload, V something) {
super(source, payload);
this.something = something;
}
}
@SuppressWarnings({ "serial" })
static class PayloadStringTestEvent extends PayloadTestEvent<Long, String> {
public PayloadStringTestEvent(Object source, String payload, Long something) {
super(source, payload, something);
}
}
}