etiennestuder/gradle-jooq-plugin

java.lang.ClassNotFoundException: jakarta.xml.bind.annotation.XmlSchema

LinkedList opened this issue ยท 15 comments

Hi ๐Ÿ‘‹
Since the upgrade to v 7.0 the generator needs explicit dependency to jakarta.xml.bind:jakarta.xml.bind-api,
I don't know if this is intentional but I couldn't find anything in the README.

Exception in thread "main" java.lang.NoClassDefFoundError: jakarta/xml/bind/annotation/XmlSchema
	at org.jooq.util.jaxb.tools.MiniJAXB.getNamespace(MiniJAXB.java:400)
	at org.jooq.util.jaxb.tools.MiniJAXB.addDefaultNamespace(MiniJAXB.java:188)
	at org.jooq.util.jaxb.tools.MiniJAXB.unmarshal0(MiniJAXB.java:175)
	at org.jooq.util.jaxb.tools.MiniJAXB.unmarshal(MiniJAXB.java:161)
	at org.jooq.codegen.GenerationTool.load(GenerationTool.java:1180)
	at org.jooq.codegen.GenerationTool.main(GenerationTool.java:203)
Caused by: java.lang.ClassNotFoundException: jakarta.xml.bind.annotation.XmlSchema
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
	... 6 more
Caused by: java.lang.ClassNotFoundException: jakarta.xml.bind.annotation.XmlSchema

the fix is just adding the dependency: jooqGenerator("jakarta.xml.bind:jakarta.xml.bind-api:3.0.1")

Hm, I cannot reproduce it. It should be pulled in as a transitive dependency. All integration tests and examples are working fine. I'll try to investigate. Thanks for reporting.

Do you observe the same thing when running the examples, for exmaple, this one?
https://github.com/etiennestuder/gradle-jooq-plugin/tree/master/example/use_groovy_dsl

@etiennestuder It does work in the example project ๐Ÿค” I will investigate why it's happening with our current setup..

@LinkedList What version of Gradle and jOOQ are you using in your real project?

@etiennestuder
We have Gradle 7.2, jooq is set to 3.16.3 but I am onto something. Gradle is somehow downgrading the bind-api dependency to 2.3.3

 $ gradle -q dependencyInsight --configuration jooqGenerator --dependency jakarta.xml.bind-api                                                                                                                                                                                                                                                                                                                                                                          
executing gradlew instead of gradle
jakarta.xml.bind:jakarta.xml.bind-api:2.3.3 (selected by rule)
   variant "runtime" [
      org.gradle.status          = release (not requested)
      org.gradle.usage           = java-runtime (not requested)
      org.gradle.libraryelements = jar (not requested)
      org.gradle.category        = library (not requested)
   ]

jakarta.xml.bind:jakarta.xml.bind-api:3.0.0 -> 2.3.3
\--- org.jooq:jooq:3.16.3
     +--- org.jooq:jooq-codegen:3.16.3
     |    \--- jooqGenerator (requested org.jooq:jooq-codegen)
     \--- org.jooq:jooq-meta:3.16.3
          \--- org.jooq:jooq-codegen:3.16.3 (*)

(*) - dependencies omitted (listed previously)

Hm, this is not what I'm seeing on my projects nor the example projects.

I see:

$ gr -q dependencyInsight --configuration jooqGenerator --dependency jakarta.xml.bind-api 
jakarta.xml.bind:jakarta.xml.bind-api:3.0.0
   variant "runtime" [
      org.gradle.status          = release (not requested)
      org.gradle.usage           = java-runtime (not requested)
      org.gradle.libraryelements = jar (not requested)
      org.gradle.category        = library (not requested)
   ]

jakarta.xml.bind:jakarta.xml.bind-api:3.0.0
\--- org.jooq:jooq:3.16.3
     +--- org.jooq:jooq-codegen:3.16.3
     |    \--- jooqGenerator (requested org.jooq:jooq-codegen)
     \--- org.jooq:jooq-meta:3.16.3
          \--- org.jooq:jooq-codegen:3.16.3 (*)

(*) - dependencies omitted (listed previously)

Do have some dependency rules specifiec?

Running your command above with --scan might help since you can check the build scan on scans.gradle.com for possibly more dependency insights.

Your dependency rule likely comes in via a configurations.all invocation. Maybe you use the Spring dependency mgmt plugin, or the Nebula plugin, or something like this that brings in dependency rules that enforce certain versions. Or maybe you have such rules in your own build.

