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.config;
018
019import java.util.ArrayList;
020import java.util.List;
021
022import org.springframework.beans.factory.BeanFactory;
023import org.springframework.beans.factory.BeanFactoryAware;
024import org.springframework.beans.factory.InitializingBean;
025import org.springframework.beans.factory.config.ConfigurableBeanFactory;
026import org.springframework.lang.Nullable;
027import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
028import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory;
029import org.springframework.util.Assert;
030
031/**
032 * Helper bean for registering {@link JmsListenerEndpoint} with a {@link JmsListenerEndpointRegistry}.
033 *
034 * @author Stephane Nicoll
035 * @author Juergen Hoeller
036 * @since 4.1
037 * @see org.springframework.jms.annotation.JmsListenerConfigurer
038 */
039public class JmsListenerEndpointRegistrar implements BeanFactoryAware, InitializingBean {
040
041        @Nullable
042        private JmsListenerEndpointRegistry endpointRegistry;
043
044        @Nullable
045        private MessageHandlerMethodFactory messageHandlerMethodFactory;
046
047        @Nullable
048        private JmsListenerContainerFactory<?> containerFactory;
049
050        @Nullable
051        private String containerFactoryBeanName;
052
053        @Nullable
054        private BeanFactory beanFactory;
055
056        private final List<JmsListenerEndpointDescriptor> endpointDescriptors = new ArrayList<>();
057
058        private boolean startImmediately;
059
060        private Object mutex = this.endpointDescriptors;
061
062
063        /**
064         * Set the {@link JmsListenerEndpointRegistry} instance to use.
065         */
066        public void setEndpointRegistry(@Nullable JmsListenerEndpointRegistry endpointRegistry) {
067                this.endpointRegistry = endpointRegistry;
068        }
069
070        /**
071         * Return the {@link JmsListenerEndpointRegistry} instance for this
072         * registrar, may be {@code null}.
073         */
074        @Nullable
075        public JmsListenerEndpointRegistry getEndpointRegistry() {
076                return this.endpointRegistry;
077        }
078
079        /**
080         * Set the {@link MessageHandlerMethodFactory} to use to configure the message
081         * listener responsible to serve an endpoint detected by this processor.
082         * <p>By default, {@link DefaultMessageHandlerMethodFactory} is used and it
083         * can be configured further to support additional method arguments
084         * or to customize conversion and validation support. See
085         * {@link DefaultMessageHandlerMethodFactory} javadoc for more details.
086         */
087        public void setMessageHandlerMethodFactory(@Nullable MessageHandlerMethodFactory messageHandlerMethodFactory) {
088                this.messageHandlerMethodFactory = messageHandlerMethodFactory;
089        }
090
091        /**
092         * Return the custom {@link MessageHandlerMethodFactory} to use, if any.
093         */
094        @Nullable
095        public MessageHandlerMethodFactory getMessageHandlerMethodFactory() {
096                return this.messageHandlerMethodFactory;
097        }
098
099        /**
100         * Set the {@link JmsListenerContainerFactory} to use in case a {@link JmsListenerEndpoint}
101         * is registered with a {@code null} container factory.
102         * <p>Alternatively, the bean name of the {@link JmsListenerContainerFactory} to use
103         * can be specified for a lazy lookup, see {@link #setContainerFactoryBeanName}.
104         */
105        public void setContainerFactory(JmsListenerContainerFactory<?> containerFactory) {
106                this.containerFactory = containerFactory;
107        }
108
109        /**
110         * Set the bean name of the {@link JmsListenerContainerFactory} to use in case
111         * a {@link JmsListenerEndpoint} is registered with a {@code null} container factory.
112         * Alternatively, the container factory instance can be registered directly:
113         * see {@link #setContainerFactory(JmsListenerContainerFactory)}.
114         * @see #setBeanFactory
115         */
116        public void setContainerFactoryBeanName(String containerFactoryBeanName) {
117                this.containerFactoryBeanName = containerFactoryBeanName;
118        }
119
120        /**
121         * A {@link BeanFactory} only needs to be available in conjunction with
122         * {@link #setContainerFactoryBeanName}.
123         */
124        @Override
125        public void setBeanFactory(BeanFactory beanFactory) {
126                this.beanFactory = beanFactory;
127                if (beanFactory instanceof ConfigurableBeanFactory) {
128                        this.mutex = ((ConfigurableBeanFactory) beanFactory).getSingletonMutex();
129                }
130        }
131
132
133        @Override
134        public void afterPropertiesSet() {
135                registerAllEndpoints();
136        }
137
138        protected void registerAllEndpoints() {
139                Assert.state(this.endpointRegistry != null, "No JmsListenerEndpointRegistry set");
140                synchronized (this.mutex) {
141                        for (JmsListenerEndpointDescriptor descriptor : this.endpointDescriptors) {
142                                this.endpointRegistry.registerListenerContainer(
143                                                descriptor.endpoint, resolveContainerFactory(descriptor));
144                        }
145                        this.startImmediately = true;  // trigger immediate startup
146                }
147        }
148
149        private JmsListenerContainerFactory<?> resolveContainerFactory(JmsListenerEndpointDescriptor descriptor) {
150                if (descriptor.containerFactory != null) {
151                        return descriptor.containerFactory;
152                }
153                else if (this.containerFactory != null) {
154                        return this.containerFactory;
155                }
156                else if (this.containerFactoryBeanName != null) {
157                        Assert.state(this.beanFactory != null, "BeanFactory must be set to obtain container factory by bean name");
158                        // Consider changing this if live change of the factory is required...
159                        this.containerFactory = this.beanFactory.getBean(
160                                        this.containerFactoryBeanName, JmsListenerContainerFactory.class);
161                        return this.containerFactory;
162                }
163                else {
164                        throw new IllegalStateException("Could not resolve the " +
165                                        JmsListenerContainerFactory.class.getSimpleName() + " to use for [" +
166                                        descriptor.endpoint + "] no factory was given and no default is set.");
167                }
168        }
169
170        /**
171         * Register a new {@link JmsListenerEndpoint} alongside the
172         * {@link JmsListenerContainerFactory} to use to create the underlying container.
173         * <p>The {@code factory} may be {@code null} if the default factory has to be
174         * used for that endpoint.
175         */
176        public void registerEndpoint(JmsListenerEndpoint endpoint, @Nullable JmsListenerContainerFactory<?> factory) {
177                Assert.notNull(endpoint, "Endpoint must not be null");
178                Assert.hasText(endpoint.getId(), "Endpoint id must be set");
179
180                // Factory may be null, we defer the resolution right before actually creating the container
181                JmsListenerEndpointDescriptor descriptor = new JmsListenerEndpointDescriptor(endpoint, factory);
182
183                synchronized (this.mutex) {
184                        if (this.startImmediately) {  // register and start immediately
185                                Assert.state(this.endpointRegistry != null, "No JmsListenerEndpointRegistry set");
186                                this.endpointRegistry.registerListenerContainer(descriptor.endpoint,
187                                                resolveContainerFactory(descriptor), true);
188                        }
189                        else {
190                                this.endpointDescriptors.add(descriptor);
191                        }
192                }
193        }
194
195        /**
196         * Register a new {@link JmsListenerEndpoint} using the default
197         * {@link JmsListenerContainerFactory} to create the underlying container.
198         * @see #setContainerFactory(JmsListenerContainerFactory)
199         * @see #registerEndpoint(JmsListenerEndpoint, JmsListenerContainerFactory)
200         */
201        public void registerEndpoint(JmsListenerEndpoint endpoint) {
202                registerEndpoint(endpoint, null);
203        }
204
205
206        private static class JmsListenerEndpointDescriptor {
207
208                public final JmsListenerEndpoint endpoint;
209
210                @Nullable
211                public final JmsListenerContainerFactory<?> containerFactory;
212
213                public JmsListenerEndpointDescriptor(JmsListenerEndpoint endpoint,
214                                @Nullable JmsListenerContainerFactory<?> containerFactory) {
215
216                        this.endpoint = endpoint;
217                        this.containerFactory = containerFactory;
218                }
219        }
220
221}