Support location check with vendor placeholder
Update location check logic triggered if `flyway.check-location=true` to resolve any vendor placeholders in `flyway.locations`. See gh-10387
This commit is contained in:
		
							parent
							
								
									3be667cf7b
								
							
						
					
					
						commit
						1ee47cec31
					
				|  | @ -16,12 +16,12 @@ | |||
| 
 | ||||
| package org.springframework.boot.autoconfigure.flyway; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.HashSet; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| import javax.annotation.PostConstruct; | ||||
| import javax.persistence.EntityManagerFactory; | ||||
| import javax.sql.DataSource; | ||||
| 
 | ||||
|  | @ -104,6 +104,8 @@ public class FlywayAutoConfiguration { | |||
| 
 | ||||
| 		private List<FlywayCallback> flywayCallbacks; | ||||
| 
 | ||||
| 		private static final String VENDOR_PLACEHOLDER = "{vendor}"; | ||||
| 
 | ||||
| 		public FlywayConfiguration(FlywayProperties properties, | ||||
| 				ResourceLoader resourceLoader, ObjectProvider<DataSource> dataSource, | ||||
| 				@FlywayDataSource ObjectProvider<DataSource> flywayDataSource, | ||||
|  | @ -117,28 +119,6 @@ public class FlywayAutoConfiguration { | |||
| 			this.flywayCallbacks = flywayCallbacks.getIfAvailable(Collections::emptyList); | ||||
| 		} | ||||
| 
 | ||||
| 		@PostConstruct | ||||
| 		public void checkLocationExists() { | ||||
| 			if (this.properties.isCheckLocation()) { | ||||
| 				Assert.state(!this.properties.getLocations().isEmpty(), | ||||
| 						"Migration script locations not configured"); | ||||
| 				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 | ||||
| 		@ConfigurationProperties(prefix = "spring.flyway") | ||||
| 		public Flyway flyway() { | ||||
|  | @ -156,10 +136,61 @@ public class FlywayAutoConfiguration { | |||
| 			} | ||||
| 			flyway.setCallbacks(this.flywayCallbacks | ||||
| 					.toArray(new FlywayCallback[this.flywayCallbacks.size()])); | ||||
| 			flyway.setLocations(this.properties.getLocations().toArray(new String[0])); | ||||
| 			String[] locations = resolveLocations(this.properties.getLocations().toArray(new String[0]), flyway.getDataSource()); | ||||
| 			checkLocationExists(locations); | ||||
| 			flyway.setLocations(locations); | ||||
| 			return flyway; | ||||
| 		} | ||||
| 
 | ||||
| 		private static String[] resolveLocations(String[] locations, DataSource dataSource) { | ||||
| 			if (usesVendorLocation(locations)) { | ||||
| 				try { | ||||
| 					String url = (String) JdbcUtils | ||||
| 							.extractDatabaseMetaData(dataSource, "getURL"); | ||||
| 					DatabaseDriver vendor = DatabaseDriver.fromJdbcUrl(url); | ||||
| 					if (vendor != DatabaseDriver.UNKNOWN) { | ||||
| 						for (int i = 0; i < locations.length; i++) { | ||||
| 							locations[i] = locations[i].replace(VENDOR_PLACEHOLDER, | ||||
| 									vendor.getId()); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 				catch (MetaDataAccessException ex) { | ||||
| 					throw new IllegalStateException(ex); | ||||
| 				} | ||||
| 			} | ||||
| 			return locations; | ||||
| 		} | ||||
| 
 | ||||
| 		private static boolean usesVendorLocation(String... locations) { | ||||
| 			for (String location : locations) { | ||||
| 				if (location.contains(VENDOR_PLACEHOLDER)) { | ||||
| 					return true; | ||||
| 				} | ||||
| 			} | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		private void checkLocationExists(String... locations) { | ||||
| 			if (this.properties.isCheckLocation()) { | ||||
| 				Assert.state(locations.length != 0, | ||||
| 						"Migration script locations not configured"); | ||||
| 				boolean exists = hasAtLeastOneLocation(locations); | ||||
| 				Assert.state(exists, | ||||
| 						() -> "Cannot find migrations location in: " + Arrays.asList(locations) | ||||
| 								+ " (please add migrations or check your Flyway configuration)"); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		private boolean hasAtLeastOneLocation(String... locations) { | ||||
| 			for (String location : locations) { | ||||
| 				if (this.resourceLoader.getResource(location).exists()) { | ||||
| 					return true; | ||||
| 				} | ||||
| 			} | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 		@Bean | ||||
| 		@ConditionalOnMissingBean | ||||
| 		public FlywayMigrationInitializer flywayInitializer(Flyway flyway) { | ||||
|  | @ -202,38 +233,13 @@ public class FlywayAutoConfiguration { | |||
| 
 | ||||
| 	private static class SpringBootFlyway extends Flyway { | ||||
| 
 | ||||
| 		private static final String VENDOR_PLACEHOLDER = "{vendor}"; | ||||
| 
 | ||||
| 		@Override | ||||
| 		public void setLocations(String... locations) { | ||||
| 			if (usesVendorLocation(locations)) { | ||||
| 				try { | ||||
| 					String url = (String) JdbcUtils | ||||
| 							.extractDatabaseMetaData(getDataSource(), "getURL"); | ||||
| 					DatabaseDriver vendor = DatabaseDriver.fromJdbcUrl(url); | ||||
| 					if (vendor != DatabaseDriver.UNKNOWN) { | ||||
| 						for (int i = 0; i < locations.length; i++) { | ||||
| 							locations[i] = locations[i].replace(VENDOR_PLACEHOLDER, | ||||
| 									vendor.getId()); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 				catch (MetaDataAccessException ex) { | ||||
| 					throw new IllegalStateException(ex); | ||||
| 				} | ||||
| 			} | ||||
| 			locations = FlywayConfiguration.resolveLocations(locations, | ||||
| 					getDataSource()); | ||||
| 			super.setLocations(locations); | ||||
| 		} | ||||
| 
 | ||||
| 		private boolean usesVendorLocation(String... locations) { | ||||
| 			for (String location : locations) { | ||||
| 				if (location.contains(VENDOR_PLACEHOLDER)) { | ||||
| 					return true; | ||||
| 				} | ||||
| 			} | ||||
| 			return false; | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
|  |  | |||
|  | @ -255,6 +255,17 @@ public class FlywayAutoConfigurationTests { | |||
| 				}); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void useOneLocationWithVendorDirectory() { | ||||
| 		this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) | ||||
| 				.withPropertyValues("spring.flyway.locations=classpath:db/vendors/{vendor}") | ||||
| 				.run((context) -> { | ||||
| 					assertThat(context).hasSingleBean(Flyway.class); | ||||
| 					Flyway flyway = context.getBean(Flyway.class); | ||||
| 					assertThat(flyway.getLocations()).containsExactly("classpath:db/vendors/h2"); | ||||
| 				}); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void callbacksAreConfiguredAndOrdered() { | ||||
| 		this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue