001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.context.event;
018
019import java.lang.reflect.InvocationTargetException;
020import java.lang.reflect.Method;
021import java.lang.reflect.Proxy;
022import java.lang.reflect.UndeclaredThrowableException;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.List;
027import java.util.concurrent.CompletionStage;
028
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.reactivestreams.Subscriber;
032import org.reactivestreams.Subscription;
033
034import org.springframework.aop.support.AopUtils;
035import org.springframework.context.ApplicationContext;
036import org.springframework.context.ApplicationEvent;
037import org.springframework.context.PayloadApplicationEvent;
038import org.springframework.context.expression.AnnotatedElementKey;
039import org.springframework.core.BridgeMethodResolver;
040import org.springframework.core.ReactiveAdapter;
041import org.springframework.core.ReactiveAdapterRegistry;
042import org.springframework.core.ResolvableType;
043import org.springframework.core.annotation.AnnotatedElementUtils;
044import org.springframework.core.annotation.Order;
045import org.springframework.lang.Nullable;
046import org.springframework.util.Assert;
047import org.springframework.util.ClassUtils;
048import org.springframework.util.ObjectUtils;
049import org.springframework.util.ReflectionUtils;
050import org.springframework.util.StringUtils;
051import org.springframework.util.concurrent.ListenableFuture;
052
053/**
054 * {@link GenericApplicationListener} adapter that delegates the processing of
055 * an event to an {@link EventListener} annotated method.
056 *
057 * <p>Delegates to {@link #processEvent(ApplicationEvent)} to give subclasses
058 * a chance to deviate from the default. Unwraps the content of a
059 * {@link PayloadApplicationEvent} if necessary to allow a method declaration
060 * to define any arbitrary event type. If a condition is defined, it is
061 * evaluated prior to invoking the underlying method.
062 *
063 * @author Stephane Nicoll
064 * @author Juergen Hoeller
065 * @author Sam Brannen
066 * @since 4.2
067 */
068public class ApplicationListenerMethodAdapter implements GenericApplicationListener {
069
070        private static final boolean reactiveStreamsPresent = ClassUtils.isPresent(
071                        "org.reactivestreams.Publisher", ApplicationListenerMethodAdapter.class.getClassLoader());
072
073
074        protected final Log logger = LogFactory.getLog(getClass());
075
076        private final String beanName;
077
078        private final Method method;
079
080        private final Method targetMethod;
081
082        private final AnnotatedElementKey methodKey;
083
084        private final List<ResolvableType> declaredEventTypes;
085
086        @Nullable
087        private final String condition;
088
089        private final int order;
090
091        @Nullable
092        private ApplicationContext applicationContext;
093
094        @Nullable
095        private EventExpressionEvaluator evaluator;
096
097
098        public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) {
099                this.beanName = beanName;
100                this.method = BridgeMethodResolver.findBridgedMethod(method);
101                this.targetMethod = (!Proxy.isProxyClass(targetClass) ?
102                                AopUtils.getMostSpecificMethod(method, targetClass) : this.method);
103                this.methodKey = new AnnotatedElementKey(this.targetMethod, targetClass);
104
105                EventListener ann = AnnotatedElementUtils.findMergedAnnotation(this.targetMethod, EventListener.class);
106                this.declaredEventTypes = resolveDeclaredEventTypes(method, ann);
107                this.condition = (ann != null ? ann.condition() : null);
108                this.order = resolveOrder(this.targetMethod);
109        }
110
111        private static List<ResolvableType> resolveDeclaredEventTypes(Method method, @Nullable EventListener ann) {
112                int count = method.getParameterCount();
113                if (count > 1) {
114                        throw new IllegalStateException(
115                                        "Maximum one parameter is allowed for event listener method: " + method);
116                }
117
118                if (ann != null) {
119                        Class<?>[] classes = ann.classes();
120                        if (classes.length > 0) {
121                                List<ResolvableType> types = new ArrayList<>(classes.length);
122                                for (Class<?> eventType : classes) {
123                                        types.add(ResolvableType.forClass(eventType));
124                                }
125                                return types;
126                        }
127                }
128
129                if (count == 0) {
130                        throw new IllegalStateException(
131                                        "Event parameter is mandatory for event listener method: " + method);
132                }
133                return Collections.singletonList(ResolvableType.forMethodParameter(method, 0));
134        }
135
136        private static int resolveOrder(Method method) {
137                Order ann = AnnotatedElementUtils.findMergedAnnotation(method, Order.class);
138                return (ann != null ? ann.value() : 0);
139        }
140
141
142        /**
143         * Initialize this instance.
144         */
145        void init(ApplicationContext applicationContext, EventExpressionEvaluator evaluator) {
146                this.applicationContext = applicationContext;
147                this.evaluator = evaluator;
148        }
149
150
151        @Override
152        public void onApplicationEvent(ApplicationEvent event) {
153                processEvent(event);
154        }
155
156        @Override
157        public boolean supportsEventType(ResolvableType eventType) {
158                for (ResolvableType declaredEventType : this.declaredEventTypes) {
159                        if (declaredEventType.isAssignableFrom(eventType)) {
160                                return true;
161                        }
162                        if (PayloadApplicationEvent.class.isAssignableFrom(eventType.toClass())) {
163                                ResolvableType payloadType = eventType.as(PayloadApplicationEvent.class).getGeneric();
164                                if (declaredEventType.isAssignableFrom(payloadType)) {
165                                        return true;
166                                }
167                        }
168                }
169                return eventType.hasUnresolvableGenerics();
170        }
171
172        @Override
173        public boolean supportsSourceType(@Nullable Class<?> sourceType) {
174                return true;
175        }
176
177        @Override
178        public int getOrder() {
179                return this.order;
180        }
181
182
183        /**
184         * Process the specified {@link ApplicationEvent}, checking if the condition
185         * matches and handling a non-null result, if any.
186         */
187        public void processEvent(ApplicationEvent event) {
188                Object[] args = resolveArguments(event);
189                if (shouldHandle(event, args)) {
190                        Object result = doInvoke(args);
191                        if (result != null) {
192                                handleResult(result);
193                        }
194                        else {
195                                logger.trace("No result object given - no result to handle");
196                        }
197                }
198        }
199
200        /**
201         * Resolve the method arguments to use for the specified {@link ApplicationEvent}.
202         * <p>These arguments will be used to invoke the method handled by this instance.
203         * Can return {@code null} to indicate that no suitable arguments could be resolved
204         * and therefore the method should not be invoked at all for the specified event.
205         */
206        @Nullable
207        protected Object[] resolveArguments(ApplicationEvent event) {
208                ResolvableType declaredEventType = getResolvableType(event);
209                if (declaredEventType == null) {
210                        return null;
211                }
212                if (this.method.getParameterCount() == 0) {
213                        return new Object[0];
214                }
215                Class<?> declaredEventClass = declaredEventType.toClass();
216                if (!ApplicationEvent.class.isAssignableFrom(declaredEventClass) &&
217                                event instanceof PayloadApplicationEvent) {
218                        Object payload = ((PayloadApplicationEvent<?>) event).getPayload();
219                        if (declaredEventClass.isInstance(payload)) {
220                                return new Object[] {payload};
221                        }
222                }
223                return new Object[] {event};
224        }
225
226        protected void handleResult(Object result) {
227                if (reactiveStreamsPresent && new ReactiveResultHandler().subscribeToPublisher(result)) {
228                        if (logger.isTraceEnabled()) {
229                                logger.trace("Adapted to reactive result: " + result);
230                        }
231                }
232                else if (result instanceof CompletionStage) {
233                        ((CompletionStage<?>) result).whenComplete((event, ex) -> {
234                                if (ex != null) {
235                                        handleAsyncError(ex);
236                                }
237                                else if (event != null) {
238                                        publishEvent(event);
239                                }
240                        });
241                }
242                else if (result instanceof ListenableFuture) {
243                        ((ListenableFuture<?>) result).addCallback(this::publishEvents, this::handleAsyncError);
244                }
245                else {
246                        publishEvents(result);
247                }
248        }
249
250        private void publishEvents(Object result) {
251                if (result.getClass().isArray()) {
252                        Object[] events = ObjectUtils.toObjectArray(result);
253                        for (Object event : events) {
254                                publishEvent(event);
255                        }
256                }
257                else if (result instanceof Collection<?>) {
258                        Collection<?> events = (Collection<?>) result;
259                        for (Object event : events) {
260                                publishEvent(event);
261                        }
262                }
263                else {
264                        publishEvent(result);
265                }
266        }
267
268        private void publishEvent(@Nullable Object event) {
269                if (event != null) {
270                        Assert.notNull(this.applicationContext, "ApplicationContext must not be null");
271                        this.applicationContext.publishEvent(event);
272                }
273        }
274
275        protected void handleAsyncError(Throwable t) {
276                logger.error("Unexpected error occurred in asynchronous listener", t);
277        }
278
279        private boolean shouldHandle(ApplicationEvent event, @Nullable Object[] args) {
280                if (args == null) {
281                        return false;
282                }
283                String condition = getCondition();
284                if (StringUtils.hasText(condition)) {
285                        Assert.notNull(this.evaluator, "EventExpressionEvaluator must not be null");
286                        return this.evaluator.condition(
287                                        condition, event, this.targetMethod, this.methodKey, args, this.applicationContext);
288                }
289                return true;
290        }
291
292        /**
293         * Invoke the event listener method with the given argument values.
294         */
295        @Nullable
296        protected Object doInvoke(Object... args) {
297                Object bean = getTargetBean();
298                // Detect package-protected NullBean instance through equals(null) check
299                if (bean.equals(null)) {
300                        return null;
301                }
302
303                ReflectionUtils.makeAccessible(this.method);
304                try {
305                        return this.method.invoke(bean, args);
306                }
307                catch (IllegalArgumentException ex) {
308                        assertTargetBean(this.method, bean, args);
309                        throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex);
310                }
311                catch (IllegalAccessException ex) {
312                        throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex);
313                }
314                catch (InvocationTargetException ex) {
315                        // Throw underlying exception
316                        Throwable targetException = ex.getTargetException();
317                        if (targetException instanceof RuntimeException) {
318                                throw (RuntimeException) targetException;
319                        }
320                        else {
321                                String msg = getInvocationErrorMessage(bean, "Failed to invoke event listener method", args);
322                                throw new UndeclaredThrowableException(targetException, msg);
323                        }
324                }
325        }
326
327        /**
328         * Return the target bean instance to use.
329         */
330        protected Object getTargetBean() {
331                Assert.notNull(this.applicationContext, "ApplicationContext must no be null");
332                return this.applicationContext.getBean(this.beanName);
333        }
334
335        /**
336         * Return the condition to use.
337         * <p>Matches the {@code condition} attribute of the {@link EventListener}
338         * annotation or any matching attribute on a composed annotation that
339         * is meta-annotated with {@code @EventListener}.
340         */
341        @Nullable
342        protected String getCondition() {
343                return this.condition;
344        }
345
346        /**
347         * Add additional details such as the bean type and method signature to
348         * the given error message.
349         * @param message error message to append the HandlerMethod details to
350         */
351        protected String getDetailedErrorMessage(Object bean, String message) {
352                StringBuilder sb = new StringBuilder(message).append("\n");
353                sb.append("HandlerMethod details: \n");
354                sb.append("Bean [").append(bean.getClass().getName()).append("]\n");
355                sb.append("Method [").append(this.method.toGenericString()).append("]\n");
356                return sb.toString();
357        }
358
359        /**
360         * Assert that the target bean class is an instance of the class where the given
361         * method is declared. In some cases the actual bean instance at event-
362         * processing time may be a JDK dynamic proxy (lazy initialization, prototype
363         * beans, and others). Event listener beans that require proxying should prefer
364         * class-based proxy mechanisms.
365         */
366        private void assertTargetBean(Method method, Object targetBean, Object[] args) {
367                Class<?> methodDeclaringClass = method.getDeclaringClass();
368                Class<?> targetBeanClass = targetBean.getClass();
369                if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) {
370                        String msg = "The event listener method class '" + methodDeclaringClass.getName() +
371                                        "' is not an instance of the actual bean class '" +
372                                        targetBeanClass.getName() + "'. If the bean requires proxying " +
373                                        "(e.g. due to @Transactional), please use class-based proxying.";
374                        throw new IllegalStateException(getInvocationErrorMessage(targetBean, msg, args));
375                }
376        }
377
378        private String getInvocationErrorMessage(Object bean, String message, Object[] resolvedArgs) {
379                StringBuilder sb = new StringBuilder(getDetailedErrorMessage(bean, message));
380                sb.append("Resolved arguments: \n");
381                for (int i = 0; i < resolvedArgs.length; i++) {
382                        sb.append("[").append(i).append("] ");
383                        if (resolvedArgs[i] == null) {
384                                sb.append("[null] \n");
385                        }
386                        else {
387                                sb.append("[type=").append(resolvedArgs[i].getClass().getName()).append("] ");
388                                sb.append("[value=").append(resolvedArgs[i]).append("]\n");
389                        }
390                }
391                return sb.toString();
392        }
393
394        @Nullable
395        private ResolvableType getResolvableType(ApplicationEvent event) {
396                ResolvableType payloadType = null;
397                if (event instanceof PayloadApplicationEvent) {
398                        PayloadApplicationEvent<?> payloadEvent = (PayloadApplicationEvent<?>) event;
399                        ResolvableType eventType = payloadEvent.getResolvableType();
400                        if (eventType != null) {
401                                payloadType = eventType.as(PayloadApplicationEvent.class).getGeneric();
402                        }
403                }
404                for (ResolvableType declaredEventType : this.declaredEventTypes) {
405                        Class<?> eventClass = declaredEventType.toClass();
406                        if (!ApplicationEvent.class.isAssignableFrom(eventClass) &&
407                                        payloadType != null && declaredEventType.isAssignableFrom(payloadType)) {
408                                return declaredEventType;
409                        }
410                        if (eventClass.isInstance(event)) {
411                                return declaredEventType;
412                        }
413                }
414                return null;
415        }
416
417
418        @Override
419        public String toString() {
420                return this.method.toGenericString();
421        }
422
423
424        private class ReactiveResultHandler {
425
426                public boolean subscribeToPublisher(Object result) {
427                        ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(result.getClass());
428                        if (adapter != null) {
429                                adapter.toPublisher(result).subscribe(new EventPublicationSubscriber());
430                                return true;
431                        }
432                        return false;
433                }
434        }
435
436
437        private class EventPublicationSubscriber implements Subscriber<Object> {
438
439                @Override
440                public void onSubscribe(Subscription s) {
441                        s.request(Integer.MAX_VALUE);
442                }
443
444                @Override
445                public void onNext(Object o) {
446                        publishEvents(o);
447                }
448
449                @Override
450                public void onError(Throwable t) {
451                        handleAsyncError(t);
452                }
453
454                @Override
455                public void onComplete() {
456                }
457        }
458
459}