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.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.core.annotation.AnnotationUtils;
050import org.springframework.jms.config.JmsListenerConfigUtils;
051import org.springframework.jms.config.JmsListenerContainerFactory;
052import org.springframework.jms.config.JmsListenerEndpointRegistrar;
053import org.springframework.jms.config.JmsListenerEndpointRegistry;
054import org.springframework.jms.config.MethodJmsListenerEndpoint;
055import org.springframework.lang.Nullable;
056import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
057import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory;
058import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
059import org.springframework.util.Assert;
060import org.springframework.util.StringUtils;
061import org.springframework.util.StringValueResolver;
062
063/**
064 * Bean post-processor that registers methods annotated with {@link JmsListener}
065 * to be invoked by a JMS message listener container created under the cover
066 * by a {@link org.springframework.jms.config.JmsListenerContainerFactory}
067 * according to the attributes of the annotation.
068 *
069 * <p>Annotated methods can use flexible arguments as defined by {@link JmsListener}.
070 *
071 * <p>This post-processor is automatically registered by Spring's
072 * {@code <jms:annotation-driven>} XML element, and also by the {@link EnableJms}
073 * annotation.
074 *
075 * <p>Autodetects any {@link JmsListenerConfigurer} instances in the container,
076 * allowing for customization of the registry to be used, the default container
077 * factory or for fine-grained control over endpoints registration. See the
078 * {@link EnableJms} javadocs for complete usage details.
079 *
080 * @author Stephane Nicoll
081 * @author Juergen Hoeller
082 * @since 4.1
083 * @see JmsListener
084 * @see EnableJms
085 * @see JmsListenerConfigurer
086 * @see JmsListenerEndpointRegistrar
087 * @see JmsListenerEndpointRegistry
088 * @see org.springframework.jms.config.JmsListenerEndpoint
089 * @see MethodJmsListenerEndpoint
090 */
091public class JmsListenerAnnotationBeanPostProcessor
092                implements MergedBeanDefinitionPostProcessor, Ordered, BeanFactoryAware, SmartInitializingSingleton {
093
094        /**
095         * The bean name of the default {@link JmsListenerContainerFactory}.
096         */
097        static final String DEFAULT_JMS_LISTENER_CONTAINER_FACTORY_BEAN_NAME = "jmsListenerContainerFactory";
098
099
100        protected final Log logger = LogFactory.getLog(getClass());
101
102        @Nullable
103        private String containerFactoryBeanName = DEFAULT_JMS_LISTENER_CONTAINER_FACTORY_BEAN_NAME;
104
105        @Nullable
106        private JmsListenerEndpointRegistry endpointRegistry;
107
108        private final MessageHandlerMethodFactoryAdapter messageHandlerMethodFactory =
109                        new MessageHandlerMethodFactoryAdapter();
110
111        @Nullable
112        private BeanFactory beanFactory;
113
114        @Nullable
115        private StringValueResolver embeddedValueResolver;
116
117        private final JmsListenerEndpointRegistrar registrar = new JmsListenerEndpointRegistrar();
118
119        private final AtomicInteger counter = new AtomicInteger();
120
121        private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));
122
123
124        @Override
125        public int getOrder() {
126                return LOWEST_PRECEDENCE;
127        }
128
129        /**
130         * Set the name of the {@link JmsListenerContainerFactory} to use by default.
131         * <p>If none is specified, "jmsListenerContainerFactory" is assumed to be defined.
132         */
133        public void setContainerFactoryBeanName(String containerFactoryBeanName) {
134                this.containerFactoryBeanName = containerFactoryBeanName;
135        }
136
137        /**
138         * Set the {@link JmsListenerEndpointRegistry} that will hold the created
139         * endpoint and manage the lifecycle of the related listener container.
140         */
141        public void setEndpointRegistry(JmsListenerEndpointRegistry endpointRegistry) {
142                this.endpointRegistry = endpointRegistry;
143        }
144
145        /**
146         * Set the {@link MessageHandlerMethodFactory} to use to configure the message
147         * listener responsible to serve an endpoint detected by this processor.
148         * <p>By default, {@link DefaultMessageHandlerMethodFactory} is used and it
149         * can be configured further to support additional method arguments
150         * or to customize conversion and validation support. See
151         * {@link DefaultMessageHandlerMethodFactory} Javadoc for more details.
152         */
153        public void setMessageHandlerMethodFactory(MessageHandlerMethodFactory messageHandlerMethodFactory) {
154                this.messageHandlerMethodFactory.setMessageHandlerMethodFactory(messageHandlerMethodFactory);
155        }
156
157        /**
158         * Making a {@link BeanFactory} available is optional; if not set,
159         * {@link JmsListenerConfigurer} beans won't get autodetected and an
160         * {@link #setEndpointRegistry endpoint registry} has to be explicitly configured.
161         */
162        @Override
163        public void setBeanFactory(BeanFactory beanFactory) {
164                this.beanFactory = beanFactory;
165                if (beanFactory instanceof ConfigurableBeanFactory) {
166                        this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory);
167                }
168                this.registrar.setBeanFactory(beanFactory);
169        }
170
171
172        @Override
173        public void afterSingletonsInstantiated() {
174                // Remove resolved singleton classes from cache
175                this.nonAnnotatedClasses.clear();
176
177                if (this.beanFactory instanceof ListableBeanFactory) {
178                        // Apply JmsListenerConfigurer beans from the BeanFactory, if any
179                        Map<String, JmsListenerConfigurer> beans =
180                                        ((ListableBeanFactory) this.beanFactory).getBeansOfType(JmsListenerConfigurer.class);
181                        List<JmsListenerConfigurer> configurers = new ArrayList<>(beans.values());
182                        AnnotationAwareOrderComparator.sort(configurers);
183                        for (JmsListenerConfigurer configurer : configurers) {
184                                configurer.configureJmsListeners(this.registrar);
185                        }
186                }
187
188                if (this.containerFactoryBeanName != null) {
189                        this.registrar.setContainerFactoryBeanName(this.containerFactoryBeanName);
190                }
191
192                if (this.registrar.getEndpointRegistry() == null) {
193                        // Determine JmsListenerEndpointRegistry bean from the BeanFactory
194                        if (this.endpointRegistry == null) {
195                                Assert.state(this.beanFactory != null, "BeanFactory must be set to find endpoint registry by bean name");
196                                this.endpointRegistry = this.beanFactory.getBean(
197                                                JmsListenerConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME, JmsListenerEndpointRegistry.class);
198                        }
199                        this.registrar.setEndpointRegistry(this.endpointRegistry);
200                }
201
202
203                // Set the custom handler method factory once resolved by the configurer
204                MessageHandlerMethodFactory handlerMethodFactory = this.registrar.getMessageHandlerMethodFactory();
205                if (handlerMethodFactory != null) {
206                        this.messageHandlerMethodFactory.setMessageHandlerMethodFactory(handlerMethodFactory);
207                }
208
209                // Actually register all listeners
210                this.registrar.afterPropertiesSet();
211        }
212
213
214        @Override
215        public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
216        }
217
218        @Override
219        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
220                return bean;
221        }
222
223        @Override
224        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
225                if (bean instanceof AopInfrastructureBean || bean instanceof JmsListenerContainerFactory ||
226                                bean instanceof JmsListenerEndpointRegistry) {
227                        // Ignore AOP infrastructure such as scoped proxies.
228                        return bean;
229                }
230
231                Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
232                if (!this.nonAnnotatedClasses.contains(targetClass) &&
233                                AnnotationUtils.isCandidateClass(targetClass, JmsListener.class)) {
234                        Map<Method, Set<JmsListener>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
235                                        (MethodIntrospector.MetadataLookup<Set<JmsListener>>) method -> {
236                                                Set<JmsListener> listenerMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
237                                                                method, JmsListener.class, JmsListeners.class);
238                                                return (!listenerMethods.isEmpty() ? listenerMethods : null);
239                                        });
240                        if (annotatedMethods.isEmpty()) {
241                                this.nonAnnotatedClasses.add(targetClass);
242                                if (logger.isTraceEnabled()) {
243                                        logger.trace("No @JmsListener annotations found on bean type: " + targetClass);
244                                }
245                        }
246                        else {
247                                // Non-empty set of methods
248                                annotatedMethods.forEach((method, listeners) ->
249                                                listeners.forEach(listener -> processJmsListener(listener, method, bean)));
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                        String id = resolve(jmsListener.id());
321                        return (id != null ? id : "");
322                }
323                else {
324                        return "org.springframework.jms.JmsListenerEndpointContainer#" + this.counter.getAndIncrement();
325                }
326        }
327
328        @Nullable
329        private String resolve(String value) {
330                return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value);
331        }
332
333
334        /**
335         * A {@link MessageHandlerMethodFactory} adapter that offers a configurable underlying
336         * instance to use. Useful if the factory to use is determined once the endpoints
337         * have been registered but not created yet.
338         * @see JmsListenerEndpointRegistrar#setMessageHandlerMethodFactory
339         */
340        private class MessageHandlerMethodFactoryAdapter implements MessageHandlerMethodFactory {
341
342                @Nullable
343                private MessageHandlerMethodFactory messageHandlerMethodFactory;
344
345                public void setMessageHandlerMethodFactory(MessageHandlerMethodFactory messageHandlerMethodFactory) {
346                        this.messageHandlerMethodFactory = messageHandlerMethodFactory;
347                }
348
349                @Override
350                public InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method method) {
351                        return getMessageHandlerMethodFactory().createInvocableHandlerMethod(bean, method);
352                }
353
354                private MessageHandlerMethodFactory getMessageHandlerMethodFactory() {
355                        if (this.messageHandlerMethodFactory == null) {
356                                this.messageHandlerMethodFactory = createDefaultJmsHandlerMethodFactory();
357                        }
358                        return this.messageHandlerMethodFactory;
359                }
360
361                private MessageHandlerMethodFactory createDefaultJmsHandlerMethodFactory() {
362                        DefaultMessageHandlerMethodFactory defaultFactory = new DefaultMessageHandlerMethodFactory();
363                        if (beanFactory != null) {
364                                defaultFactory.setBeanFactory(beanFactory);
365                        }
366                        defaultFactory.afterPropertiesSet();
367                        return defaultFactory;
368                }
369        }
370
371}