Skip to content
This repository has been archived by the owner on Jul 22, 2020. It is now read-only.

Support for custom migration resolvers #90

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ import com.hhandoko.cassandra.migration.internal.command.Validate
import com.hhandoko.cassandra.migration.internal.dbsupport.SchemaVersionDAO
import com.hhandoko.cassandra.migration.internal.info.MigrationInfoServiceImpl
import com.hhandoko.cassandra.migration.internal.resolver.CompositeMigrationResolver
import com.hhandoko.cassandra.migration.internal.resolver.MigrationResolverFactory
import com.hhandoko.cassandra.migration.internal.resolver.cql.CqlMigrationResolver
import com.hhandoko.cassandra.migration.internal.resolver.java.JavaMigrationResolver
import com.hhandoko.cassandra.migration.internal.util.Locations
import com.hhandoko.cassandra.migration.internal.util.StringUtils
import com.hhandoko.cassandra.migration.internal.util.VersionPrinter
Expand All @@ -48,6 +51,7 @@ import io.netty.handler.ssl.SslContextBuilder
import io.netty.handler.ssl.SslProvider
import java.io.FileInputStream
import java.security.KeyStore
import java.util.*
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.TrustManagerFactory

Expand Down Expand Up @@ -86,6 +90,23 @@ class CassandraMigration : CassandraMigrationConfiguration {
*/
override var baselineDescription = "<< Cassandra Baseline >>"

/**
* Retrieves the the custom MigrationResolvers to be used in addition to the built-in ones for resolving migrations to apply.
*
* @return The custom MigrationResolvers to be used in addition to the built-in ones for resolving migrations to apply.
* An empty array if none.
* (default: none)
*/
override var resolvers: Array<MigrationResolver> = emptyArray()

/**
* Whether Cassandra migration should skip the default resolvers. If true, only custom resolvers are used.
*
* @return Whether default built-in resolvers should be skipped.
* (default: false)
*/
override var isSkipDefaultResolvers = false

/**
* The encoding of CQL migrations script encoding.
* (default: "UTF-8")
Expand Down Expand Up @@ -405,8 +426,16 @@ class CassandraMigration : CassandraMigrationConfiguration {
*
* @return A new, fully configured, MigrationResolver instance.
*/
private fun createMigrationResolver(): MigrationResolver {
return CompositeMigrationResolver(classLoader, Locations(*locations), encoding, timeout)
public fun createMigrationResolver(): MigrationResolver {
val migrationResolvers = ArrayList<MigrationResolver>(locations.size * 2)
if (!isSkipDefaultResolvers) {
migrationResolvers.addAll(MigrationResolverFactory.createDefaultResolvers(
classLoader, encoding, timeout, Locations(*locations)))
}
if (resolvers.isNotEmpty()) {
migrationResolvers.addAll(resolvers)
}
return CompositeMigrationResolver(*migrationResolvers.toTypedArray())
}

private fun migrationTableName(): String{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package com.hhandoko.cassandra.migration.api.configuration

import com.hhandoko.cassandra.migration.api.MigrationVersion
import com.hhandoko.cassandra.migration.api.resolver.MigrationResolver

/**
* Readonly interface for main Cassandra migration configuration.
Expand Down Expand Up @@ -58,22 +59,22 @@ interface CassandraMigrationConfiguration {
*/
val baselineDescription: String

// /**
// * Retrieves the the custom MigrationResolvers to be used in addition to the built-in ones for resolving migrations to apply.
// *
// * @return The custom MigrationResolvers to be used in addition to the built-in ones for resolving migrations to apply.
// * An empty array if none.
// * (default: none)
// */
// val resolvers: Array<MigrationResolver>
/**
* Retrieves the the custom MigrationResolvers to be used in addition to the built-in ones for resolving migrations to apply.
*
* @return The custom MigrationResolvers to be used in addition to the built-in ones for resolving migrations to apply.
* An empty array if none.
* (default: none)
*/
val resolvers: Array<MigrationResolver>

// /**
// * Whether Cassandra migration should skip the default resolvers. If true, only custom resolvers are used.
// *
// * @return Whether default built-in resolvers should be skipped.
// * (default: false)
// */
// val isSkipDefaultResolvers: Boolean
/**
* Whether Cassandra migration should skip the default resolvers. If true, only custom resolvers are used.
*
* @return Whether default built-in resolvers should be skipped.
* (default: false)
*/
val isSkipDefaultResolvers: Boolean

// /**
// * Gets the callbacks for lifecycle notifications.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,22 @@ package com.hhandoko.cassandra.migration.internal.resolver
import com.hhandoko.cassandra.migration.api.CassandraMigrationException
import com.hhandoko.cassandra.migration.api.resolver.MigrationResolver
import com.hhandoko.cassandra.migration.api.resolver.ResolvedMigration
import com.hhandoko.cassandra.migration.internal.resolver.cql.CqlMigrationResolver
import com.hhandoko.cassandra.migration.internal.resolver.java.JavaMigrationResolver
import com.hhandoko.cassandra.migration.internal.util.Locations
import java.util.*

/**
* Facility for retrieving and sorting the available migrations from the classpath through the various migration
* resolvers.
*
* @param classLoader The ClassLoader for loading migrations on the classpath.
* @param locations The locations where migrations are located.
* @param encoding The CQL migrations encoding.
* @param timeout The CQL migrations read timeout duration in seconds.
* @param customMigrationResolvers Custom Migration Resolvers.
*/
class CompositeMigrationResolver(
classLoader: ClassLoader,
locations: Locations,
encoding: String,
timeout: Int,
vararg customMigrationResolvers: MigrationResolver
) : MigrationResolver {

/**
* The migration resolvers to use internally.
*/
private val migrationResolvers = ArrayList<MigrationResolver>()
public val migrationResolvers = ArrayList<MigrationResolver>()

/**
* The available migrations, sorted by version, newest first. An empty list is returned when no migrations can be
Expand All @@ -59,12 +48,7 @@ class CompositeMigrationResolver(
* CompositeMigrationResolver initialization.
*/
init {
locations.getLocations().forEach {
migrationResolvers.add(CqlMigrationResolver(classLoader, it, encoding, timeout))
migrationResolvers.add(JavaMigrationResolver(classLoader, it))
}

migrationResolvers.addAll(Arrays.asList(*customMigrationResolvers))
migrationResolvers.addAll(listOf(*customMigrationResolvers))
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.hhandoko.cassandra.migration.internal.resolver

import com.hhandoko.cassandra.migration.api.resolver.MigrationResolver
import com.hhandoko.cassandra.migration.internal.resolver.cql.CqlMigrationResolver
import com.hhandoko.cassandra.migration.internal.resolver.java.JavaMigrationResolver
import com.hhandoko.cassandra.migration.internal.util.Locations
import java.util.*

/**
* Helper for creating default resolvers
*/
object MigrationResolverFactory {

/**
* Creates the default list of resolvers (Cql and Java) for each of the locations
*
* @param classLoader The ClassLoader for loading migrations on the classpath.
* @param encoding The encoding of the .cql file(s).
* @param timeout The read script timeout duration in seconds.
* @param locations The Locations to create the default resolvers for.
* @return An array of default migration resolvers
*/
fun createDefaultResolvers(
classLoader: ClassLoader,
encoding: String,
timeout: Int,
locations: Locations): Array<MigrationResolver> {
val migrationResolvers = ArrayList<MigrationResolver>(locations.getLocations().size * 2)
locations.getLocations().forEach {
migrationResolvers.add(CqlMigrationResolver(classLoader, it, encoding, timeout))
migrationResolvers.add(JavaMigrationResolver(classLoader, it))
}
return migrationResolvers.toTypedArray()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.hhandoko.cassandra.migration

import com.hhandoko.cassandra.migration.api.resolver.MigrationResolver
import com.hhandoko.cassandra.migration.api.resolver.ResolvedMigration
import com.hhandoko.cassandra.migration.internal.resolver.CompositeMigrationResolver
import com.hhandoko.cassandra.migration.internal.resolver.ResolvedMigrationImpl
import com.hhandoko.cassandra.migration.internal.resolver.cql.CqlMigrationResolver
import com.hhandoko.cassandra.migration.internal.resolver.java.JavaMigrationResolver
import io.kotlintest.matchers.be
import io.kotlintest.specs.FreeSpec

class CassandraMigrationSpec : FreeSpec() {


init {
"Cassandra migration" - {

"resolvers" - {
val scriptsLocations = arrayOf("migration/integ", "migration/integ/java")
val cm = CassandraMigration()
cm.locations = scriptsLocations

"defaults to cql and java" {
val resolver = cm.createMigrationResolver()

// should be a composite resolver
resolver should be an CompositeMigrationResolver::class

// 1 each for the defaults
val resolvers = (resolver as CompositeMigrationResolver).migrationResolvers
resolvers.size shouldBe 2
resolvers[0] should be an CqlMigrationResolver::class
resolvers[1] should be an JavaMigrationResolver::class
}

"can be added" {

cm.resolvers = arrayOf(object : MigrationResolver {
override fun resolveMigrations(): List<ResolvedMigration> {
val resolvedMigration = ResolvedMigrationImpl()
resolvedMigration.description = "custom"
return arrayListOf(resolvedMigration).toList()
}
});

val resolver = cm.createMigrationResolver()

// should be a composite resolver
resolver should be an CompositeMigrationResolver::class

// 1 each for the defaults and 1 for our custom
val resolvers = (resolver as CompositeMigrationResolver).migrationResolvers
resolvers.size shouldBe 3

// should be the last in the list
val resolvedMigration = resolvers.last().resolveMigrations().first()
resolvedMigration.description shouldBe "custom"
}

"can be replaced" {
cm.isSkipDefaultResolvers = true
cm.resolvers = arrayOf(object : MigrationResolver {
override fun resolveMigrations(): List<ResolvedMigration> {
val resolvedMigration = ResolvedMigrationImpl()
resolvedMigration.description = "custom"
return arrayListOf(resolvedMigration).toList()
}
});

val resolver = cm.createMigrationResolver()

// should be a composite resolver
resolver should be an CompositeMigrationResolver::class

// just 1 for our custom
val resolvers = (resolver as CompositeMigrationResolver).migrationResolvers
resolvers.size shouldBe 1

// should be the last in the list
val resolvedMigration = resolvers[0].resolveMigrations().first()
resolvedMigration.description shouldBe "custom"
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ class CompositeMigrationResolverSpec : FreeSpec() {

"should resolve migrations in multiple locations" {
val resolver = CompositeMigrationResolver(
Thread.currentThread().contextClassLoader,
Locations("migration/subdir/dir2", "migration.outoforder", "migration/subdir/dir1"),
"UTF-8",
timeout = 0
*MigrationResolverFactory.createDefaultResolvers(
Thread.currentThread().contextClassLoader,
"UTF-8",
0,
Locations("migration/subdir/dir2", "migration.outoforder", "migration/subdir/dir1"))
)
val migrations = resolver.resolveMigrations()

Expand Down