diff --git a/common-spring/src/main/kotlin/ru/touchin/common/spring/annotations/RequiredBy.kt b/common-spring/src/main/kotlin/ru/touchin/common/spring/annotations/RequiredBy.kt new file mode 100644 index 0000000..b81e6f9 --- /dev/null +++ b/common-spring/src/main/kotlin/ru/touchin/common/spring/annotations/RequiredBy.kt @@ -0,0 +1,9 @@ +package ru.touchin.common.spring.annotations + +import org.springframework.context.annotation.Import +import ru.touchin.common.spring.processors.RequiredByBeanDefinitionPostProcessor + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS) +@Import(RequiredByBeanDefinitionPostProcessor::class) +annotation class RequiredBy(vararg val value: String) diff --git a/common-spring/src/main/kotlin/ru/touchin/common/spring/processors/RequiredByBeanDefinitionPostProcessor.kt b/common-spring/src/main/kotlin/ru/touchin/common/spring/processors/RequiredByBeanDefinitionPostProcessor.kt new file mode 100644 index 0000000..e98d520 --- /dev/null +++ b/common-spring/src/main/kotlin/ru/touchin/common/spring/processors/RequiredByBeanDefinitionPostProcessor.kt @@ -0,0 +1,38 @@ +package ru.touchin.common.spring.processors + +import org.springframework.beans.BeansException +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory +import org.springframework.beans.factory.support.BeanDefinitionRegistry +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor +import org.springframework.stereotype.Component +import ru.touchin.common.spring.annotations.RequiredBy + +@Component +class RequiredByBeanDefinitionPostProcessor : BeanDefinitionRegistryPostProcessor { + + @Throws(BeansException::class) + override fun postProcessBeanDefinitionRegistry(registry: BeanDefinitionRegistry) { + for (beanName in registry.beanDefinitionNames) { + val beanClassName = registry.getBeanDefinition(beanName).beanClassName?:continue + + getDependantBeanNames(beanClassName).forEach { dependantBeanName -> + registry.getBeanDefinition(dependantBeanName).setDependsOn(beanName) + } + } + } + + @Throws(BeansException::class) + override fun postProcessBeanFactory(beanFactory: ConfigurableListableBeanFactory) { + } + + private fun getDependantBeanNames(beanClassName: String): List { + val beanClass = Class.forName(beanClassName) + var dependantBeans = emptyList() + + if (beanClass.isAnnotationPresent(RequiredBy::class.java)) { + dependantBeans = beanClass.getAnnotation(RequiredBy::class.java).value.toList() + } + + return dependantBeans + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index be52383..f371643 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index b9c349c..f5581b4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -60,3 +60,4 @@ include("s3-storage") include("server-info-spring-web") include("geoip-core") include("user-agent") +include("smart-migration") diff --git a/smart-migration/build.gradle b/smart-migration/build.gradle new file mode 100644 index 0000000..74e8552 --- /dev/null +++ b/smart-migration/build.gradle @@ -0,0 +1,12 @@ +plugins { + id("kotlin") + id("kotlin-spring") + id("maven-publish") +} + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation("org.springframework.boot:spring-boot") + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation project(":common-spring") +} diff --git a/smart-migration/src/main/kotlin/ru/touchin/smartmigration/BeforeLiquibase.kt b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/BeforeLiquibase.kt new file mode 100644 index 0000000..1866dff --- /dev/null +++ b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/BeforeLiquibase.kt @@ -0,0 +1,59 @@ +@file:Suppress("unused") +package ru.touchin.smartmigration + +import org.springframework.stereotype.Component +import ru.touchin.common.spring.annotations.RequiredBy +import ru.touchin.smartmigration.logic.DataSourceSQL +import ru.touchin.smartmigration.logic.factory.DataSourceSqlFactoryImpl +import java.sql.Date +import java.text.SimpleDateFormat +import javax.annotation.PostConstruct +import javax.sql.DataSource + +private val SQL_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS") + +private val CURRENT_TIME_SQL: String + get() = SQL_DATE_FORMAT.format(Date(System.currentTimeMillis())) + +@Component +@RequiredBy("liquibase") +class BeforeLiquibase( + private val dataSource: DataSource +) { + + val dataSourceSql: DataSourceSQL = DataSourceSqlFactoryImpl() + .getDataSourceSql(dataSource.connection.metaData.databaseProductName) + + @PostConstruct + fun doAction() { + val buildNumber = System.getenv("BUILD_NUMBER") + ?: return + + checkMigrationTable() + + if (checkBuildMigrationExecuted(buildNumber)) { + System.setProperty("spring.liquibase.enabled", "false") + } else { + insertMigration(buildNumber) + } + } + + private fun checkBuildMigrationExecuted(buildNumber: String): Boolean { + return dataSourceSql.getMigrationCheckSQL(buildNumber).let { + dataSource.connection.createStatement().executeQuery(it).next() + } + } + + private fun checkMigrationTable() { + dataSourceSql.getTableCheckSQL().let { + dataSource.connection.createStatement().execute(it) + } + } + + private fun insertMigration(buildNumber: String) { + dataSourceSql.getInsertMigrationSQL(buildNumber, CURRENT_TIME_SQL).let { + dataSource.connection.createStatement().execute(it) + } + } + +} diff --git a/smart-migration/src/main/kotlin/ru/touchin/smartmigration/SmartMigrationConfig.kt b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/SmartMigrationConfig.kt new file mode 100644 index 0000000..21021a0 --- /dev/null +++ b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/SmartMigrationConfig.kt @@ -0,0 +1,8 @@ +package ru.touchin.smartmigration + +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan("ru.touchin.smartmigration") +open class SmartMigrationConfig diff --git a/smart-migration/src/main/kotlin/ru/touchin/smartmigration/annotation/EnableSmartMigration.kt b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/annotation/EnableSmartMigration.kt new file mode 100644 index 0000000..fcab586 --- /dev/null +++ b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/annotation/EnableSmartMigration.kt @@ -0,0 +1,7 @@ +package ru.touchin.smartmigration.annotation + +import org.springframework.context.annotation.Import +import ru.touchin.smartmigration.SmartMigrationConfig + +@Import(SmartMigrationConfig::class) +annotation class EnableSmartMigration diff --git a/smart-migration/src/main/kotlin/ru/touchin/smartmigration/logic/DataSourceSQL.kt b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/logic/DataSourceSQL.kt new file mode 100644 index 0000000..7bfba9b --- /dev/null +++ b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/logic/DataSourceSQL.kt @@ -0,0 +1,11 @@ +package ru.touchin.smartmigration.logic + +interface DataSourceSQL { + + fun getTableCheckSQL(): String + + fun getMigrationCheckSQL(buildNumber: String): String + + fun getInsertMigrationSQL(buildNumber: String, formattedTime: String): String + +} diff --git a/smart-migration/src/main/kotlin/ru/touchin/smartmigration/logic/PostgresDataSourceImpl.kt b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/logic/PostgresDataSourceImpl.kt new file mode 100644 index 0000000..782a3d4 --- /dev/null +++ b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/logic/PostgresDataSourceImpl.kt @@ -0,0 +1,33 @@ +package ru.touchin.smartmigration.logic + +import org.intellij.lang.annotations.Language + +private const val MIGRATION_TABLE_NAME = "SMART_MIGRATION" + +class PostgresDataSourceImpl: DataSourceSQL { + + @Language("SQL") + override fun getTableCheckSQL(): String { + return """ + CREATE TABLE IF NOT EXISTS smart_migration ( + ID BIGSERIAL PRIMARY KEY, + BUILD_NUMBER VARCHAR(255) NOT NULL, + DATE timestamp NOT NULL + ); + """ + } + + @Language("SQL") + override fun getMigrationCheckSQL(buildNumber: String): String { + return "SELECT * FROM $MIGRATION_TABLE_NAME WHERE BUILD_NUMBER = '$buildNumber'" + } + + @Language("SQL") + override fun getInsertMigrationSQL(buildNumber: String, formattedTime: String): String { + return """ + INSERT INTO $MIGRATION_TABLE_NAME (BUILD_NUMBER, DATE) + VALUES ('$buildNumber', '$formattedTime'); + """ + } + +} diff --git a/smart-migration/src/main/kotlin/ru/touchin/smartmigration/logic/SqlDataSourceImpl.kt b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/logic/SqlDataSourceImpl.kt new file mode 100644 index 0000000..fac3bff --- /dev/null +++ b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/logic/SqlDataSourceImpl.kt @@ -0,0 +1,38 @@ +package ru.touchin.smartmigration.logic + +import org.intellij.lang.annotations.Language + +private const val MIGRATION_TABLE_NAME = "SMART_MIGRATION" + +class SqlDatasourceImpl:DataSourceSQL { + + @Language("TSQL") + override fun getTableCheckSQL(): String { + return """ + IF NOT EXISTS ( + SELECT * FROM sysobjects WHERE name = '$MIGRATION_TABLE_NAME' and xtype='U' + ) + CREATE TABLE SMART_MIGRATION ( + ID BIGINT PRIMARY KEY IDENTITY , + BUILD_NUMBER VARCHAR(255) NOT NULL, + DATE DATETIME NOT NULL + ); + """ + } + + @Language("TSQL") + override fun getMigrationCheckSQL(buildNumber: String): String { + return "SELECT * FROM $MIGRATION_TABLE_NAME WHERE BUILD_NUMBER = '$buildNumber'" + } + + @Language("TSQL") + override fun getInsertMigrationSQL(buildNumber: String, formattedTime: String): String { + return """ + INSERT INTO $MIGRATION_TABLE_NAME (BUILD_NUMBER, DATE) + VALUES ('$buildNumber', '$formattedTime'); + """ + } + +} + + diff --git a/smart-migration/src/main/kotlin/ru/touchin/smartmigration/logic/factory/DataSourceSqlFactory.kt b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/logic/factory/DataSourceSqlFactory.kt new file mode 100644 index 0000000..601e876 --- /dev/null +++ b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/logic/factory/DataSourceSqlFactory.kt @@ -0,0 +1,9 @@ +package ru.touchin.smartmigration.logic.factory + +import ru.touchin.smartmigration.logic.DataSourceSQL + +interface DataSourceSqlFactory { + + fun getDataSourceSql(driverName: String): DataSourceSQL + +} diff --git a/smart-migration/src/main/kotlin/ru/touchin/smartmigration/logic/factory/DataSourceSqlFactoryImpl.kt b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/logic/factory/DataSourceSqlFactoryImpl.kt new file mode 100644 index 0000000..eb6c29a --- /dev/null +++ b/smart-migration/src/main/kotlin/ru/touchin/smartmigration/logic/factory/DataSourceSqlFactoryImpl.kt @@ -0,0 +1,19 @@ +package ru.touchin.smartmigration.logic.factory + +import ru.touchin.smartmigration.logic.DataSourceSQL +import ru.touchin.smartmigration.logic.PostgresDataSourceImpl +import ru.touchin.smartmigration.logic.SqlDatasourceImpl + +class DataSourceSqlFactoryImpl: DataSourceSqlFactory { + + override fun getDataSourceSql(driverName: String): DataSourceSQL { + return when(driverName){ + "Microsoft SQL Server" -> SqlDatasourceImpl() + "PostgresSQL" -> PostgresDataSourceImpl() + else -> { + PostgresDataSourceImpl() + } + } + } + +}