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.UndeclaredThrowableException;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Collections;
025import java.util.List;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029
030import org.springframework.context.ApplicationContext;
031import org.springframework.context.ApplicationEvent;
032import org.springframework.context.PayloadApplicationEvent;
033import org.springframework.context.expression.AnnotatedElementKey;
034import org.springframework.core.BridgeMethodResolver;
035import org.springframework.core.ResolvableType;
036import org.springframework.core.annotation.AnnotatedElementUtils;
037import org.springframework.core.annotation.Order;
038import org.springframework.expression.EvaluationContext;
039import org.springframework.util.Assert;
040import org.springframework.util.ClassUtils;
041import org.springframework.util.ObjectUtils;
042import org.springframework.util.ReflectionUtils;
043import org.springframework.util.StringUtils;
044
045/**
046 * {@link GenericApplicationListener} adapter that delegates the processing of
047 * an event to an {@link EventListener} annotated method.
048 *
049 * <p>Delegates to {@link #processEvent(ApplicationEvent)} to give sub-classes
050 * a chance to deviate from the default. Unwraps the content of a
051 * {@link PayloadApplicationEvent} if necessary to allow method declaration
052 * to define any arbitrary event type. If a condition is defined, it is
053 * evaluated prior to invoking the underlying method.
054 *
055 * @author Stephane Nicoll
056 * @author Juergen Hoeller
057 * @author Sam Brannen
058 * @since 4.2
059 */
060public class ApplicationListenerMethodAdapter implements GenericApplicationListener {
061
062        protected final Log logger = LogFactory.getLog(getClass());
063
064        private final String beanName;
065
066        private final Method method;
067
068        private final Class<?> targetClass;
069
070        private final Method bridgedMethod;
071
072        private final List<ResolvableType> declaredEventTypes;
073
074        private final String condition;
075
076        private final int order;
077
078        private final AnnotatedElementKey methodKey;
079
080        private ApplicationContext applicationContext;
081
082        private EventExpressionEvaluator evaluator;
083
084
085        public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) {
086                this.beanName = beanName;
087                this.method = method;
088                this.targetClass = targetClass;
089                this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
090
091                Method targetMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
092                EventListener ann = AnnotatedElementUtils.findMergedAnnotation(targetMethod, EventListener.class);
093
094                this.declaredEventTypes = resolveDeclaredEventTypes(method, ann);
095                this.condition = (ann != null ? ann.condition() : null);
096                this.order = resolveOrder(targetMethod);
097
098                this.methodKey = new AnnotatedElementKey(method, targetClass);
099        }
100
101
102        private List<ResolvableType> resolveDeclaredEventTypes(Method method, EventListener ann) {
103                int count = method.getParameterTypes().length;
104                if (count > 1) {
105                        throw new IllegalStateException(
106                                        "Maximum one parameter is allowed for event listener method: " + method);
107                }
108                if (ann != null && ann.classes().length > 0) {
109                        List<ResolvableType> types = new ArrayList<ResolvableType>(ann.classes().length);
110                        for (Class<?> eventType : ann.classes()) {
111                                types.add(ResolvableType.forClass(eventType));
112                        }
113                        return types;
114                }
115                else {
116                        if (count == 0) {
117                                throw new IllegalStateException(
118                                                "Event parameter is mandatory for event listener method: " + method);
119                        }
120                        return Collections.singletonList(ResolvableType.forMethodParameter(method, 0));
121                }
122        }
123
124        private int resolveOrder(Method method) {
125                Order ann = AnnotatedElementUtils.findMergedAnnotation(method, Order.class);
126                return (ann != null ? ann.value() : 0);
127        }
128
129        /**
130         * Initialize this instance.
131         */
132        void init(ApplicationContext applicationContext, EventExpressionEvaluator evaluator) {
133                this.applicationContext = applicationContext;
134                this.evaluator = evaluator;
135        }
136
137
138        @Override
139        public void onApplicationEvent(ApplicationEvent event) {
140                processEvent(event);
141        }
142
143        @Override
144        public boolean supportsEventType(ResolvableType eventType) {
145                for (ResolvableType declaredEventType : this.declaredEventTypes) {
146                        if (declaredEventType.isAssignableFrom(eventType)) {
147                                return true;
148                        }
149                        else if (PayloadApplicationEvent.class.isAssignableFrom(eventType.getRawClass())) {
150                                ResolvableType payloadType = eventType.as(PayloadApplicationEvent.class).getGeneric();
151                                if (declaredEventType.isAssignableFrom(payloadType)) {
152                                        return true;
153                                }
154                        }
155                }
156                return eventType.hasUnresolvableGenerics();
157        }
158
159        @Override
160        public boolean supportsSourceType(Class<?> sourceType) {
161                return true;
162        }
163
164        @Override
165        public int getOrder() {
166                return this.order;
167        }
168
169
170        /**
171         * Process the specified {@link ApplicationEvent}, checking if the condition
172         * match and handling non-null result, if any.
173         */
174        public void processEvent(ApplicationEvent event) {
175                Object[] args = resolveArguments(event);
176                if (shouldHandle(event, args)) {
177                        Object result = doInvoke(args);
178                        if (result != null) {
179                                handleResult(result);
180                        }
181                        else {
182                                logger.trace("No result object given - no result to handle");
183                        }
184                }
185        }
186
187        /**
188         * Resolve the method arguments to use for the specified {@link ApplicationEvent}.
189         * <p>These arguments will be used to invoke the method handled by this instance.
190         * Can return {@code null} to indicate that no suitable arguments could be resolved
191         * and therefore the method should not be invoked at all for the specified event.
192         */
193        protected Object[] resolveArguments(ApplicationEvent event) {
194                ResolvableType declaredEventType = getResolvableType(event);
195                if (declaredEventType == null) {
196                        return null;
197                }
198                if (this.method.getParameterTypes().length == 0) {
199                        return new Object[0];
200                }
201                Class<?> eventClass = declaredEventType.getRawClass();
202                if ((eventClass == null || !ApplicationEvent.class.isAssignableFrom(eventClass)) &&
203                                event instanceof PayloadApplicationEvent) {
204                        Object payload = ((PayloadApplicationEvent) event).getPayload();
205                        if (eventClass == null || eventClass.isInstance(payload)) {
206                                return new Object[] {payload};
207                        }
208                }
209                return new Object[] {event};
210        }
211
212        protected void handleResult(Object result) {
213                if (result.getClass().isArray()) {
214                        Object[] events = ObjectUtils.toObjectArray(result);
215                        for (Object event : events) {
216                                publishEvent(event);
217                        }
218                }
219                else if (result instanceof Collection<?>) {
220                        Collection<?> events = (Collection<?>) result;
221                        for (Object event : events) {
222                                publishEvent(event);
223                        }
224                }
225                else {
226                        publishEvent(result);
227                }
228        }
229
230        private void publishEvent(Object event) {
231                if (event != null) {
232                        Assert.notNull(this.applicationContext, "ApplicationContext must not be null");
233                        this.applicationContext.publishEvent(event);
234                }
235        }
236
237        private boolean shouldHandle(ApplicationEvent event, Object[] args) {
238                if (args == null) {
239                        return false;
240                }
241                String condition = getCondition();
242                if (StringUtils.hasText(condition)) {
243                        Assert.notNull(this.evaluator, "EventExpressionEvaluator must no be null");
244                        EvaluationContext evaluationContext = this.evaluator.createEvaluationContext(
245                                        event, this.targetClass, this.method, args, this.applicationContext);
246                        return this.evaluator.condition(condition, this.methodKey, evaluationContext);
247                }
248                return true;
249        }
250
251        /**
252         * Invoke the event listener method with the given argument values.
253         */
254        protected Object doInvoke(Object... args) {
255                Object bean = getTargetBean();
256                ReflectionUtils.makeAccessible(this.bridgedMethod);
257                try {
258                        return this.bridgedMethod.invoke(bean, args);
259                }
260                catch (IllegalArgumentException ex) {
261                        assertTargetBean(this.bridgedMethod, bean, args);
262                        throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex);
263                }
264                catch (IllegalAccessException ex) {
265                        throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex);
266                }
267                catch (InvocationTargetException ex) {
268                        // Throw underlying exception
269                        Throwable targetException = ex.getTargetException();
270                        if (targetException instanceof RuntimeException) {
271                                throw (RuntimeException) targetException;
272                        }
273                        else {
274                                String msg = getInvocationErrorMessage(bean, "Failed to invoke event listener method", args);
275                                throw new UndeclaredThrowableException(targetException, msg);
276                        }
277                }
278        }
279
280        /**
281         * Return the target bean instance to use.
282         */
283        protected Object getTargetBean() {
284                Assert.notNull(this.applicationContext, "ApplicationContext must no be null");
285                return this.applicationContext.getBean(this.beanName);
286        }
287
288        /**
289         * Return the condition to use.
290         * <p>Matches the {@code condition} attribute of the {@link EventListener}
291         * annotation or any matching attribute on a composed annotation that
292         * is meta-annotated with {@code @EventListener}.
293         */
294        protected String getCondition() {
295                return this.condition;
296        }
297
298        /**
299         * Add additional details such as the bean type and method signature to
300         * the given error message.
301         * @param message error message to append the HandlerMethod details to
302         */
303        protected String getDetailedErrorMessage(Object bean, String message) {
304                StringBuilder sb = new StringBuilder(message).append("\n");
305                sb.append("HandlerMethod details: \n");
306                sb.append("Bean [").append(bean.getClass().getName()).append("]\n");
307                sb.append("Method [").append(this.bridgedMethod.toGenericString()).append("]\n");
308                return sb.toString();
309        }
310
311        /**
312         * Assert that the target bean class is an instance of the class where the given
313         * method is declared. In some cases the actual bean instance at event-
314         * processing time may be a JDK dynamic proxy (lazy initialization, prototype
315         * beans, and others). Event listener beans that require proxying should prefer
316         * class-based proxy mechanisms.
317         */
318        private void assertTargetBean(Method method, Object targetBean, Object[] args) {
319                Class<?> methodDeclaringClass = method.getDeclaringClass();
320                Class<?> targetBeanClass = targetBean.getClass();
321                if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) {
322                        String msg = "The event listener method class '" + methodDeclaringClass.getName() +
323                                        "' is not an instance of the actual bean class '" +
324                                        targetBeanClass.getName() + "'. If the bean requires proxying " +
325                                        "(e.g. due to @Transactional), please use class-based proxying.";
326                        throw new IllegalStateException(getInvocationErrorMessage(targetBean, msg, args));
327                }
328        }
329
330        private String getInvocationErrorMessage(Object bean, String message, Object[] resolvedArgs) {
331                StringBuilder sb = new StringBuilder(getDetailedErrorMessage(bean, message));
332                sb.append("Resolved arguments: \n");
333                for (int i = 0; i < resolvedArgs.length; i++) {
334                        sb.append("[").append(i).append("] ");
335                        if (resolvedArgs[i] == null) {
336                                sb.append("[null] \n");
337                        }
338                        else {
339                                sb.append("[type=").append(resolvedArgs[i].getClass().getName()).append("] ");
340                                sb.append("[value=").append(resolvedArgs[i]).append("]\n");
341                        }
342                }
343                return sb.toString();
344        }
345
346
347        private ResolvableType getResolvableType(ApplicationEvent event) {
348                ResolvableType payloadType = null;
349                if (event instanceof PayloadApplicationEvent) {
350                        PayloadApplicationEvent<?> payloadEvent = (PayloadApplicationEvent<?>) event;
351                        payloadType = payloadEvent.getResolvableType().as(PayloadApplicationEvent.class).getGeneric();
352                }
353                for (ResolvableType declaredEventType : this.declaredEventTypes) {
354                        if (!ApplicationEvent.class.isAssignableFrom(declaredEventType.getRawClass()) && payloadType != null) {
355                                if (declaredEventType.isAssignableFrom(payloadType)) {
356                                        return declaredEventType;
357                                }
358                        }
359                        if (declaredEventType.getRawClass().isInstance(event)) {
360                                return declaredEventType;
361                        }
362                }
363                return null;
364        }
365
366
367        @Override
368        public String toString() {
369                return this.method.toGenericString();
370        }
371
372}