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