From cf4117856b1656c24753aae045300f8a3a7c80c5 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Wed, 26 Oct 2022 15:05:58 +0200 Subject: [PATCH] Extract Flyway configuration mapping in separate class Closes gh-32849 --- .../flyway/FlywayAutoConfiguration.java | 133 +------------ .../flyway/FlywayPropertiesMapper.java | 174 ++++++++++++++++++ 2 files changed, 175 insertions(+), 132 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesMapper.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java index 5e40517196..9d202356c8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java @@ -16,13 +16,9 @@ package org.springframework.boot.autoconfigure.flyway; -import java.sql.DatabaseMetaData; -import java.time.Duration; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import javax.sql.DataSource; @@ -32,7 +28,6 @@ import org.flywaydb.core.api.MigrationVersion; import org.flywaydb.core.api.callback.Callback; import org.flywaydb.core.api.configuration.FluentConfiguration; import org.flywaydb.core.api.migration.JavaMigration; -import org.flywaydb.database.sqlserver.SQLServerConfigurationExtension; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -51,9 +46,7 @@ import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.jdbc.DataSourceBuilder; -import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; @@ -65,9 +58,7 @@ import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.io.ResourceLoader; import org.springframework.jdbc.datasource.SimpleDriverDataSource; import org.springframework.jdbc.support.JdbcUtils; -import org.springframework.jdbc.support.MetaDataAccessException; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -171,81 +162,7 @@ public class FlywayAutoConfiguration { } private void configureProperties(FluentConfiguration configuration, FlywayProperties properties) { - PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); - String[] locations = new LocationResolver(configuration.getDataSource()) - .resolveLocations(properties.getLocations()).toArray(new String[0]); - map.from(properties.isFailOnMissingLocations()).to(configuration::failOnMissingLocations); - map.from(locations).to(configuration::locations); - map.from(properties.getEncoding()).to(configuration::encoding); - map.from(properties.getConnectRetries()).to(configuration::connectRetries); - map.from(properties.getConnectRetriesInterval()).as(Duration::getSeconds).as(Long::intValue) - .to(configuration::connectRetriesInterval); - map.from(properties.getLockRetryCount()).to(configuration::lockRetryCount); - map.from(properties.getDefaultSchema()).to(configuration::defaultSchema); - map.from(properties.getSchemas()).as(StringUtils::toStringArray).to(configuration::schemas); - map.from(properties.isCreateSchemas()).to(configuration::createSchemas); - map.from(properties.getTable()).to(configuration::table); - map.from(properties.getTablespace()).to(configuration::tablespace); - map.from(properties.getBaselineDescription()).to(configuration::baselineDescription); - map.from(properties.getBaselineVersion()).to(configuration::baselineVersion); - map.from(properties.getInstalledBy()).to(configuration::installedBy); - map.from(properties.getPlaceholders()).to(configuration::placeholders); - map.from(properties.getPlaceholderPrefix()).to(configuration::placeholderPrefix); - map.from(properties.getPlaceholderSuffix()).to(configuration::placeholderSuffix); - map.from(properties.getPlaceholderSeparator()).to(configuration::placeholderSeparator); - map.from(properties.isPlaceholderReplacement()).to(configuration::placeholderReplacement); - map.from(properties.getSqlMigrationPrefix()).to(configuration::sqlMigrationPrefix); - map.from(properties.getSqlMigrationSuffixes()).as(StringUtils::toStringArray) - .to(configuration::sqlMigrationSuffixes); - map.from(properties.getSqlMigrationSeparator()).to(configuration::sqlMigrationSeparator); - map.from(properties.getRepeatableSqlMigrationPrefix()).to(configuration::repeatableSqlMigrationPrefix); - map.from(properties.getTarget()).to(configuration::target); - map.from(properties.isBaselineOnMigrate()).to(configuration::baselineOnMigrate); - map.from(properties.isCleanDisabled()).to(configuration::cleanDisabled); - map.from(properties.isCleanOnValidationError()).to(configuration::cleanOnValidationError); - map.from(properties.isGroup()).to(configuration::group); - map.from(properties.isMixed()).to(configuration::mixed); - map.from(properties.isOutOfOrder()).to(configuration::outOfOrder); - map.from(properties.isSkipDefaultCallbacks()).to(configuration::skipDefaultCallbacks); - map.from(properties.isSkipDefaultResolvers()).to(configuration::skipDefaultResolvers); - map.from(properties.isValidateMigrationNaming()).to(configuration::validateMigrationNaming); - map.from(properties.isValidateOnMigrate()).to(configuration::validateOnMigrate); - map.from(properties.getInitSqls()).whenNot(CollectionUtils::isEmpty) - .as((initSqls) -> StringUtils.collectionToDelimitedString(initSqls, "\n")) - .to(configuration::initSql); - map.from(properties.getScriptPlaceholderPrefix()) - .to((prefix) -> configuration.scriptPlaceholderPrefix(prefix)); - map.from(properties.getScriptPlaceholderSuffix()) - .to((suffix) -> configuration.scriptPlaceholderSuffix(suffix)); - // Flyway Teams properties - map.from(properties.getBatch()).to(configuration::batch); - map.from(properties.getDryRunOutput()).to(configuration::dryRunOutput); - map.from(properties.getErrorOverrides()).to(configuration::errorOverrides); - map.from(properties.getLicenseKey()).to(configuration::licenseKey); - map.from(properties.getOracleSqlplus()).to(configuration::oracleSqlplus); - map.from(properties.getOracleSqlplusWarn()).to(configuration::oracleSqlplusWarn); - map.from(properties.getStream()).to(configuration::stream); - map.from(properties.getUndoSqlMigrationPrefix()).to(configuration::undoSqlMigrationPrefix); - map.from(properties.getCherryPick()).to(configuration::cherryPick); - map.from(properties.getJdbcProperties()).whenNot(Map::isEmpty).to(configuration::jdbcProperties); - map.from(properties.getKerberosConfigFile()).to(configuration::kerberosConfigFile); - map.from(properties.getOracleKerberosCacheFile()).to(configuration::oracleKerberosCacheFile); - map.from(properties.getOutputQueryResults()).to(configuration::outputQueryResults); - map.from(properties.getSqlServerKerberosLoginFile()).whenNonNull() - .to((sqlServerKerberosLoginFile) -> configureSqlServerKerberosLoginFile(configuration, - sqlServerKerberosLoginFile)); - map.from(properties.getSkipExecutingMigrations()).to(configuration::skipExecutingMigrations); - map.from(properties.getIgnoreMigrationPatterns()).whenNot(List::isEmpty) - .as((patterns) -> patterns.toArray(new String[0])).to(configuration::ignoreMigrationPatterns); - map.from(properties.getDetectEncoding()).to(configuration::detectEncoding); - } - - private void configureSqlServerKerberosLoginFile(FluentConfiguration configuration, - String sqlServerKerberosLoginFile) { - SQLServerConfigurationExtension sqlServerConfigurationExtension = configuration.getPluginRegister() - .getPlugin(SQLServerConfigurationExtension.class); - Assert.state(sqlServerConfigurationExtension != null, "Flyway SQL Server extension missing"); - sqlServerConfigurationExtension.setKerberosLoginFile(sqlServerKerberosLoginFile); + new FlywayPropertiesMapper().map(properties, configuration); } private void configureCallbacks(FluentConfiguration configuration, List callbacks) { @@ -269,54 +186,6 @@ public class FlywayAutoConfiguration { } - private static class LocationResolver { - - private static final String VENDOR_PLACEHOLDER = "{vendor}"; - - private final DataSource dataSource; - - LocationResolver(DataSource dataSource) { - this.dataSource = dataSource; - } - - List resolveLocations(List locations) { - if (usesVendorLocation(locations)) { - DatabaseDriver databaseDriver = getDatabaseDriver(); - return replaceVendorLocations(locations, databaseDriver); - } - return locations; - } - - private List replaceVendorLocations(List locations, DatabaseDriver databaseDriver) { - if (databaseDriver == DatabaseDriver.UNKNOWN) { - return locations; - } - String vendor = databaseDriver.getId(); - return locations.stream().map((location) -> location.replace(VENDOR_PLACEHOLDER, vendor)).toList(); - } - - private DatabaseDriver getDatabaseDriver() { - try { - String url = JdbcUtils.extractDatabaseMetaData(this.dataSource, DatabaseMetaData::getURL); - return DatabaseDriver.fromJdbcUrl(url); - } - catch (MetaDataAccessException ex) { - throw new IllegalStateException(ex); - } - - } - - private boolean usesVendorLocation(Collection locations) { - for (String location : locations) { - if (location.contains(VENDOR_PLACEHOLDER)) { - return true; - } - } - return false; - } - - } - /** * Convert a String or Number to a {@link MigrationVersion}. */ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesMapper.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesMapper.java new file mode 100644 index 0000000000..07b5badb63 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayPropertiesMapper.java @@ -0,0 +1,174 @@ +/* + * Copyright 2012-2022 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 + * + * https://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.flyway; + +import java.sql.DatabaseMetaData; +import java.time.Duration; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import javax.sql.DataSource; + +import org.flywaydb.core.api.configuration.FluentConfiguration; +import org.flywaydb.database.sqlserver.SQLServerConfigurationExtension; + +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.jdbc.DatabaseDriver; +import org.springframework.jdbc.support.JdbcUtils; +import org.springframework.jdbc.support.MetaDataAccessException; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +/** + * Maps {@link FlywayProperties} to a Flyway {@link FluentConfiguration}. + * + * @author Moritz Halbritter + * @since 3.0.0 + */ +public class FlywayPropertiesMapper { + + /** + * Maps {@link FlywayProperties} to a Flyway {@link FluentConfiguration}. + * @param properties properties to map from + * @param configuration flyway configuration to map to + */ + public void map(FlywayProperties properties, FluentConfiguration configuration) { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + String[] locations = new LocationResolver(configuration.getDataSource()) + .resolveLocations(properties.getLocations()).toArray(new String[0]); + map.from(properties.isFailOnMissingLocations()).to(configuration::failOnMissingLocations); + map.from(locations).to(configuration::locations); + map.from(properties.getEncoding()).to(configuration::encoding); + map.from(properties.getConnectRetries()).to(configuration::connectRetries); + map.from(properties.getConnectRetriesInterval()).as(Duration::getSeconds).as(Long::intValue) + .to(configuration::connectRetriesInterval); + map.from(properties.getLockRetryCount()).to(configuration::lockRetryCount); + map.from(properties.getDefaultSchema()).to(configuration::defaultSchema); + map.from(properties.getSchemas()).as(StringUtils::toStringArray).to(configuration::schemas); + map.from(properties.isCreateSchemas()).to(configuration::createSchemas); + map.from(properties.getTable()).to(configuration::table); + map.from(properties.getTablespace()).to(configuration::tablespace); + map.from(properties.getBaselineDescription()).to(configuration::baselineDescription); + map.from(properties.getBaselineVersion()).to(configuration::baselineVersion); + map.from(properties.getInstalledBy()).to(configuration::installedBy); + map.from(properties.getPlaceholders()).to(configuration::placeholders); + map.from(properties.getPlaceholderPrefix()).to(configuration::placeholderPrefix); + map.from(properties.getPlaceholderSuffix()).to(configuration::placeholderSuffix); + map.from(properties.getPlaceholderSeparator()).to(configuration::placeholderSeparator); + map.from(properties.isPlaceholderReplacement()).to(configuration::placeholderReplacement); + map.from(properties.getSqlMigrationPrefix()).to(configuration::sqlMigrationPrefix); + map.from(properties.getSqlMigrationSuffixes()).as(StringUtils::toStringArray) + .to(configuration::sqlMigrationSuffixes); + map.from(properties.getSqlMigrationSeparator()).to(configuration::sqlMigrationSeparator); + map.from(properties.getRepeatableSqlMigrationPrefix()).to(configuration::repeatableSqlMigrationPrefix); + map.from(properties.getTarget()).to(configuration::target); + map.from(properties.isBaselineOnMigrate()).to(configuration::baselineOnMigrate); + map.from(properties.isCleanDisabled()).to(configuration::cleanDisabled); + map.from(properties.isCleanOnValidationError()).to(configuration::cleanOnValidationError); + map.from(properties.isGroup()).to(configuration::group); + map.from(properties.isMixed()).to(configuration::mixed); + map.from(properties.isOutOfOrder()).to(configuration::outOfOrder); + map.from(properties.isSkipDefaultCallbacks()).to(configuration::skipDefaultCallbacks); + map.from(properties.isSkipDefaultResolvers()).to(configuration::skipDefaultResolvers); + map.from(properties.isValidateMigrationNaming()).to(configuration::validateMigrationNaming); + map.from(properties.isValidateOnMigrate()).to(configuration::validateOnMigrate); + map.from(properties.getInitSqls()).whenNot(CollectionUtils::isEmpty) + .as((initSqls) -> StringUtils.collectionToDelimitedString(initSqls, "\n")).to(configuration::initSql); + map.from(properties.getScriptPlaceholderPrefix()).to(configuration::scriptPlaceholderPrefix); + map.from(properties.getScriptPlaceholderSuffix()).to(configuration::scriptPlaceholderSuffix); + // Flyway Teams properties + map.from(properties.getBatch()).to(configuration::batch); + map.from(properties.getDryRunOutput()).to(configuration::dryRunOutput); + map.from(properties.getErrorOverrides()).to(configuration::errorOverrides); + map.from(properties.getLicenseKey()).to(configuration::licenseKey); + map.from(properties.getOracleSqlplus()).to(configuration::oracleSqlplus); + map.from(properties.getOracleSqlplusWarn()).to(configuration::oracleSqlplusWarn); + map.from(properties.getStream()).to(configuration::stream); + map.from(properties.getUndoSqlMigrationPrefix()).to(configuration::undoSqlMigrationPrefix); + map.from(properties.getCherryPick()).to(configuration::cherryPick); + map.from(properties.getJdbcProperties()).whenNot(Map::isEmpty).to(configuration::jdbcProperties); + map.from(properties.getKerberosConfigFile()).to(configuration::kerberosConfigFile); + map.from(properties.getOracleKerberosCacheFile()).to(configuration::oracleKerberosCacheFile); + map.from(properties.getOutputQueryResults()).to(configuration::outputQueryResults); + map.from(properties.getSqlServerKerberosLoginFile()).whenNonNull() + .to((sqlServerKerberosLoginFile) -> configureSqlServerKerberosLoginFile(configuration, + sqlServerKerberosLoginFile)); + map.from(properties.getSkipExecutingMigrations()).to(configuration::skipExecutingMigrations); + map.from(properties.getIgnoreMigrationPatterns()).whenNot(List::isEmpty) + .as((patterns) -> patterns.toArray(new String[0])).to(configuration::ignoreMigrationPatterns); + map.from(properties.getDetectEncoding()).to(configuration::detectEncoding); + } + + private void configureSqlServerKerberosLoginFile(FluentConfiguration configuration, + String sqlServerKerberosLoginFile) { + SQLServerConfigurationExtension sqlServerConfigurationExtension = configuration.getPluginRegister() + .getPlugin(SQLServerConfigurationExtension.class); + Assert.state(sqlServerConfigurationExtension != null, "Flyway SQL Server extension missing"); + sqlServerConfigurationExtension.setKerberosLoginFile(sqlServerKerberosLoginFile); + } + + private static class LocationResolver { + + private static final String VENDOR_PLACEHOLDER = "{vendor}"; + + private final DataSource dataSource; + + LocationResolver(DataSource dataSource) { + this.dataSource = dataSource; + } + + List resolveLocations(List locations) { + if (usesVendorLocation(locations)) { + DatabaseDriver databaseDriver = getDatabaseDriver(); + return replaceVendorLocations(locations, databaseDriver); + } + return locations; + } + + private List replaceVendorLocations(List locations, DatabaseDriver databaseDriver) { + if (databaseDriver == DatabaseDriver.UNKNOWN) { + return locations; + } + String vendor = databaseDriver.getId(); + return locations.stream().map((location) -> location.replace(VENDOR_PLACEHOLDER, vendor)).toList(); + } + + private DatabaseDriver getDatabaseDriver() { + try { + String url = JdbcUtils.extractDatabaseMetaData(this.dataSource, DatabaseMetaData::getURL); + return DatabaseDriver.fromJdbcUrl(url); + } + catch (MetaDataAccessException ex) { + throw new IllegalStateException(ex); + } + + } + + private boolean usesVendorLocation(Collection locations) { + for (String location : locations) { + if (location.contains(VENDOR_PLACEHOLDER)) { + return true; + } + } + return false; + } + + } + +}