Note that there is also such a rule in the gradle-jooq-plugin, but it only applies to the jOOQ libraries:
https://github.com/alextu/gradle-jooq-plugin/blob/master/src/main/groovy/nu/studer/gradle/jooq/JooqPlugin.java#L96

Not sure if this helps, but my build is experiencing the exact same issue, with the exact same results from dependencyInsight. I'd like to add my build.gradle file to the discussion to see if it sheds any light on the subject.

plugins {
    id 'java'
    id 'jacoco'
    id 'maven-publish'
    id 'org.springframework.boot' version '2.3.1.RELEASE'
    id 'com.github.johnrengelman.shadow' version '5.2.0'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'com.jfrog.artifactory' version '4.16.0'
    id 'nu.studer.jooq' version '7.0'
}

ext {
    assertjVersion = '3.22.0'
    awsSdkVersion = '1.11.812'
    awsLambdaEventsVersion = '2.2.9' //version 3.1.0 does not deserialize S3Event properly
    awsLambdaCoreVersion = '1.2.1'
    geojsonJacksonVersion = '1.14'
    lombokVersion = '1.18.22'
    mapstructVersion = '1.4.2.Final'
    postgresVersion = '42.3.2'
    slf4jVersion = '1.7.32'
    springCloudFunctionVersion = '3.0.6.RELEASE'
    springCloudVersion = '2.2.6.RELEASE'
    testNGVersion = '7.4.0'
    msDbSchemaVersion = '0.10.10'
    jooqVersion = '3.16.3'
}

dependencies {
    implementation("org.mapstruct:mapstruct:${mapstructVersion}")
    annotationProcessor("org.mapstruct:mapstruct-processor:${mapstructVersion}")
    annotationProcessor("org.projectlombok:lombok:${lombokVersion}")
    compileOnly("org.projectlombok:lombok:${lombokVersion}")
    implementation("org.springframework.cloud:spring-cloud-function-context")
    implementation("org.springframework.cloud:spring-cloud-starter-function-web")
    implementation("org.slf4j:slf4j-api:${slf4jVersion}")
    implementation("de.grundid.opendatalab:geojson-jackson:${geojsonJacksonVersion}")


    implementation 'org.liquibase:liquibase-core'
    implementation("org.jooq:jooq:${jooqVersion}")
    implementation("org.jooq:jooq-codegen:${jooqVersion}")

    implementation('org.springframework.boot:spring-boot-starter-jooq')
    implementation('org.springframework.boot:spring-boot-starter-data-rest')
    implementation('org.hibernate:hibernate-core:5.6.5.Final')
    implementation("org.postgresql:postgresql:${postgresVersion}")
    jooqGenerator("org.postgresql:postgresql:${postgresVersion}")

    implementation("com.amazonaws:aws-lambda-java-events:${awsLambdaEventsVersion}")
    implementation("com.amazonaws:aws-lambda-java-core:${awsLambdaCoreVersion}")
    implementation("com.amazonaws:aws-java-sdk-s3:${awsSdkVersion}")

    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation("org.testng:testng:${testNGVersion}")
    testImplementation("org.assertj:assertj-core:${assertjVersion}")
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-function-dependencies:${springCloudFunctionVersion}"
    }
}


repositories {
    maven {
        url "https://myrepo"
        credentials {
            username "user"
            password "pass"
        }
    }
    mavenCentral()
}

import org.jooq.meta.jaxb.Logging

jooq {
    version = '3.16.3'
    edition = nu.studer.gradle.jooq.JooqEdition.OSS

    configurations {
        main {
            generateSchemaSourceOnCompilation = true
            generationTool {
                logging = Logging.DEBUG
                jdbc {
                    driver = 'org.postgresql.Driver'
                    url = 'jdbc:postgresql://localhost:5432/database'
                    user = 'database'
                    password = 'database'
                }
                generator {
                    name = 'org.jooq.codegen.DefaultGenerator'
                    database {
                        name = 'org.jooq.meta.postgres.PostgresDatabase'
                        inputSchema = 'public'
                        includes = '''
                                        omitted
                        '''
                        forcedTypes {
                            forcedType {
                                name = 'varchar'
                                includeExpression = '.*'
                                includeTypes = 'JSONB?'
                            }
                            forcedType {
                                name = 'varchar'
                                includeExpression = '.*'
                                includeTypes = 'INET'
                            }
                        }
                    }
                    generate {
                        deprecated = true
                        records = true
                        immutablePojos = true
                        fluentSetters = true
                    }
                    target {
                        packageName = 'com.projectname.db'
                        directory = 'build/generated-src/jooq/main'  // default (can be omitted)
                    }
                    strategy.name = 'org.jooq.codegen.DefaultGeneratorStrategy'
                }
            }
        }

    }
}

