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