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}