tasks.generateJooq.with {
//    def out = new ByteArrayOutputStream()
    javaExecSpec = { JavaExecSpec s ->
//        s.standardOutput = out
//        s.errorOutput = out
        s.ignoreExitValue = true
        s.jvmArgs '-Xmx512M'
    }
    execResultHandler = {
        ExecResult r ->
            if (r.exitValue != 0) {
                throw new RuntimeException('jOOQ source code generation failed:\n\n')
            }
    }
}

Your dependency rule likely comes in via a configurations.all invocation. Maybe you use the Spring dependency mgmt plugin, or the Nebula plugin, or something like this that brings in dependency rules that enforce certain versions. Or maybe you have such rules in your own build.

Note that there is also such a rule in the gradle-jooq-plugin, but it only applies to the jOOQ libraries: https://github.com/alextu/gradle-jooq-plugin/blob/master/src/main/groovy/nu/studer/gradle/jooq/JooqPlugin.java#L96

@etiennestuder That seems correct, when I removed spring dependency management, the code generator started working (although my build broke). Upgrading to the latest (1.0.11.RELEASE) still produces the same issue.

As a workaround, I just put the plugin into isolation:

plugins {
    id 'java'
    id 'jacoco'
    id 'maven-publish'
    id 'org.springframework.boot' version '2.3.1.RELEASE'
    id 'com.github.johnrengelman.shadow' version '5.2.0'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE' apply false
    id 'com.jfrog.artifactory' version '4.16.0'
    id 'nu.studer.jooq' version '7.0'
}

build {

    apply plugin: 'io.spring.dependency-management'

    dependencyManagement {
        imports {
            mavenBom "org.springframework.cloud:spring-cloud-function-dependencies:${springCloudFunctionVersion}"
        }
    }

}

$ ./gradlew -q dependencyInsight --configuration jooqGenerator --dependency jakarta.xml.bind-api 
jakarta.xml.bind:jakarta.xml.bind-api:3.0.0
   variant "runtime" [
      org.gradle.status          = release (not requested)
      org.gradle.usage           = java-runtime (not requested)
      org.gradle.libraryelements = jar (not requested)
      org.gradle.category        = library (not requested)
   ]

jakarta.xml.bind:jakarta.xml.bind-api:3.0.0
\--- org.jooq:jooq:3.16.3
     +--- org.jooq:jooq-codegen:3.16.3
     |    \--- jooqGenerator (requested org.jooq:jooq-codegen)
     \--- org.jooq:jooq-meta:3.16.3
          \--- org.jooq:jooq-codegen:3.16.3 (*)

(*) - dependencies omitted (listed previously)

Yep, as suspected. Thanks for the report, @LinkedList and the additional confirmation @mbrinkg8tr.

I'll close this issue now. The gradle-jooq-plugin is applying normal dependency declarations and it is outside of this plugin control what version rules other plugins enforce.

I've added a section to the README should this come up again for someone.

Thank you @etiennestuder and @mbrinkg8tr for help analysing this ๐Ÿ‘

I've added a section to the README should this come up again for someone.

Any workaround for this?

 .\gradlew -q dependencyInsight --configuration jooqGenerator --dependency jakarta.xml.bind-api 
jakarta.xml.bind:jakarta.xml.bind-api:2.3.3 (selected by rule)
   variant "runtime" [
      org.gradle.status          = release (not requested)
      org.gradle.usage           = java-runtime (not requested)
      org.gradle.libraryelements = jar (not requested)
      org.gradle.category        = library (not requested)
   ]

jakarta.xml.bind:jakarta.xml.bind-api:3.0.0 -> 2.3.3
\--- org.jooq:jooq:3.16.4
     +--- org.jooq:jooq-codegen:3.16.4
     |    \--- jooqGenerator (requested org.jooq:jooq-codegen)
     \--- org.jooq:jooq-meta:3.16.4
          \--- org.jooq:jooq-codegen:3.16.4 (*)

(*) - dependencies omitted (listed previously)

@hantsy explicitly adding the version should work

the fix is just adding the dependency: jooqGenerator("jakarta.xml.bind:jakarta.xml.bind-api:3.0.1")

@LinkedList Thanks.