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.autoconfigure.liquibase;
018
019import java.util.function.Supplier;
020
021import javax.annotation.PostConstruct;
022import javax.persistence.EntityManagerFactory;
023import javax.sql.DataSource;
024
025import liquibase.change.DatabaseChange;
026import liquibase.integration.spring.SpringLiquibase;
027
028import org.springframework.beans.factory.ObjectProvider;
029import org.springframework.boot.autoconfigure.AutoConfigureAfter;
030import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
031import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
032import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
033import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
034import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.autoconfigure.jdbc.JdbcOperationsDependsOnPostProcessor;
039import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
040import org.springframework.boot.context.properties.EnableConfigurationProperties;
041import org.springframework.boot.jdbc.DataSourceBuilder;
042import org.springframework.context.annotation.Bean;
043import org.springframework.context.annotation.Configuration;
044import org.springframework.context.annotation.Import;
045import org.springframework.core.io.Resource;
046import org.springframework.core.io.ResourceLoader;
047import org.springframework.jdbc.core.JdbcOperations;
048import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
049import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
050import org.springframework.util.Assert;
051
052/**
053 * {@link EnableAutoConfiguration Auto-configuration} for Liquibase.
054 *
055 * @author Marcel Overdijk
056 * @author Dave Syer
057 * @author Phillip Webb
058 * @author EddĂș MelĂ©ndez
059 * @author Andy Wilkinson
060 * @author Dominic Gunn
061 * @since 1.1.0
062 */
063@Configuration
064@ConditionalOnClass({ SpringLiquibase.class, DatabaseChange.class })
065@ConditionalOnBean(DataSource.class)
066@ConditionalOnProperty(prefix = "spring.liquibase", name = "enabled", matchIfMissing = true)
067@AutoConfigureAfter({ DataSourceAutoConfiguration.class,
068                HibernateJpaAutoConfiguration.class })
069public class LiquibaseAutoConfiguration {
070
071        @Bean
072        public LiquibaseSchemaManagementProvider liquibaseDefaultDdlModeProvider(
073                        ObjectProvider<SpringLiquibase> liquibases) {
074                return new LiquibaseSchemaManagementProvider(liquibases);
075        }
076
077        @Configuration
078        @ConditionalOnMissingBean(SpringLiquibase.class)
079        @EnableConfigurationProperties({ DataSourceProperties.class,
080                        LiquibaseProperties.class })
081        @Import(LiquibaseJpaDependencyConfiguration.class)
082        public static class LiquibaseConfiguration {
083
084                private final LiquibaseProperties properties;
085
086                private final DataSourceProperties dataSourceProperties;
087
088                private final ResourceLoader resourceLoader;
089
090                private final DataSource dataSource;
091
092                private final DataSource liquibaseDataSource;
093
094                public LiquibaseConfiguration(LiquibaseProperties properties,
095                                DataSourceProperties dataSourceProperties, ResourceLoader resourceLoader,
096                                ObjectProvider<DataSource> dataSource,
097                                @LiquibaseDataSource ObjectProvider<DataSource> liquibaseDataSource) {
098                        this.properties = properties;
099                        this.dataSourceProperties = dataSourceProperties;
100                        this.resourceLoader = resourceLoader;
101                        this.dataSource = dataSource.getIfUnique();
102                        this.liquibaseDataSource = liquibaseDataSource.getIfAvailable();
103                }
104
105                @PostConstruct
106                public void checkChangelogExists() {
107                        if (this.properties.isCheckChangeLogLocation()) {
108                                Resource resource = this.resourceLoader
109                                                .getResource(this.properties.getChangeLog());
110                                Assert.state(resource.exists(),
111                                                () -> "Cannot find changelog location: " + resource
112                                                                + " (please add changelog or check your Liquibase "
113                                                                + "configuration)");
114                        }
115                }
116
117                @Bean
118                public SpringLiquibase liquibase() {
119                        SpringLiquibase liquibase = createSpringLiquibase();
120                        liquibase.setChangeLog(this.properties.getChangeLog());
121                        liquibase.setContexts(this.properties.getContexts());
122                        liquibase.setDefaultSchema(this.properties.getDefaultSchema());
123                        liquibase.setLiquibaseSchema(this.properties.getLiquibaseSchema());
124                        liquibase.setLiquibaseTablespace(this.properties.getLiquibaseTablespace());
125                        liquibase.setDatabaseChangeLogTable(
126                                        this.properties.getDatabaseChangeLogTable());
127                        liquibase.setDatabaseChangeLogLockTable(
128                                        this.properties.getDatabaseChangeLogLockTable());
129                        liquibase.setDropFirst(this.properties.isDropFirst());
130                        liquibase.setShouldRun(this.properties.isEnabled());
131                        liquibase.setLabels(this.properties.getLabels());
132                        liquibase.setChangeLogParameters(this.properties.getParameters());
133                        liquibase.setRollbackFile(this.properties.getRollbackFile());
134                        liquibase.setTestRollbackOnUpdate(this.properties.isTestRollbackOnUpdate());
135                        return liquibase;
136                }
137
138                private SpringLiquibase createSpringLiquibase() {
139                        DataSource liquibaseDataSource = getDataSource();
140                        if (liquibaseDataSource != null) {
141                                SpringLiquibase liquibase = new SpringLiquibase();
142                                liquibase.setDataSource(liquibaseDataSource);
143                                return liquibase;
144                        }
145                        SpringLiquibase liquibase = new DataSourceClosingSpringLiquibase();
146                        liquibase.setDataSource(createNewDataSource());
147                        return liquibase;
148                }
149
150                private DataSource getDataSource() {
151                        if (this.liquibaseDataSource != null) {
152                                return this.liquibaseDataSource;
153                        }
154                        if (this.properties.getUrl() == null && this.properties.getUser() == null) {
155                                return this.dataSource;
156                        }
157                        return null;
158                }
159
160                private DataSource createNewDataSource() {
161                        String url = getProperty(this.properties::getUrl,
162                                        this.dataSourceProperties::getUrl);
163                        String user = getProperty(this.properties::getUser,
164                                        this.dataSourceProperties::getUsername);
165                        String password = getProperty(this.properties::getPassword,
166                                        this.dataSourceProperties::getPassword);
167                        return DataSourceBuilder.create().url(url).username(user).password(password)
168                                        .build();
169                }
170
171                private String getProperty(Supplier<String> property,
172                                Supplier<String> defaultValue) {
173                        String value = property.get();
174                        return (value != null) ? value : defaultValue.get();
175                }
176
177        }
178
179        /**
180         * Additional configuration to ensure that {@link EntityManagerFactory} beans depend
181         * on the liquibase bean.
182         */
183        @Configuration
184        @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
185        @ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
186        protected static class LiquibaseJpaDependencyConfiguration
187                        extends EntityManagerFactoryDependsOnPostProcessor {
188
189                public LiquibaseJpaDependencyConfiguration() {
190                        super("liquibase");
191                }
192
193        }
194
195        /**
196         * Additional configuration to ensure that {@link JdbcOperations} beans depend on the
197         * liquibase bean.
198         */
199        @Configuration
200        @ConditionalOnClass(JdbcOperations.class)
201        @ConditionalOnBean(JdbcOperations.class)
202        protected static class LiquibaseJdbcOperationsDependencyConfiguration
203                        extends JdbcOperationsDependsOnPostProcessor {
204
205                public LiquibaseJdbcOperationsDependencyConfiguration() {
206                        super("liquibase");
207                }
208
209        }
210
211}