001/* 002 * Copyright 2012-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 * 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.jdbc; 018 019import java.sql.SQLException; 020 021import javax.sql.DataSource; 022import javax.sql.XADataSource; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026import org.apache.tomcat.jdbc.pool.DataSourceProxy; 027 028import org.springframework.beans.factory.BeanFactoryUtils; 029import org.springframework.beans.factory.NoSuchBeanDefinitionException; 030import org.springframework.beans.factory.config.BeanDefinition; 031import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 032import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 033import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; 034import org.springframework.boot.autoconfigure.condition.ConditionMessage; 035import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 036import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 037import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 038import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 039import org.springframework.boot.autoconfigure.condition.SpringBootCondition; 040import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerPostProcessor.Registrar; 041import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration; 042import org.springframework.boot.context.properties.EnableConfigurationProperties; 043import org.springframework.context.ApplicationContext; 044import org.springframework.context.annotation.Bean; 045import org.springframework.context.annotation.Condition; 046import org.springframework.context.annotation.ConditionContext; 047import org.springframework.context.annotation.Conditional; 048import org.springframework.context.annotation.Configuration; 049import org.springframework.context.annotation.Import; 050import org.springframework.core.Ordered; 051import org.springframework.core.annotation.Order; 052import org.springframework.core.type.AnnotatedTypeMetadata; 053import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; 054 055/** 056 * {@link EnableAutoConfiguration Auto-configuration} for {@link DataSource}. 057 * 058 * @author Dave Syer 059 * @author Phillip Webb 060 * @author Stephane Nicoll 061 * @author Kazuki Shimizu 062 */ 063@Configuration 064@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) 065@EnableConfigurationProperties(DataSourceProperties.class) 066@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class }) 067public class DataSourceAutoConfiguration { 068 069 private static final Log logger = LogFactory 070 .getLog(DataSourceAutoConfiguration.class); 071 072 @Bean 073 @ConditionalOnMissingBean 074 public DataSourceInitializer dataSourceInitializer(DataSourceProperties properties, 075 ApplicationContext applicationContext) { 076 return new DataSourceInitializer(properties, applicationContext); 077 } 078 079 /** 080 * Determines if the {@code dataSource} being used by Spring was created from 081 * {@link EmbeddedDataSourceConfiguration}. 082 * @param beanFactory the bean factory 083 * @return true if the data source was auto-configured. 084 */ 085 public static boolean containsAutoConfiguredDataSource( 086 ConfigurableListableBeanFactory beanFactory) { 087 try { 088 BeanDefinition beanDefinition = beanFactory.getBeanDefinition("dataSource"); 089 return EmbeddedDataSourceConfiguration.class.getName() 090 .equals(beanDefinition.getFactoryBeanName()); 091 } 092 catch (NoSuchBeanDefinitionException ex) { 093 return false; 094 } 095 } 096 097 @Configuration 098 @Conditional(EmbeddedDatabaseCondition.class) 099 @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) 100 @Import(EmbeddedDataSourceConfiguration.class) 101 protected static class EmbeddedDatabaseConfiguration { 102 103 } 104 105 @Configuration 106 @Conditional(PooledDataSourceCondition.class) 107 @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) 108 @Import({ DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Hikari.class, 109 DataSourceConfiguration.Dbcp.class, DataSourceConfiguration.Dbcp2.class, 110 DataSourceConfiguration.Generic.class }) 111 @SuppressWarnings("deprecation") 112 protected static class PooledDataSourceConfiguration { 113 114 } 115 116 @Configuration 117 @ConditionalOnProperty(prefix = "spring.datasource", name = "jmx-enabled") 118 @ConditionalOnClass(name = "org.apache.tomcat.jdbc.pool.DataSourceProxy") 119 @Conditional(DataSourceAutoConfiguration.DataSourceAvailableCondition.class) 120 @ConditionalOnMissingBean(name = "dataSourceMBean") 121 protected static class TomcatDataSourceJmxConfiguration { 122 123 @Bean 124 public Object dataSourceMBean(DataSource dataSource) { 125 if (dataSource instanceof DataSourceProxy) { 126 try { 127 return ((DataSourceProxy) dataSource).createPool().getJmxPool(); 128 } 129 catch (SQLException ex) { 130 logger.warn("Cannot expose DataSource to JMX (could not connect)"); 131 } 132 } 133 return null; 134 } 135 136 } 137 138 /** 139 * {@link AnyNestedCondition} that checks that either {@code spring.datasource.type} 140 * is set or {@link PooledDataSourceAvailableCondition} applies. 141 */ 142 static class PooledDataSourceCondition extends AnyNestedCondition { 143 144 PooledDataSourceCondition() { 145 super(ConfigurationPhase.PARSE_CONFIGURATION); 146 } 147 148 @ConditionalOnProperty(prefix = "spring.datasource", name = "type") 149 static class ExplicitType { 150 151 } 152 153 @Conditional(PooledDataSourceAvailableCondition.class) 154 static class PooledDataSourceAvailable { 155 156 } 157 158 } 159 160 /** 161 * {@link Condition} to test if a supported connection pool is available. 162 */ 163 static class PooledDataSourceAvailableCondition extends SpringBootCondition { 164 165 @Override 166 public ConditionOutcome getMatchOutcome(ConditionContext context, 167 AnnotatedTypeMetadata metadata) { 168 ConditionMessage.Builder message = ConditionMessage 169 .forCondition("PooledDataSource"); 170 if (getDataSourceClassLoader(context) != null) { 171 return ConditionOutcome 172 .match(message.foundExactly("supported DataSource")); 173 } 174 return ConditionOutcome 175 .noMatch(message.didNotFind("supported DataSource").atAll()); 176 } 177 178 /** 179 * Returns the class loader for the {@link DataSource} class. Used to ensure that 180 * the driver class can actually be loaded by the data source. 181 * @param context the condition context 182 * @return the class loader 183 */ 184 private ClassLoader getDataSourceClassLoader(ConditionContext context) { 185 Class<?> dataSourceClass = new DataSourceBuilder(context.getClassLoader()) 186 .findType(); 187 return (dataSourceClass == null ? null : dataSourceClass.getClassLoader()); 188 } 189 190 } 191 192 /** 193 * {@link Condition} to detect when an embedded {@link DataSource} type can be used. 194 * If a pooled {@link DataSource} is available, it will always be preferred to an 195 * {@code EmbeddedDatabase}. 196 */ 197 static class EmbeddedDatabaseCondition extends SpringBootCondition { 198 199 private final SpringBootCondition pooledCondition = new PooledDataSourceCondition(); 200 201 @Override 202 public ConditionOutcome getMatchOutcome(ConditionContext context, 203 AnnotatedTypeMetadata metadata) { 204 ConditionMessage.Builder message = ConditionMessage 205 .forCondition("EmbeddedDataSource"); 206 if (anyMatches(context, metadata, this.pooledCondition)) { 207 return ConditionOutcome 208 .noMatch(message.foundExactly("supported pooled data source")); 209 } 210 EmbeddedDatabaseType type = EmbeddedDatabaseConnection 211 .get(context.getClassLoader()).getType(); 212 if (type == null) { 213 return ConditionOutcome 214 .noMatch(message.didNotFind("embedded database").atAll()); 215 } 216 return ConditionOutcome.match(message.found("embedded database").items(type)); 217 } 218 219 } 220 221 /** 222 * {@link Condition} to detect when a {@link DataSource} is available (either because 223 * the user provided one or because one will be auto-configured). 224 */ 225 @Order(Ordered.LOWEST_PRECEDENCE - 10) 226 static class DataSourceAvailableCondition extends SpringBootCondition { 227 228 private final SpringBootCondition pooledCondition = new PooledDataSourceCondition(); 229 230 private final SpringBootCondition embeddedCondition = new EmbeddedDatabaseCondition(); 231 232 @Override 233 public ConditionOutcome getMatchOutcome(ConditionContext context, 234 AnnotatedTypeMetadata metadata) { 235 ConditionMessage.Builder message = ConditionMessage 236 .forCondition("DataSourceAvailable"); 237 if (hasBean(context, DataSource.class) 238 || hasBean(context, XADataSource.class)) { 239 return ConditionOutcome 240 .match(message.foundExactly("existing data source bean")); 241 } 242 if (anyMatches(context, metadata, this.pooledCondition, 243 this.embeddedCondition)) { 244 return ConditionOutcome.match(message 245 .foundExactly("existing auto-configured data source bean")); 246 } 247 return ConditionOutcome 248 .noMatch(message.didNotFind("any existing data source bean").atAll()); 249 } 250 251 private boolean hasBean(ConditionContext context, Class<?> type) { 252 return BeanFactoryUtils.beanNamesForTypeIncludingAncestors( 253 context.getBeanFactory(), type, true, false).length > 0; 254 } 255 256 } 257 258}