diff --git a/src/main/kotlin/com/revolut/jooq/ChildFirstClassLoader.kt b/src/main/kotlin/com/revolut/jooq/ChildFirstClassLoader.kt new file mode 100644 index 0000000..cd6d8bb --- /dev/null +++ b/src/main/kotlin/com/revolut/jooq/ChildFirstClassLoader.kt @@ -0,0 +1,24 @@ +import java.net.URL +import java.net.URLClassLoader + +class ChildFirstClassLoader(classpath: Array?, parent: ClassLoader?) : URLClassLoader(classpath, parent) { + private val system: ClassLoader = getSystemClassLoader() + + @Synchronized + override fun loadClass(name: String, resolve: Boolean): Class<*>? { + var c = findLoadedClass(name) + if (c == null) { + c = try { + findClass(name) + } catch (e: ClassNotFoundException) { + try { + super.loadClass(name, resolve) + } catch (e2: ClassNotFoundException) { + system.loadClass(name) + } + } + } + if (resolve) resolveClass(c) + return c + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/revolut/jooq/FlywaySchemaVersionProvider.kt b/src/main/kotlin/com/revolut/jooq/FlywaySchemaVersionProvider.kt index b24cf50..d4607a0 100644 --- a/src/main/kotlin/com/revolut/jooq/FlywaySchemaVersionProvider.kt +++ b/src/main/kotlin/com/revolut/jooq/FlywaySchemaVersionProvider.kt @@ -9,6 +9,7 @@ class FlywaySchemaVersionProvider : SchemaVersionProvider { private val defaultSchemaName = ThreadLocal() private val flywayTableName = ThreadLocal() + @JvmStatic fun setup(defaultSchemaName: String, flywayTableName: String) { this.defaultSchemaName.set(defaultSchemaName) this.flywayTableName.set(flywayTableName) diff --git a/src/main/kotlin/com/revolut/jooq/GenerateJooqClassesTask.kt b/src/main/kotlin/com/revolut/jooq/GenerateJooqClassesTask.kt index 536168a..5e5b2fe 100644 --- a/src/main/kotlin/com/revolut/jooq/GenerateJooqClassesTask.kt +++ b/src/main/kotlin/com/revolut/jooq/GenerateJooqClassesTask.kt @@ -1,5 +1,6 @@ package com.revolut.jooq +import ChildFirstClassLoader import groovy.lang.Closure import org.flywaydb.core.Flyway import org.flywaydb.core.api.Location.FILESYSTEM_PREFIX @@ -11,13 +12,12 @@ import org.gradle.api.plugins.JavaPlugin import org.gradle.api.plugins.JavaPluginConvention import org.gradle.api.tasks.* import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME -import org.jooq.codegen.GenerationTool -import org.jooq.codegen.JavaGenerator import org.jooq.meta.jaxb.* import org.jooq.meta.jaxb.Target +import java.io.File import java.io.IOException import java.net.URL -import java.net.URLClassLoader +import javax.xml.bind.JAXBContext @CacheableTask open class GenerateJooqClassesTask : DefaultTask() { @@ -175,27 +175,56 @@ open class GenerateJooqClassesTask : DefaultTask() { private fun generateJooqClasses(jdbcAwareClassLoader: ClassLoader, dbHost: String) { project.delete(outputDirectory) + val config = prepareConfig(dbHost) + val configFile = writeConfigToTemporaryFile(config) + generateJooqClassesUsingClassloader(jdbcAwareClassLoader, configFile) + } + + private fun prepareConfig(dbHost: String): Configuration { val db = getDb() val jdbc = getJdbc() - FlywaySchemaVersionProvider.setup(defaultFlywaySchema(), flywayTableName()) - SchemaPackageRenameGeneratorStrategy.schemaToPackageMapping.set(schemaToPackageMapping.toMap()) val generator = generatorConfig.get() excludeFlywaySchemaIfNeeded(generator) - val tool = GenerationTool() - tool.setClassLoader(jdbcAwareClassLoader) - tool.run(Configuration() + return Configuration() .withLogging(Logging.DEBUG) .withJdbc(Jdbc() .withDriver(jdbc.driverClassName) .withUrl(db.getUrl(dbHost)) .withUser(db.username) .withPassword(db.password)) - .withGenerator(generator)) + .withGenerator(generator) + } + + private fun writeConfigToTemporaryFile(config: Configuration): File { + val configFile = File(temporaryDir, "config.xml") + configFile.writer().use { + JAXBContext.newInstance(config.javaClass) + .createMarshaller() + .marshal(config, it) + } + if (logger.isDebugEnabled) { + logger.debug("config for generator: {}", configFile.readText()) + } + return configFile + } + + private fun generateJooqClassesUsingClassloader(jdbcAwareClassLoader: ClassLoader, configFile: File) { + jdbcAwareClassLoader.run { + loadClass(FlywaySchemaVersionProvider::class.qualifiedName) + .getDeclaredMethod("setup", String::class.java, String::class.java) + .invoke(null, defaultFlywaySchema(), flywayTableName()) + loadClass(SchemaPackageRenameGeneratorStrategy::class.qualifiedName) + .getDeclaredMethod("setup", Map::class.java) + .invoke(null, schemaToPackageMapping.toMap()) + loadClass("org.jooq.codegen.GenerationTool") + .getDeclaredMethod("main", Array::class.java) + .invoke(null, arrayOf(configFile.absolutePath)) + } } private fun prepareGeneratorConfig(): Generator { return Generator() - .withName(JavaGenerator::class.qualifiedName) + .withName("org.jooq.codegen.JavaGenerator") .withStrategy(Strategy() .withName(SchemaPackageRenameGeneratorStrategy::class.qualifiedName)) .withDatabase(Database() @@ -229,7 +258,10 @@ open class GenerateJooqClassesTask : DefaultTask() { } private fun buildJdbcArtifactsAwareClassLoader(): ClassLoader { - return URLClassLoader(resolveJdbcArtifacts(), project.buildscript.classLoader) + return ChildFirstClassLoader( + resolveJdbcArtifacts() + + arrayOf(SchemaPackageRenameGeneratorStrategy.javaClass.protectionDomain.codeSource.location), + project.buildscript.classLoader) } @Throws(IOException::class) diff --git a/src/main/kotlin/com/revolut/jooq/JooqDockerPlugin.kt b/src/main/kotlin/com/revolut/jooq/JooqDockerPlugin.kt index f655607..a55abe7 100644 --- a/src/main/kotlin/com/revolut/jooq/JooqDockerPlugin.kt +++ b/src/main/kotlin/com/revolut/jooq/JooqDockerPlugin.kt @@ -2,14 +2,32 @@ package com.revolut.jooq import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration open class JooqDockerPlugin : Plugin { override fun apply(project: Project) { project.extensions.create("jooq", JooqExtension::class.java, project.name) - project.configurations.create("jdbc") + val configuration = project.configurations.create("jdbc") + addJooqCodegenDependencyToPluginRuntime(project, configuration) project.tasks.create("generateJooqClasses", GenerateJooqClassesTask::class.java) { group = "jooq" } } + + private fun addJooqCodegenDependencyToPluginRuntime(project: Project, configuration: Configuration) { + project.dependencies.add(configuration.name, project.provider { + findJooqVersionOnCompileClasspath(project) + ?.let { "org.jooq:jooq-codegen:$it" } + ?: throw IllegalStateException("Unable to resolve jooq version. Please add jooq to your classpath") + }) + } + + private fun findJooqVersionOnCompileClasspath(project: Project) = + project.configurations.getByName("compileClasspath") + .resolvedConfiguration + .resolvedArtifacts + .map { it.moduleVersion.id } + .find { it.name == "jooq" } + ?.version } \ No newline at end of file diff --git a/src/main/kotlin/com/revolut/jooq/SchemaPackageRenameGeneratorStrategy.kt b/src/main/kotlin/com/revolut/jooq/SchemaPackageRenameGeneratorStrategy.kt index cafa7d3..9853185 100644 --- a/src/main/kotlin/com/revolut/jooq/SchemaPackageRenameGeneratorStrategy.kt +++ b/src/main/kotlin/com/revolut/jooq/SchemaPackageRenameGeneratorStrategy.kt @@ -7,7 +7,12 @@ import org.jooq.meta.SchemaDefinition class SchemaPackageRenameGeneratorStrategy : DefaultGeneratorStrategy() { companion object { - val schemaToPackageMapping: ThreadLocal> = ThreadLocal.withInitial { emptyMap() } + private val schemaToPackageMapping: ThreadLocal> = ThreadLocal.withInitial { emptyMap() } + + @JvmStatic + fun setup(mapping: Map) { + schemaToPackageMapping.set(mapping) + } } override fun getJavaIdentifier(definition: Definition?): String { diff --git a/src/test/groovy/JooqDockerPluginSpec.groovy b/src/test/groovy/JooqDockerPluginSpec.groovy index c5a4199..718b3c1 100644 --- a/src/test/groovy/JooqDockerPluginSpec.groovy +++ b/src/test/groovy/JooqDockerPluginSpec.groovy @@ -44,6 +44,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -53,6 +54,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init.sql", "src/main/resources/db/migration/V01__init.sql") @@ -76,6 +78,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -91,6 +94,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init_multiple_schemas.sql", "src/main/resources/db/migration/V01__init_multiple_schemas.sql") @@ -116,6 +120,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -132,6 +137,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init_multiple_schemas.sql", "src/main/resources/db/migration/V01__init_multiple_schemas.sql") @@ -155,6 +161,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -173,6 +180,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init_multiple_schemas.sql", "src/main/resources/db/migration/V01__init_multiple_schemas.sql") @@ -196,6 +204,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -205,6 +214,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init.sql", "src/main/resources/db/migration/V01__init.sql") @@ -237,6 +247,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -246,6 +257,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init.sql", "src/main/resources/db/migration/V01__init.sql") @@ -279,6 +291,7 @@ class JooqDockerPluginSpec extends Specification { def initialBuildGradle = """ plugins { + java id("com.revolut.jooq-docker") } @@ -294,11 +307,13 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """ def extensionUpdatedBuildGradle = """ plugins { + java id("com.revolut.jooq-docker") } @@ -314,6 +329,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """ prepareBuildGradleFile(initialBuildGradle) @@ -348,6 +364,7 @@ class JooqDockerPluginSpec extends Specification { def initialBuildGradle = """ plugins { + java id("com.revolut.jooq-docker") } @@ -366,11 +383,13 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """ def updatedBuildGradle = """ plugins { + java id("com.revolut.jooq-docker") } @@ -389,6 +408,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """ copyResource("/V01__init_multiple_schemas.sql", "src/main/resources/db/migration/V01__init_multiple_schemas.sql") @@ -426,6 +446,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -441,6 +462,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init.sql", "src/main/resources/db/migration/V01__init.sql") @@ -462,6 +484,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -499,6 +522,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("mysql:mysql-connector-java:8.0.15") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init_mysql.sql", "src/main/resources/db/migration/V01__init_mysql.sql") @@ -520,6 +544,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -535,6 +560,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init_with_placeholders.sql", "src/main/resources/db/migration/V01__init_with_placeholders.sql") @@ -556,6 +582,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -571,6 +598,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init.sql", "src/main/resources/db/migration/V01__init.sql") @@ -594,6 +622,7 @@ class JooqDockerPluginSpec extends Specification { buildGradleFile.write( """ plugins { + id "java" id "com.revolut.jooq-docker" } @@ -612,6 +641,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc "org.postgresql:postgresql:42.2.5" + implementation "org.jooq:jooq:3.14.8" } """) copyResource("/V01__init_with_placeholders.sql", "src/main/resources/db/migration/V01__init_with_placeholders.sql") @@ -636,6 +666,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -651,6 +682,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init.sql", "src/main/resources/db/migration/V01__init.sql") @@ -674,6 +706,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -689,6 +722,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init.sql", "src/main/resources/db/migration/V01__init.sql") @@ -708,11 +742,11 @@ class JooqDockerPluginSpec extends Specification { Files.notExists(generatedFlywayClass) } - def "exclude flyway schema history given custom Flyway table name"() { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -729,6 +763,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init.sql", "src/main/resources/db/migration/V01__init.sql") @@ -748,11 +783,11 @@ class JooqDockerPluginSpec extends Specification { Files.notExists(generatedCustomFlywayClass) } - def "exclude flyway schema history without overriding existing excludes"() { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -772,6 +807,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init_multiple_schemas.sql", "src/main/resources/db/migration/V01__init_multiple_schemas.sql") @@ -797,6 +833,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -812,6 +849,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init.sql", "src/main/resources/db/migration/V01__init.sql") @@ -928,6 +966,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } apply(plugin = "java") @@ -1076,6 +1115,7 @@ class JooqDockerPluginSpec extends Specification { configureLocalGradleCache(); prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -1085,6 +1125,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init.sql", "src/main/resources/db/migration/V01__init.sql") @@ -1132,6 +1173,7 @@ class JooqDockerPluginSpec extends Specification { import org.jooq.meta.jaxb.ForcedType plugins { + java id("com.revolut.jooq-docker") } @@ -1152,6 +1194,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """ def updatedBuildFile = @@ -1159,6 +1202,7 @@ class JooqDockerPluginSpec extends Specification { import org.jooq.meta.jaxb.ForcedType plugins { + java id("com.revolut.jooq-docker") } @@ -1179,6 +1223,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """ prepareBuildGradleFile(initialBuildGradle) @@ -1218,6 +1263,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -1233,6 +1279,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init_multiple_schemas.sql", "src/main/resources/db/migration/V01__init_multiple_schemas.sql") @@ -1258,6 +1305,7 @@ class JooqDockerPluginSpec extends Specification { given: prepareBuildGradleFile(""" plugins { + java id("com.revolut.jooq-docker") } @@ -1275,6 +1323,7 @@ class JooqDockerPluginSpec extends Specification { dependencies { jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.14.8") } """) copyResource("/V01__init.sql", "src/main/resources/db/migration/V01__init.sql") @@ -1294,6 +1343,55 @@ class JooqDockerPluginSpec extends Specification { Files.exists(generatedFlywayClass) } + def "picks up jooq version from project dependencies"() { + given: + prepareBuildGradleFile(""" + plugins { + java + id("com.revolut.jooq-docker") + } + + repositories { + mavenCentral() + } + + dependencies { + jdbc("org.postgresql:postgresql:42.2.5") + implementation("org.jooq:jooq:3.13.6") + implementation("javax.annotation:javax.annotation-api:1.3.2") + } + """) + copyResource("/V01__init.sql", "src/main/resources/db/migration/V01__init.sql") + writeProjectFile("src/main/java/com/test/Main.java", + """ + package com.test; + + import static org.jooq.generated.Tables.FOO; + + public class Main { + public static void main(String[] args) { + System.out.println(FOO.ID.getName()); + } + } + """); + + when: + def result = GradleRunner.create() + .withProjectDir(projectDir) + .withPluginClasspath() + .forwardOutput() + .withArguments("classes") + .build() + + then: + result.task(":generateJooqClasses").outcome == SUCCESS + result.task(":classes").outcome == SUCCESS + def generatedFooClass = Paths.get(projectDir.getPath(), "build/generated-jooq/org/jooq/generated/tables/Foo.java") + def mainClass = Paths.get(projectDir.getPath(), "build/classes/java/main/com/test/Main.class") + Files.exists(generatedFooClass) + Files.exists(mainClass) + } + def configureLocalGradleCache() { File localBuildCacheDirectory = temporaryFolder.newFolder(); def settingsGradleFile = new File(projectDir, "settings.gradle.kts")