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.autoconfigure.liquibase; 018 019import java.lang.reflect.Method; 020 021import javax.annotation.PostConstruct; 022import javax.persistence.EntityManagerFactory; 023import javax.sql.DataSource; 024 025import liquibase.exception.LiquibaseException; 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.DataSourceBuilder; 038import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; 039import org.springframework.boot.context.properties.EnableConfigurationProperties; 040import org.springframework.context.annotation.Bean; 041import org.springframework.context.annotation.Configuration; 042import org.springframework.context.annotation.Import; 043import org.springframework.core.io.Resource; 044import org.springframework.core.io.ResourceLoader; 045import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; 046import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 047import org.springframework.util.Assert; 048import org.springframework.util.ReflectionUtils; 049 050/** 051 * {@link EnableAutoConfiguration Auto-configuration} for Liquibase. 052 * 053 * @author Marcel Overdijk 054 * @author Dave Syer 055 * @author Phillip Webb 056 * @author Eddú Meléndez 057 * @author Andy Wilkinson 058 * @since 1.1.0 059 */ 060@Configuration 061@ConditionalOnClass(SpringLiquibase.class) 062@ConditionalOnBean(DataSource.class) 063@ConditionalOnProperty(prefix = "liquibase", name = "enabled", matchIfMissing = true) 064@AutoConfigureAfter({ DataSourceAutoConfiguration.class, 065 HibernateJpaAutoConfiguration.class }) 066public class LiquibaseAutoConfiguration { 067 068 @Configuration 069 @ConditionalOnMissingBean(SpringLiquibase.class) 070 @EnableConfigurationProperties(LiquibaseProperties.class) 071 @Import(LiquibaseJpaDependencyConfiguration.class) 072 public static class LiquibaseConfiguration { 073 074 private final LiquibaseProperties properties; 075 076 private final ResourceLoader resourceLoader; 077 078 private final DataSource dataSource; 079 080 private final DataSource liquibaseDataSource; 081 082 public LiquibaseConfiguration(LiquibaseProperties properties, 083 ResourceLoader resourceLoader, ObjectProvider<DataSource> dataSource, 084 @LiquibaseDataSource ObjectProvider<DataSource> liquibaseDataSource) { 085 this.properties = properties; 086 this.resourceLoader = resourceLoader; 087 this.dataSource = dataSource.getIfUnique(); 088 this.liquibaseDataSource = liquibaseDataSource.getIfAvailable(); 089 } 090 091 @PostConstruct 092 public void checkChangelogExists() { 093 if (this.properties.isCheckChangeLogLocation()) { 094 Resource resource = this.resourceLoader 095 .getResource(this.properties.getChangeLog()); 096 Assert.state(resource.exists(), 097 "Cannot find changelog location: " + resource 098 + " (please add changelog or check your Liquibase " 099 + "configuration)"); 100 } 101 } 102 103 @Bean 104 public SpringLiquibase liquibase() { 105 SpringLiquibase liquibase = createSpringLiquibase(); 106 liquibase.setChangeLog(this.properties.getChangeLog()); 107 liquibase.setContexts(this.properties.getContexts()); 108 liquibase.setDefaultSchema(this.properties.getDefaultSchema()); 109 liquibase.setDropFirst(this.properties.isDropFirst()); 110 liquibase.setShouldRun(this.properties.isEnabled()); 111 liquibase.setLabels(this.properties.getLabels()); 112 liquibase.setChangeLogParameters(this.properties.getParameters()); 113 liquibase.setRollbackFile(this.properties.getRollbackFile()); 114 return liquibase; 115 } 116 117 private SpringLiquibase createSpringLiquibase() { 118 DataSource liquibaseDataSource = getDataSource(); 119 if (liquibaseDataSource != null) { 120 SpringLiquibase liquibase = new SpringLiquibase(); 121 liquibase.setDataSource(liquibaseDataSource); 122 return liquibase; 123 } 124 SpringLiquibase liquibase = new DataSourceClosingSpringLiquibase(); 125 liquibase.setDataSource(createNewDataSource()); 126 return liquibase; 127 } 128 129 private DataSource getDataSource() { 130 if (this.liquibaseDataSource != null) { 131 return this.liquibaseDataSource; 132 } 133 if (this.properties.getUrl() == null) { 134 return this.dataSource; 135 } 136 return null; 137 } 138 139 private DataSource createNewDataSource() { 140 return DataSourceBuilder.create().url(this.properties.getUrl()) 141 .username(this.properties.getUser()) 142 .password(this.properties.getPassword()).build(); 143 } 144 145 } 146 147 /** 148 * Additional configuration to ensure that {@link EntityManagerFactory} beans 149 * depend-on the liquibase bean. 150 */ 151 @Configuration 152 @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class) 153 @ConditionalOnBean(AbstractEntityManagerFactoryBean.class) 154 protected static class LiquibaseJpaDependencyConfiguration 155 extends EntityManagerFactoryDependsOnPostProcessor { 156 157 public LiquibaseJpaDependencyConfiguration() { 158 super("liquibase"); 159 } 160 161 } 162 163 /** 164 * A custom {@link SpringLiquibase} extension that closes the underlying 165 * {@link DataSource} once the database has been migrated. 166 */ 167 private static final class DataSourceClosingSpringLiquibase extends SpringLiquibase { 168 169 @Override 170 public void afterPropertiesSet() throws LiquibaseException { 171 super.afterPropertiesSet(); 172 closeDataSource(); 173 } 174 175 private void closeDataSource() { 176 Class<?> dataSourceClass = getDataSource().getClass(); 177 Method closeMethod = ReflectionUtils.findMethod(dataSourceClass, "close"); 178 if (closeMethod != null) { 179 ReflectionUtils.invokeMethod(closeMethod, getDataSource()); 180 } 181 } 182 183 } 184 185}