/* * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.boot.test.autoconfigure.jdbc; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** * Auto-configuration for a test database. * * @author Phillip Webb * @since 1.4.0 * @see AutoConfigureTestDatabase */ @Configuration @AutoConfigureBefore(DataSourceAutoConfiguration.class) public class TestDatabaseAutoConfiguration { private final Environment environment; TestDatabaseAutoConfiguration(Environment environment) { this.environment = environment; } @Bean @ConditionalOnProperty(prefix = "spring.test.database", name = "replace", havingValue = "AUTO_CONFIGURED") @ConditionalOnMissingBean public DataSource dataSource() { return new EmbeddedDataSourceFactory(this.environment).getEmbeddedDatabase(); } @Bean @ConditionalOnProperty(prefix = "spring.test.database", name = "replace", havingValue = "ANY", matchIfMissing = true) public static EmbeddedDataSourceBeanFactoryPostProcessor embeddedDataSourceBeanFactoryPostProcessor() { return new EmbeddedDataSourceBeanFactoryPostProcessor(); } @Order(Ordered.LOWEST_PRECEDENCE) private static class EmbeddedDataSourceBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor { private static final Log logger = LogFactory .getLog(EmbeddedDataSourceBeanFactoryPostProcessor.class); @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { Assert.isInstanceOf(ConfigurableListableBeanFactory.class, registry, "Test Database Auto-configuration can only be " + "used with a ConfigurableListableBeanFactory"); process(registry, (ConfigurableListableBeanFactory) registry); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } private void process(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory) { BeanDefinitionHolder holder = getDataSourceBeanDefinition(beanFactory); if (holder != null) { String beanName = holder.getBeanName(); boolean primary = holder.getBeanDefinition().isPrimary(); logger.info("Replacing '" + beanName + "' DataSource bean with " + (primary ? "primary " : "") + "embedded version"); registry.registerBeanDefinition(beanName, createEmbeddedBeanDefinition(primary)); } } private BeanDefinition createEmbeddedBeanDefinition(boolean primary) { BeanDefinition beanDefinition = new RootBeanDefinition( EmbeddedDataSourceFactoryBean.class); beanDefinition.setPrimary(primary); return beanDefinition; } private BeanDefinitionHolder getDataSourceBeanDefinition( ConfigurableListableBeanFactory beanFactory) { String[] beanNames = beanFactory.getBeanNamesForType(DataSource.class); if (ObjectUtils.isEmpty(beanNames)) { logger.warn("No DataSource beans found, " + "embedded version will not be used"); return null; } if (beanNames.length == 1) { String beanName = beanNames[0]; BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); return new BeanDefinitionHolder(beanDefinition, beanName); } for (String beanName : beanNames) { BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); if (beanDefinition.isPrimary()) { return new BeanDefinitionHolder(beanDefinition, beanName); } } logger.warn("No primary DataSource found, " + "embedded version will not be used"); return null; } } private static class EmbeddedDataSourceFactoryBean implements FactoryBean<DataSource>, EnvironmentAware, InitializingBean { private EmbeddedDataSourceFactory factory; private EmbeddedDatabase embeddedDatabase; @Override public void setEnvironment(Environment environment) { this.factory = new EmbeddedDataSourceFactory(environment); } @Override public void afterPropertiesSet() throws Exception { this.embeddedDatabase = this.factory.getEmbeddedDatabase(); } @Override public DataSource getObject() throws Exception { return this.embeddedDatabase; } @Override public Class<?> getObjectType() { return EmbeddedDatabase.class; } @Override public boolean isSingleton() { return true; } } private static class EmbeddedDataSourceFactory { private final Environment environment; EmbeddedDataSourceFactory(Environment environment) { this.environment = environment; } public EmbeddedDatabase getEmbeddedDatabase() { EmbeddedDatabaseConnection connection = this.environment.getProperty( "spring.test.database.connection", EmbeddedDatabaseConnection.class, EmbeddedDatabaseConnection.NONE); if (EmbeddedDatabaseConnection.NONE.equals(connection)) { connection = EmbeddedDatabaseConnection.get(getClass().getClassLoader()); } Assert.state(connection != EmbeddedDatabaseConnection.NONE, "Failed to replace DataSource with an embedded database for tests. If " + "you want an embedded database please put a supported one " + "on the classpath or tune the replace attribute of " + "@AutoconfigureTestDatabase."); return new EmbeddedDatabaseBuilder().generateUniqueName(true) .setType(connection.getType()).build(); } } }