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}