001/*
002 * Copyright 2012-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 *      http://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.boot.autoconfigure.amqp;
018
019import java.time.Duration;
020import java.util.stream.Collectors;
021
022import com.rabbitmq.client.Channel;
023
024import org.springframework.amqp.core.AmqpAdmin;
025import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
026import org.springframework.amqp.rabbit.connection.ConnectionFactory;
027import org.springframework.amqp.rabbit.connection.ConnectionNameStrategy;
028import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean;
029import org.springframework.amqp.rabbit.core.RabbitAdmin;
030import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
031import org.springframework.amqp.rabbit.core.RabbitTemplate;
032import org.springframework.amqp.support.converter.MessageConverter;
033import org.springframework.beans.factory.ObjectProvider;
034import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
035import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
036import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
037import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
038import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
039import org.springframework.boot.context.properties.EnableConfigurationProperties;
040import org.springframework.boot.context.properties.PropertyMapper;
041import org.springframework.context.annotation.Bean;
042import org.springframework.context.annotation.Configuration;
043import org.springframework.context.annotation.Import;
044
045/**
046 * {@link EnableAutoConfiguration Auto-configuration} for {@link RabbitTemplate}.
047 * <p>
048 * This configuration class is active only when the RabbitMQ and Spring AMQP client
049 * libraries are on the classpath.
050 * <P>
051 * Registers the following beans:
052 * <ul>
053 * <li>{@link org.springframework.amqp.rabbit.core.RabbitTemplate RabbitTemplate} if there
054 * is no other bean of the same type in the context.</li>
055 * <li>{@link org.springframework.amqp.rabbit.connection.CachingConnectionFactory
056 * CachingConnectionFactory} instance if there is no other bean of the same type in the
057 * context.</li>
058 * <li>{@link org.springframework.amqp.core.AmqpAdmin } instance as long as
059 * {@literal spring.rabbitmq.dynamic=true}.</li>
060 * </ul>
061 * <p>
062 * The {@link org.springframework.amqp.rabbit.connection.CachingConnectionFactory} honors
063 * the following properties:
064 * <ul>
065 * <li>{@literal spring.rabbitmq.port} is used to specify the port to which the client
066 * should connect, and defaults to 5672.</li>
067 * <li>{@literal spring.rabbitmq.username} is used to specify the (optional) username.
068 * </li>
069 * <li>{@literal spring.rabbitmq.password} is used to specify the (optional) password.
070 * </li>
071 * <li>{@literal spring.rabbitmq.host} is used to specify the host, and defaults to
072 * {@literal localhost}.</li>
073 * <li>{@literal spring.rabbitmq.virtualHost} is used to specify the (optional) virtual
074 * host to which the client should connect.</li>
075 * </ul>
076 *
077 * @author Greg Turnquist
078 * @author Josh Long
079 * @author Stephane Nicoll
080 * @author Gary Russell
081 * @author Phillip Webb
082 * @author Artsiom Yudovin
083 */
084@Configuration
085@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
086@EnableConfigurationProperties(RabbitProperties.class)
087@Import(RabbitAnnotationDrivenConfiguration.class)
088public class RabbitAutoConfiguration {
089
090        @Configuration
091        @ConditionalOnMissingBean(ConnectionFactory.class)
092        protected static class RabbitConnectionFactoryCreator {
093
094                @Bean
095                public CachingConnectionFactory rabbitConnectionFactory(
096                                RabbitProperties properties,
097                                ObjectProvider<ConnectionNameStrategy> connectionNameStrategy)
098                                throws Exception {
099                        PropertyMapper map = PropertyMapper.get();
100                        CachingConnectionFactory factory = new CachingConnectionFactory(
101                                        getRabbitConnectionFactoryBean(properties).getObject());
102                        map.from(properties::determineAddresses).to(factory::setAddresses);
103                        map.from(properties::isPublisherConfirms).to(factory::setPublisherConfirms);
104                        map.from(properties::isPublisherReturns).to(factory::setPublisherReturns);
105                        RabbitProperties.Cache.Channel channel = properties.getCache().getChannel();
106                        map.from(channel::getSize).whenNonNull().to(factory::setChannelCacheSize);
107                        map.from(channel::getCheckoutTimeout).whenNonNull().as(Duration::toMillis)
108                                        .to(factory::setChannelCheckoutTimeout);
109                        RabbitProperties.Cache.Connection connection = properties.getCache()
110                                        .getConnection();
111                        map.from(connection::getMode).whenNonNull().to(factory::setCacheMode);
112                        map.from(connection::getSize).whenNonNull()
113                                        .to(factory::setConnectionCacheSize);
114                        map.from(connectionNameStrategy::getIfUnique).whenNonNull()
115                                        .to(factory::setConnectionNameStrategy);
116                        return factory;
117                }
118
119                private RabbitConnectionFactoryBean getRabbitConnectionFactoryBean(
120                                RabbitProperties properties) throws Exception {
121                        PropertyMapper map = PropertyMapper.get();
122                        RabbitConnectionFactoryBean factory = new RabbitConnectionFactoryBean();
123                        map.from(properties::determineHost).whenNonNull().to(factory::setHost);
124                        map.from(properties::determinePort).to(factory::setPort);
125                        map.from(properties::determineUsername).whenNonNull()
126                                        .to(factory::setUsername);
127                        map.from(properties::determinePassword).whenNonNull()
128                                        .to(factory::setPassword);
129                        map.from(properties::determineVirtualHost).whenNonNull()
130                                        .to(factory::setVirtualHost);
131                        map.from(properties::getRequestedHeartbeat).whenNonNull()
132                                        .asInt(Duration::getSeconds).to(factory::setRequestedHeartbeat);
133                        RabbitProperties.Ssl ssl = properties.getSsl();
134                        if (ssl.isEnabled()) {
135                                factory.setUseSSL(true);
136                                map.from(ssl::getAlgorithm).whenNonNull().to(factory::setSslAlgorithm);
137                                map.from(ssl::getKeyStoreType).to(factory::setKeyStoreType);
138                                map.from(ssl::getKeyStore).to(factory::setKeyStore);
139                                map.from(ssl::getKeyStorePassword).to(factory::setKeyStorePassphrase);
140                                map.from(ssl::getTrustStoreType).to(factory::setTrustStoreType);
141                                map.from(ssl::getTrustStore).to(factory::setTrustStore);
142                                map.from(ssl::getTrustStorePassword).to(factory::setTrustStorePassphrase);
143                                map.from(ssl::isValidateServerCertificate).to((validate) -> factory
144                                                .setSkipServerCertificateValidation(!validate));
145                                map.from(ssl::getVerifyHostname)
146                                                .to(factory::setEnableHostnameVerification);
147                        }
148                        map.from(properties::getConnectionTimeout).whenNonNull()
149                                        .asInt(Duration::toMillis).to(factory::setConnectionTimeout);
150                        factory.afterPropertiesSet();
151                        return factory;
152                }
153
154        }
155
156        @Configuration
157        @Import(RabbitConnectionFactoryCreator.class)
158        protected static class RabbitTemplateConfiguration {
159
160                private final RabbitProperties properties;
161
162                private final ObjectProvider<MessageConverter> messageConverter;
163
164                private final ObjectProvider<RabbitRetryTemplateCustomizer> retryTemplateCustomizers;
165
166                public RabbitTemplateConfiguration(RabbitProperties properties,
167                                ObjectProvider<MessageConverter> messageConverter,
168                                ObjectProvider<RabbitRetryTemplateCustomizer> retryTemplateCustomizers) {
169                        this.properties = properties;
170                        this.messageConverter = messageConverter;
171                        this.retryTemplateCustomizers = retryTemplateCustomizers;
172                }
173
174                @Bean
175                @ConditionalOnSingleCandidate(ConnectionFactory.class)
176                @ConditionalOnMissingBean
177                public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
178                        PropertyMapper map = PropertyMapper.get();
179                        RabbitTemplate template = new RabbitTemplate(connectionFactory);
180                        MessageConverter messageConverter = this.messageConverter.getIfUnique();
181                        if (messageConverter != null) {
182                                template.setMessageConverter(messageConverter);
183                        }
184                        template.setMandatory(determineMandatoryFlag());
185                        RabbitProperties.Template properties = this.properties.getTemplate();
186                        if (properties.getRetry().isEnabled()) {
187                                template.setRetryTemplate(new RetryTemplateFactory(
188                                                this.retryTemplateCustomizers.orderedStream()
189                                                                .collect(Collectors.toList())).createRetryTemplate(
190                                                                                properties.getRetry(),
191                                                                                RabbitRetryTemplateCustomizer.Target.SENDER));
192                        }
193                        map.from(properties::getReceiveTimeout).whenNonNull().as(Duration::toMillis)
194                                        .to(template::setReceiveTimeout);
195                        map.from(properties::getReplyTimeout).whenNonNull().as(Duration::toMillis)
196                                        .to(template::setReplyTimeout);
197                        map.from(properties::getExchange).to(template::setExchange);
198                        map.from(properties::getRoutingKey).to(template::setRoutingKey);
199                        map.from(properties::getDefaultReceiveQueue).whenNonNull()
200                                        .to(template::setDefaultReceiveQueue);
201                        return template;
202                }
203
204                private boolean determineMandatoryFlag() {
205                        Boolean mandatory = this.properties.getTemplate().getMandatory();
206                        return (mandatory != null) ? mandatory : this.properties.isPublisherReturns();
207                }
208
209                @Bean
210                @ConditionalOnSingleCandidate(ConnectionFactory.class)
211                @ConditionalOnProperty(prefix = "spring.rabbitmq", name = "dynamic", matchIfMissing = true)
212                @ConditionalOnMissingBean
213                public AmqpAdmin amqpAdmin(ConnectionFactory connectionFactory) {
214                        return new RabbitAdmin(connectionFactory);
215                }
216
217        }
218
219        @Configuration
220        @ConditionalOnClass(RabbitMessagingTemplate.class)
221        @ConditionalOnMissingBean(RabbitMessagingTemplate.class)
222        @Import(RabbitTemplateConfiguration.class)
223        protected static class MessagingTemplateConfiguration {
224
225                @Bean
226                @ConditionalOnSingleCandidate(RabbitTemplate.class)
227                public RabbitMessagingTemplate rabbitMessagingTemplate(
228                                RabbitTemplate rabbitTemplate) {
229                        return new RabbitMessagingTemplate(rabbitTemplate);
230                }
231
232        }
233
234}