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.devtools.autoconfigure; 018 019import java.util.Arrays; 020import java.util.HashSet; 021import java.util.Set; 022 023import javax.sql.DataSource; 024 025import org.springframework.beans.factory.DisposableBean; 026import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; 027import org.springframework.beans.factory.config.BeanDefinition; 028import org.springframework.boot.autoconfigure.AutoConfigureAfter; 029import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 030import org.springframework.boot.autoconfigure.condition.ConditionMessage; 031import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 032import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 033import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 034import org.springframework.boot.autoconfigure.condition.SpringBootCondition; 035import org.springframework.boot.autoconfigure.data.jpa.EntityManagerFactoryDependsOnPostProcessor; 036import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 037import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; 038import org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration.DevToolsDataSourceCondition; 039import org.springframework.context.annotation.Bean; 040import org.springframework.context.annotation.ConditionContext; 041import org.springframework.context.annotation.Conditional; 042import org.springframework.context.annotation.Configuration; 043import org.springframework.context.annotation.ConfigurationCondition; 044import org.springframework.core.type.AnnotatedTypeMetadata; 045import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; 046import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 047 048/** 049 * {@link EnableAutoConfiguration Auto-configuration} for DevTools-specific 050 * {@link DataSource} configuration. 051 * 052 * @author Andy Wilkinson 053 * @since 1.3.3 054 */ 055@AutoConfigureAfter(DataSourceAutoConfiguration.class) 056@Conditional(DevToolsDataSourceCondition.class) 057@Configuration 058public class DevToolsDataSourceAutoConfiguration { 059 060 @Bean 061 NonEmbeddedInMemoryDatabaseShutdownExecutor inMemoryDatabaseShutdownExecutor( 062 DataSource dataSource, DataSourceProperties dataSourceProperties) { 063 return new NonEmbeddedInMemoryDatabaseShutdownExecutor(dataSource, 064 dataSourceProperties); 065 } 066 067 /** 068 * Additional configuration to ensure that 069 * {@link javax.persistence.EntityManagerFactory} beans depend on the 070 * {@code inMemoryDatabaseShutdownExecutor} bean. 071 */ 072 @Configuration 073 @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class) 074 @ConditionalOnBean(AbstractEntityManagerFactoryBean.class) 075 static class DatabaseShutdownExecutorJpaDependencyConfiguration 076 extends EntityManagerFactoryDependsOnPostProcessor { 077 078 DatabaseShutdownExecutorJpaDependencyConfiguration() { 079 super("inMemoryDatabaseShutdownExecutor"); 080 } 081 082 } 083 084 static final class NonEmbeddedInMemoryDatabaseShutdownExecutor 085 implements DisposableBean { 086 087 private final DataSource dataSource; 088 089 private final DataSourceProperties dataSourceProperties; 090 091 NonEmbeddedInMemoryDatabaseShutdownExecutor(DataSource dataSource, 092 DataSourceProperties dataSourceProperties) { 093 this.dataSource = dataSource; 094 this.dataSourceProperties = dataSourceProperties; 095 } 096 097 @Override 098 public void destroy() throws Exception { 099 if (dataSourceRequiresShutdown()) { 100 this.dataSource.getConnection().createStatement().execute("SHUTDOWN"); 101 } 102 } 103 104 private boolean dataSourceRequiresShutdown() { 105 for (InMemoryDatabase inMemoryDatabase : InMemoryDatabase.values()) { 106 if (inMemoryDatabase.matches(this.dataSourceProperties)) { 107 return true; 108 } 109 } 110 return false; 111 } 112 113 private enum InMemoryDatabase { 114 115 DERBY(null, "org.apache.derby.jdbc.EmbeddedDriver"), 116 117 H2("jdbc:h2:mem:", "org.h2.Driver", "org.h2.jdbcx.JdbcDataSource"), 118 119 HSQLDB("jdbc:hsqldb:mem:", "org.hsqldb.jdbcDriver", 120 "org.hsqldb.jdbc.JDBCDriver", 121 "org.hsqldb.jdbc.pool.JDBCXADataSource"); 122 123 private final String urlPrefix; 124 125 private final Set<String> driverClassNames; 126 127 InMemoryDatabase(String urlPrefix, String... driverClassNames) { 128 this.urlPrefix = urlPrefix; 129 this.driverClassNames = new HashSet<String>( 130 Arrays.asList(driverClassNames)); 131 } 132 133 boolean matches(DataSourceProperties properties) { 134 String url = properties.getUrl(); 135 return (url == null || this.urlPrefix == null 136 || url.startsWith(this.urlPrefix)) 137 && this.driverClassNames 138 .contains(properties.determineDriverClassName()); 139 } 140 141 } 142 143 } 144 145 static class DevToolsDataSourceCondition extends SpringBootCondition 146 implements ConfigurationCondition { 147 148 @Override 149 public ConfigurationPhase getConfigurationPhase() { 150 return ConfigurationPhase.REGISTER_BEAN; 151 } 152 153 @Override 154 public ConditionOutcome getMatchOutcome(ConditionContext context, 155 AnnotatedTypeMetadata metadata) { 156 ConditionMessage.Builder message = ConditionMessage 157 .forCondition("DevTools DataSource Condition"); 158 String[] dataSourceBeanNames = context.getBeanFactory() 159 .getBeanNamesForType(DataSource.class); 160 if (dataSourceBeanNames.length != 1) { 161 return ConditionOutcome 162 .noMatch(message.didNotFind("a single DataSource bean").atAll()); 163 } 164 if (context.getBeanFactory() 165 .getBeanNamesForType(DataSourceProperties.class).length != 1) { 166 return ConditionOutcome.noMatch( 167 message.didNotFind("a single DataSourceProperties bean").atAll()); 168 } 169 BeanDefinition dataSourceDefinition = context.getRegistry() 170 .getBeanDefinition(dataSourceBeanNames[0]); 171 if (dataSourceDefinition instanceof AnnotatedBeanDefinition 172 && ((AnnotatedBeanDefinition) dataSourceDefinition) 173 .getFactoryMethodMetadata() != null 174 && ((AnnotatedBeanDefinition) dataSourceDefinition) 175 .getFactoryMethodMetadata().getDeclaringClassName() 176 .startsWith(DataSourceAutoConfiguration.class.getPackage() 177 .getName() + ".DataSourceConfiguration$")) { 178 return ConditionOutcome 179 .match(message.foundExactly("auto-configured DataSource")); 180 } 181 return ConditionOutcome 182 .noMatch(message.didNotFind("an auto-configured DataSource").atAll()); 183 } 184 185 } 186 187}