001/*
002 * Copyright 2002-2018 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.jms.annotation;
018
019import java.lang.reflect.Method;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025import java.util.concurrent.ConcurrentHashMap;
026import java.util.concurrent.atomic.AtomicInteger;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030
031import org.springframework.aop.framework.AopInfrastructureBean;
032import org.springframework.aop.framework.AopProxyUtils;
033import org.springframework.aop.support.AopUtils;
034import org.springframework.beans.BeansException;
035import org.springframework.beans.factory.BeanFactory;
036import org.springframework.beans.factory.BeanFactoryAware;
037import org.springframework.beans.factory.BeanInitializationException;
038import org.springframework.beans.factory.ListableBeanFactory;
039import org.springframework.beans.factory.NoSuchBeanDefinitionException;
040import org.springframework.beans.factory.SmartInitializingSingleton;
041import org.springframework.beans.factory.config.ConfigurableBeanFactory;
042import org.springframework.beans.factory.config.EmbeddedValueResolver;
043import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
044import org.springframework.beans.factory.support.RootBeanDefinition;
045import org.springframework.core.MethodIntrospector;
046import org.springframework.core.Ordered;
047import org.springframework.core.annotation.AnnotatedElementUtils;
048import org.springframework.core.annotation.AnnotationAwareOrderComparator;
049import org.springframework.jms.config.JmsListenerConfigUtils;
050import org.springframework.jms.config.JmsListenerContainerFactory;
051import org.springframework.jms.config.JmsListenerEndpointRegistrar;
052import org.springframework.jms.config.JmsListenerEndpointRegistry;
053import org.springframework.jms.config.MethodJmsListenerEndpoint;
054import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
055import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory;
056import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
057import org.springframework.util.Assert;
058import org.springframework.util.StringUtils;
059import org.springframework.util.StringValueResolver;
060
061/**
062 * Bean post-processor that registers methods annotated with {@link JmsListener}
063 * to be invoked by a JMS message listener container created under the cover
064 * by a {@link org.springframework.jms.config.JmsListenerContainerFactory}
065 * according to the attributes of the annotation.
066 *
067 * <p>Annotated methods can use flexible arguments as defined by {@link JmsListener}.
068 *
069 * <p>This post-processor is automatically registered by Spring's
070 * {@code <jms:annotation-driven>} XML element, and also by the {@link EnableJms}
071 * annotation.
072 *
073 * <p>Autodetects any {@link JmsListenerConfigurer} instances in the container,
074 * allowing for customization of the registry to be used, the default container
075 * factory or for fine-grained control over endpoints registration. See the
076 * {@link EnableJms} javadocs for complete usage details.
077 *
078 * @author Stephane Nicoll
079 * @author Juergen Hoeller
080 * @since 4.1
081 * @see JmsListener
082 * @see EnableJms
083 * @see JmsListenerConfigurer
084 * @see JmsListenerEndpointRegistrar
085 * @see JmsListenerEndpointRegistry
086 * @see org.springframework.jms.config.JmsListenerEndpoint
087 * @see MethodJmsListenerEndpoint
088 */
089public class JmsListenerAnnotationBeanPostProcessor
090                implements MergedBeanDefinitionPostProcessor, Ordered, BeanFactoryAware, SmartInitializingSingleton {
091
092        /**
093         * The bean name of the default {@link JmsListenerContainerFactory}.
094         */
095        static final String DEFAULT_JMS_LISTENER_CONTAINER_FACTORY_BEAN_NAME = "jmsListenerContainerFactory";
096
097
098        protected final Log logger = LogFactory.getLog(getClass());
099
100        private String containerFactoryBeanName = DEFAULT_JMS_LISTENER_CONTAINER_FACTORY_BEAN_NAME;
101
102        private JmsListenerEndpointRegistry endpointRegistry;
103
104        private final MessageHandlerMethodFactoryAdapter messageHandlerMethodFactory =
105                        new MessageHandlerMethodFactoryAdapter();
106
107        private BeanFactory beanFactory;
108
109        private StringValueResolver embeddedValueResolver;
110
111        private final JmsListenerEndpointRegistrar registrar = new JmsListenerEndpointRegistrar();
112
113        private final AtomicInteger counter = new AtomicInteger();
114
115        private final Set<Class<?>> nonAnnotatedClasses =
116                        Collections.newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(64));
117
118
119        @Override
120        public int getOrder() {
121                return LOWEST_PRECEDENCE;
122        }
123
124        /**
125         * Set the name of the {@link JmsListenerContainerFactory} to use by default.
126         * <p>If none is specified, "jmsListenerContainerFactory" is assumed to be defined.
127         */
128        public void setContainerFactoryBeanName(String containerFactoryBeanName) {
129                this.containerFactoryBeanName = containerFactoryBeanName;
130        }
131
132        /**
133         * Set the {@link JmsListenerEndpointRegistry} that will hold the created
134         * endpoint and manage the lifecycle of the related listener container.
135         */
136        public void setEndpointRegistry(JmsListenerEndpointRegistry endpointRegistry) {
137                this.endpointRegistry = endpointRegistry;
138        }
139
140        /**
141         * Set the {@link MessageHandlerMethodFactory} to use to configure the message
142         * listener responsible to serve an endpoint detected by this processor.
143         * <p>By default, {@link DefaultMessageHandlerMethodFactory} is used and it
144         * can be configured further to support additional method arguments
145         * or to customize conversion and validation support. See
146         * {@link DefaultMessageHandlerMethodFactory} Javadoc for more details.
147         */
148        public void setMessageHandlerMethodFactory(MessageHandlerMethodFactory messageHandlerMethodFactory) {
149                this.messageHandlerMethodFactory.setMessageHandlerMethodFactory(messageHandlerMethodFactory);
150        }
151
152        /**
153         * Making a {@link BeanFactory} available is optional; if not set,
154         * {@link JmsListenerConfigurer} beans won't get autodetected and an
155         * {@link #setEndpointRegistry endpoint registry} has to be explicitly configured.
156         */
157        @Override
158        public void setBeanFactory(BeanFactory beanFactory) {
159                this.beanFactory = beanFactory;
160                if (beanFactory instanceof ConfigurableBeanFactory) {
161                        this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory);
162                }
163                this.registrar.setBeanFactory(beanFactory);
164        }
165
166
167        @Override
168        public void afterSingletonsInstantiated() {
169                // Remove resolved singleton classes from cache
170                this.nonAnnotatedClasses.clear();
171
172                if (this.beanFactory instanceof ListableBeanFactory) {
173                        // Apply JmsListenerConfigurer beans from the BeanFactory, if any
174                        Map<String, JmsListenerConfigurer> beans =
175                                        ((ListableBeanFactory) this.beanFactory).getBeansOfType(JmsListenerConfigurer.class);
176                        List<JmsListenerConfigurer> configurers = new ArrayList<JmsListenerConfigurer>(beans.values());
177                        AnnotationAwareOrderComparator.sort(configurers);
178                        for (JmsListenerConfigurer configurer : configurers) {
179                                configurer.configureJmsListeners(this.registrar);
180                        }
181                }
182
183                if (this.containerFactoryBeanName != null) {
184                        this.registrar.setContainerFactoryBeanName(this.containerFactoryBeanName);
185                }
186
187                if (this.registrar.getEndpointRegistry() == null) {
188                        // Determine JmsListenerEndpointRegistry bean from the BeanFactory
189                        if (this.endpointRegistry == null) {
190                                Assert.state(this.beanFactory != null, "BeanFactory must be set to find endpoint registry by bean name");
191                                this.endpointRegistry = this.beanFactory.getBean(
192                                                JmsListenerConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME, JmsListenerEndpointRegistry.class);
193                        }
194                        this.registrar.setEndpointRegistry(this.endpointRegistry);
195                }
196
197
198                // Set the custom handler method factory once resolved by the configurer
199                MessageHandlerMethodFactory handlerMethodFactory = this.registrar.getMessageHandlerMethodFactory();
200                if (handlerMethodFactory != null) {
201                        this.messageHandlerMethodFactory.setMessageHandlerMethodFactory(handlerMethodFactory);
202                }
203
204                // Actually register all listeners
205                this.registrar.afterPropertiesSet();
206        }
207
208
209        @Override
210        public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
211        }
212
213        @Override
214        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
215                return bean;
216        }
217
218        @Override
219        public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
220                if (bean instanceof AopInfrastructureBean) {
221                        // Ignore AOP infrastructure such as scoped proxies.
222                        return bean;
223                }
224
225                Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
226                if (!this.nonAnnotatedClasses.contains(targetClass)) {
227                        Map<Method, Set<JmsListener>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
228                                        new MethodIntrospector.MetadataLookup<Set<JmsListener>>() {
229                                                @Override
230                                                public Set<JmsListener> inspect(Method method) {
231                                                        Set<JmsListener> listenerMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
232                                                                        method, JmsListener.class, JmsListeners.class);
233                                                        return (!listenerMethods.isEmpty() ? listenerMethods : null);
234                                                }
235                                        });
236                        if (annotatedMethods.isEmpty()) {
237                                this.nonAnnotatedClasses.add(targetClass);
238                                if (logger.isTraceEnabled()) {
239                                        logger.trace("No @JmsListener annotations found on bean type: " + targetClass);
240                                }
241                        }
242                        else {
243                                // Non-empty set of methods
244                                for (Map.Entry<Method, Set<JmsListener>> entry : annotatedMethods.entrySet()) {
245                                        Method method = entry.getKey();
246                                        for (JmsListener listener : entry.getValue()) {
247                                                processJmsListener(listener, method, bean);
248                                        }
249                                }
250                                if (logger.isDebugEnabled()) {
251                                        logger.debug(annotatedMethods.size() + " @JmsListener methods processed on bean '" + beanName +
252                                                        "': " + annotatedMethods);
253                                }
254                        }
255                }
256                return bean;
257        }
258
259        /**
260         * Process the given {@link JmsListener} annotation on the given method,
261         * registering a corresponding endpoint for the given bean instance.
262         * @param jmsListener the annotation to process
263         * @param mostSpecificMethod the annotated method
264         * @param bean the instance to invoke the method on
265         * @see #createMethodJmsListenerEndpoint()
266         * @see JmsListenerEndpointRegistrar#registerEndpoint
267         */
268        protected void processJmsListener(JmsListener jmsListener, Method mostSpecificMethod, Object bean) {
269                Method invocableMethod = AopUtils.selectInvocableMethod(mostSpecificMethod, bean.getClass());
270
271                MethodJmsListenerEndpoint endpoint = createMethodJmsListenerEndpoint();
272                endpoint.setBean(bean);
273                endpoint.setMethod(invocableMethod);
274                endpoint.setMostSpecificMethod(mostSpecificMethod);
275                endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory);
276                endpoint.setEmbeddedValueResolver(this.embeddedValueResolver);
277                endpoint.setBeanFactory(this.beanFactory);
278                endpoint.setId(getEndpointId(jmsListener));
279                endpoint.setDestination(resolve(jmsListener.destination()));
280                if (StringUtils.hasText(jmsListener.selector())) {
281                        endpoint.setSelector(resolve(jmsListener.selector()));
282                }
283                if (StringUtils.hasText(jmsListener.subscription())) {
284                        endpoint.setSubscription(resolve(jmsListener.subscription()));
285                }
286                if (StringUtils.hasText(jmsListener.concurrency())) {
287                        endpoint.setConcurrency(resolve(jmsListener.concurrency()));
288                }
289
290                JmsListenerContainerFactory<?> factory = null;
291                String containerFactoryBeanName = resolve(jmsListener.containerFactory());
292                if (StringUtils.hasText(containerFactoryBeanName)) {
293                        Assert.state(this.beanFactory != null, "BeanFactory must be set to obtain container factory by bean name");
294                        try {
295                                factory = this.beanFactory.getBean(containerFactoryBeanName, JmsListenerContainerFactory.class);
296                        }
297                        catch (NoSuchBeanDefinitionException ex) {
298                                throw new BeanInitializationException("Could not register JMS listener endpoint on [" +
299                                                mostSpecificMethod + "], no " + JmsListenerContainerFactory.class.getSimpleName() +
300                                                " with id '" + containerFactoryBeanName + "' was found in the application context", ex);
301                        }
302                }
303
304                this.registrar.registerEndpoint(endpoint, factory);
305        }
306
307        /**
308         * Instantiate an empty {@link MethodJmsListenerEndpoint} for further
309         * configuration with provided parameters in {@link #processJmsListener}.
310         * @return a new {@code MethodJmsListenerEndpoint} or subclass thereof
311         * @since 4.1.9
312         * @see MethodJmsListenerEndpoint#createMessageListenerInstance()
313         */
314        protected MethodJmsListenerEndpoint createMethodJmsListenerEndpoint() {
315                return new MethodJmsListenerEndpoint();
316        }
317
318        private String getEndpointId(JmsListener jmsListener) {
319                if (StringUtils.hasText(jmsListener.id())) {
320                        return resolve(jmsListener.id());
321                }
322                else {
323                        return "org.springframework.jms.JmsListenerEndpointContainer#" + this.counter.getAndIncrement();
324                }
325        }
326
327        private String resolve(String value) {
328                return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value);
329        }
330
331
332        /**
333         * A {@link MessageHandlerMethodFactory} adapter that offers a configurable underlying
334         * instance to use. Useful if the factory to use is determined once the endpoints
335         * have been registered but not created yet.
336         * @see JmsListenerEndpointRegistrar#setMessageHandlerMethodFactory
337         */
338        private class MessageHandlerMethodFactoryAdapter implements MessageHandlerMethodFactory {
339
340                private MessageHandlerMethodFactory messageHandlerMethodFactory;
341
342                public void setMessageHandlerMethodFactory(MessageHandlerMethodFactory messageHandlerMethodFactory) {
343                        this.messageHandlerMethodFactory = messageHandlerMethodFactory;
344                }
345
346                @Override
347                public InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method method) {
348                        return getMessageHandlerMethodFactory().createInvocableHandlerMethod(bean, method);
349                }
350
351                private MessageHandlerMethodFactory getMessageHandlerMethodFactory() {
352                        if (this.messageHandlerMethodFactory == null) {
353                                this.messageHandlerMethodFactory = createDefaultJmsHandlerMethodFactory();
354                        }
355                        return this.messageHandlerMethodFactory;
356                }
357
358                private MessageHandlerMethodFactory createDefaultJmsHandlerMethodFactory() {
359                        DefaultMessageHandlerMethodFactory defaultFactory = new DefaultMessageHandlerMethodFactory();
360                        defaultFactory.setBeanFactory(beanFactory);
361                        defaultFactory.afterPropertiesSet();
362                        return defaultFactory;
363                }
364        }
365
366}