From 93aefa8537b3387f1fa0c58983e7cd921e6e8b23 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 6 Jun 2014 22:28:49 -0700 Subject: [PATCH] Polish Flyway and Liquibase Extract common "depends on" functionality to a new EntityManagerFactoryDependsOnPostProcessor class. Apply consistent formatting. Fix issue with Flyway location detection. --- ...yManagerFactoryDependsOnPostProcessor.java | 91 +++++++++++++++++++ .../flyway/FlywayAutoConfiguration.java | 86 ++++-------------- .../liquibase/LiquibaseAutoConfiguration.java | 73 +++------------ .../flyway/FlywayAutoConfigurationTests.java | 88 ++++++++++-------- 4 files changed, 173 insertions(+), 165 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/EntityManagerFactoryDependsOnPostProcessor.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/EntityManagerFactoryDependsOnPostProcessor.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/EntityManagerFactoryDependsOnPostProcessor.java new file mode 100644 index 0000000000..1e085c159e --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/EntityManagerFactoryDependsOnPostProcessor.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.data.jpa; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.EntityManagerFactory; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; +import org.springframework.util.StringUtils; + +/** + * {@link BeanFactoryPostProcessor} that can be used to dynamically declare that all + * {@link EntityManagerFactory} beans should "depend on" a specific bean. + * + * @author Marcel Overdijk + * @author Dave Syer + * @author Phillip Webb + * @since 1.1.0 + * @see BeanDefinition#setDependsOn(String[]) + */ +public class EntityManagerFactoryDependsOnPostProcessor implements + BeanFactoryPostProcessor { + + private final String dependsOn; + + public EntityManagerFactoryDependsOnPostProcessor(String dependsOn) { + this.dependsOn = dependsOn; + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + for (String beanName : getEntityManagerFactoryBeanNames(beanFactory)) { + BeanDefinition definition = getBeanDefinition(beanName, beanFactory); + definition.setDependsOn(StringUtils.addStringToArray( + definition.getDependsOn(), this.dependsOn)); + } + } + + private static BeanDefinition getBeanDefinition(String beanName, + ConfigurableListableBeanFactory beanFactory) { + try { + return beanFactory.getBeanDefinition(beanName); + } + catch (NoSuchBeanDefinitionException ex) { + BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory(); + if (parentBeanFactory instanceof ConfigurableListableBeanFactory) { + return getBeanDefinition(beanName, + (ConfigurableListableBeanFactory) parentBeanFactory); + } + throw ex; + } + } + + private Iterable getEntityManagerFactoryBeanNames( + ListableBeanFactory beanFactory) { + Set names = new HashSet(); + names.addAll(Arrays.asList(BeanFactoryUtils.beanNamesForTypeIncludingAncestors( + beanFactory, EntityManagerFactory.class, true, false))); + for (String factoryBeanName : BeanFactoryUtils + .beanNamesForTypeIncludingAncestors(beanFactory, + AbstractEntityManagerFactoryBean.class, true, false)) { + names.add(BeanFactoryUtils.transformedBeanName(factoryBeanName)); + } + return names; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java index c3fa2442d4..e45561cdf0 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java @@ -16,27 +16,19 @@ package org.springframework.boot.autoconfigure.flyway; -import java.util.HashSet; -import java.util.Set; - import javax.annotation.PostConstruct; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import org.flywaydb.core.Flyway; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.data.jpa.EntityManagerFactoryDependsOnPostProcessor; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -44,21 +36,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -import static java.util.Arrays.asList; -import static org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors; -import static org.springframework.beans.factory.BeanFactoryUtils.transformedBeanName; /** * {@link EnableAutoConfiguration Auto-configuration} for Flyway database migrations. * * @author Dave Syer + * @author Phillip Webb * @since 1.1.0 */ @Configuration @@ -90,20 +77,24 @@ public class FlywayAutoConfiguration { @PostConstruct public void checkLocationExists() { if (this.properties.isCheckLocation()) { - Assert.state(!this.properties.getLocations().isEmpty(), "Migration script locations not configured"); - boolean exists = false; - for (String location : this.properties.getLocations()) { - Resource resource = this.resourceLoader.getResource(location); - exists = (!exists && resource.exists()); - } + boolean exists = hasAtLeastOneLocation(); Assert.state(exists, "Cannot find migrations location in: " + this.properties.getLocations() + " (please add migrations or check your Flyway configuration)"); } } + private boolean hasAtLeastOneLocation() { + for (String location : this.properties.getLocations()) { + if (this.resourceLoader.getResource(location).exists()) { + return true; + } + } + return false; + } + @Bean(initMethod = "migrate") @ConfigurationProperties(prefix = "flyway") public Flyway flyway() { @@ -124,55 +115,18 @@ public class FlywayAutoConfiguration { } + /** + * Additional configuration to ensure that {@link EntityManagerFactory} beans + * depend-on the liquibase bean. + */ @Configuration @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class) @ConditionalOnBean(AbstractEntityManagerFactoryBean.class) - protected static class FlywayJpaDependencyConfiguration implements - BeanFactoryPostProcessor { - - public static final String FLYWAY_JPA_BEAN_NAME = "flyway"; - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { - - for (String beanName : getEntityManagerFactoryBeanNames(beanFactory)) { - BeanDefinition definition = getBeanDefinition(beanName, beanFactory); - definition.setDependsOn(StringUtils.addStringToArray( - definition.getDependsOn(), FLYWAY_JPA_BEAN_NAME)); - } - } - - private static BeanDefinition getBeanDefinition(String beanName, - ConfigurableListableBeanFactory beanFactory) { - try { - return beanFactory.getBeanDefinition(beanName); - } - catch (NoSuchBeanDefinitionException e) { - - BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory(); - - if (parentBeanFactory instanceof ConfigurableListableBeanFactory) { - return getBeanDefinition(beanName, - (ConfigurableListableBeanFactory) parentBeanFactory); - } - - throw e; - } - } - - private static Iterable getEntityManagerFactoryBeanNames( - ListableBeanFactory beanFactory) { - - Set names = new HashSet(); - names.addAll(asList(beanNamesForTypeIncludingAncestors(beanFactory, - EntityManagerFactory.class, true, false))); - - for (String factoryBeanName : beanNamesForTypeIncludingAncestors(beanFactory, - AbstractEntityManagerFactoryBean.class, true, false)) { - names.add(transformedBeanName(factoryBeanName)); - } + protected static class FlywayJpaDependencyConfiguration extends + EntityManagerFactoryDependsOnPostProcessor { - return names; + public FlywayJpaDependencyConfiguration() { + super("flyway"); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java index 596916662d..4f09b7a55a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java @@ -16,28 +16,20 @@ package org.springframework.boot.autoconfigure.liquibase; -import java.util.HashSet; -import java.util.Set; - import javax.annotation.PostConstruct; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import liquibase.integration.spring.SpringLiquibase; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.data.jpa.EntityManagerFactoryDependsOnPostProcessor; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -49,17 +41,13 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -import static java.util.Arrays.asList; -import static org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors; -import static org.springframework.beans.factory.BeanFactoryUtils.transformedBeanName; /** * {@link EnableAutoConfiguration Auto-configuration} for Liquibase. * * @author Marcel Overdijk * @author Dave Syer + * @author Phillip Webb * @since 1.1.0 */ @Configuration @@ -90,8 +78,8 @@ public class LiquibaseAutoConfiguration { Resource resource = this.resourceLoader.getResource(this.properties .getChangeLog()); Assert.state(resource.exists(), "Cannot find changelog location: " - + resource - + " (please add changelog or check your Liquibase configuration)"); + + resource + " (please add changelog or check your Liquibase " + + "configuration)"); } } @@ -108,55 +96,18 @@ public class LiquibaseAutoConfiguration { } } + /** + * Additional configuration to ensure that {@link EntityManagerFactory} beans + * depend-on the liquibase bean. + */ @Configuration @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class) @ConditionalOnBean(AbstractEntityManagerFactoryBean.class) - protected static class LiquibaseJpaDependencyConfiguration implements - BeanFactoryPostProcessor { - - public static final String LIQUIBASE_JPA_BEAN_NAME = "liquibase"; - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { - - for (String beanName : getEntityManagerFactoryBeanNames(beanFactory)) { - BeanDefinition definition = getBeanDefinition(beanName, beanFactory); - definition.setDependsOn(StringUtils.addStringToArray( - definition.getDependsOn(), LIQUIBASE_JPA_BEAN_NAME)); - } - } - - private static BeanDefinition getBeanDefinition(String beanName, - ConfigurableListableBeanFactory beanFactory) { - try { - return beanFactory.getBeanDefinition(beanName); - } - catch (NoSuchBeanDefinitionException e) { - - BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory(); - - if (parentBeanFactory instanceof ConfigurableListableBeanFactory) { - return getBeanDefinition(beanName, - (ConfigurableListableBeanFactory) parentBeanFactory); - } - - throw e; - } - } - - private static Iterable getEntityManagerFactoryBeanNames( - ListableBeanFactory beanFactory) { - - Set names = new HashSet(); - names.addAll(asList(beanNamesForTypeIncludingAncestors(beanFactory, - EntityManagerFactory.class, true, false))); - - for (String factoryBeanName : beanNamesForTypeIncludingAncestors(beanFactory, - AbstractEntityManagerFactoryBean.class, true, false)) { - names.add(transformedBeanName(factoryBeanName)); - } + protected static class LiquibaseJpaDependencyConfiguration extends + EntityManagerFactoryDependsOnPostProcessor { - return names; + public LiquibaseJpaDependencyConfiguration() { + super("liquibase"); } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index 771ee1ff29..8b914345a9 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -42,11 +42,12 @@ import static org.junit.Assert.assertNotNull; * Tests for {@link FlywayAutoConfiguration}. * * @author Dave Syer + * @author Phillip Webb */ public class FlywayAutoConfigurationTests { @Rule - public ExpectedException expected = ExpectedException.none(); + public ExpectedException thrown = ExpectedException.none(); private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();; @@ -64,82 +65,93 @@ public class FlywayAutoConfigurationTests { } @Test - public void testNoDataSource() throws Exception { - this.context.register(FlywayAutoConfiguration.class, + public void noDataSource() throws Exception { + registerAndRefresh(FlywayAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); assertEquals(0, this.context.getBeanNamesForType(Flyway.class).length); } @Test - public void testCreateDataSource() throws Exception { + public void createDataSource() throws Exception { EnvironmentTestUtils.addEnvironment(this.context, "flyway.url:jdbc:hsqldb:mem:flywaytest", "flyway.user:sa"); - this.context - .register(EmbeddedDataSourceConfiguration.class, - FlywayAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); + registerAndRefresh(EmbeddedDataSourceConfiguration.class, + FlywayAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); Flyway flyway = this.context.getBean(Flyway.class); assertNotNull(flyway.getDataSource()); } @Test - public void testFlywayDataSource() throws Exception { - this.context.register(FlywayDataSourceConfiguration.class, + public void flywayDataSource() throws Exception { + registerAndRefresh(FlywayDataSourceConfiguration.class, EmbeddedDataSourceConfiguration.class, FlywayAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); Flyway flyway = this.context.getBean(Flyway.class); assertNotNull(flyway.getDataSource()); } @Test - public void testDefaultFlyway() throws Exception { - this.context - .register(EmbeddedDataSourceConfiguration.class, - FlywayAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); + public void defaultFlyway() throws Exception { + registerAndRefresh(EmbeddedDataSourceConfiguration.class, + FlywayAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); Flyway flyway = this.context.getBean(Flyway.class); assertEquals("[classpath:db/migration]", Arrays.asList(flyway.getLocations()) .toString()); } @Test - public void testOverrideLocations() throws Exception { + public void overrideLocations() throws Exception { EnvironmentTestUtils.addEnvironment(this.context, "flyway.locations:classpath:db/changelog,classpath:db/migration"); - this.context - .register(EmbeddedDataSourceConfiguration.class, - FlywayAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); + registerAndRefresh(EmbeddedDataSourceConfiguration.class, + FlywayAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); Flyway flyway = this.context.getBean(Flyway.class); assertEquals("[classpath:db/changelog, classpath:db/migration]", Arrays.asList(flyway.getLocations()).toString()); } @Test - public void testOverrideSchemas() throws Exception { + public void overrideSchemas() throws Exception { EnvironmentTestUtils.addEnvironment(this.context, "flyway.schemas:public"); - this.context - .register(EmbeddedDataSourceConfiguration.class, - FlywayAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); - this.context.refresh(); + registerAndRefresh(EmbeddedDataSourceConfiguration.class, + FlywayAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); Flyway flyway = this.context.getBean(Flyway.class); assertEquals("[public]", Arrays.asList(flyway.getSchemas()).toString()); } - @Test(expected = BeanCreationException.class) - public void testChangeLogDoesNotExist() throws Exception { - EnvironmentTestUtils.addEnvironment(this.context, "flyway.locations:no-such-dir"); - this.context - .register(EmbeddedDataSourceConfiguration.class, - FlywayAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class); + @Test + public void changeLogDoesNotExist() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "flyway.locations:file:no-such-dir"); + this.thrown.expect(BeanCreationException.class); + registerAndRefresh(EmbeddedDataSourceConfiguration.class, + FlywayAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); + } + + @Test + public void checkLocationsAllMissing() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "flyway.locations:classpath:db/missing1,classpath:db/migration2", + "flyway.check-location:true"); + this.thrown.expect(BeanCreationException.class); + this.thrown.expectMessage("Cannot find migrations location in"); + registerAndRefresh(EmbeddedDataSourceConfiguration.class, + FlywayAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); + } + + @Test + public void checkLocationsAllExist() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "flyway.locations:classpath:db/changelog,classpath:db/migration", + "flyway.check-location:true"); + registerAndRefresh(EmbeddedDataSourceConfiguration.class, + FlywayAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); + } + + private void registerAndRefresh(Class... annotatedClasses) { + this.context.register(annotatedClasses); this.context.refresh(); + } @Configuration