casid / jte

Secure and speedy templates for Java and Kotlin.
https://jte.gg
Apache License 2.0
748 stars 56 forks source link

java.lang.NoClassDefFoundError: gg/jte/html/HtmlTemplateOutput #280

Closed venkatwilliams closed 11 months ago

venkatwilliams commented 11 months ago

Handler dispatch failed: java.lang.NoClassDefFoundError: gg/jte/html/HtmlTemplateOutput] with root cause

java.lang.ClassNotFoundException: gg.jte.html.HtmlTemplateOutput at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445) ~[na:na] at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:592) ~[na:na] at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525) ~[na:na] at java.base/java.lang.Class.getDeclaredMethods0(Native Method) ~[na:na] at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3402) ~[na:na] at java.base/java.lang.Class.getDeclaredMethods(Class.java:2504) ~[na:na] at gg.jte.runtime.Template.findRenderMethods(Template.java:69) ~[jte-runtime-3.1.0.jar!/:na] at gg.jte.runtime.Template.(Template.java:26) ~[jte-runtime-3.1.0.jar!/:na] at gg.jte.runtime.TemplateLoader.load(TemplateLoader.java:26) ~[jte-runtime-3.1.0.jar!/:na] at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708) ~[na:na] at gg.jte.TemplateEngine.resolveTemplate(TemplateEngine.java:339) ~[jte-runtime-3.1.0.jar!/:na] at gg.jte.TemplateEngine.render(TemplateEngine.java:228) ~[jte-runtime-3.1.0.jar!/:na]

private static Path JTE_FILE_PATH = Path.of("src", "main", "resources", "jte"); private static Path JTE_PRECOMPILED_FILE_PATH = Path.of("jte-classes");

if(activeProfile != null) {
   CodeResolver codeResolver = new DirectoryCodeResolver(JTE_FILE_PATH);
   this.templateEngine = TemplateEngine.create(codeResolver, ContentType.Html);
} else {
  this.templateEngine = TemplateEngine.createPrecompiled(JTE_PRECOMPILED_FILE_PATH, ContentType.Html);
}

I can see the dependent libraries in the jar file.

inflated: META-INF/build-info.properties created: BOOT-INF/classes/jte/ inflated: BOOT-INF/classes/jte/hello.jte inflated: BOOT-INF/classes/application.properties extracted: BOOT-INF/lib/jte-extension-api-3.1.0.jar extracted: BOOT-INF/lib/jte-runtime-3.1.0.jar extracted: BOOT-INF/lib/jte-3.1.0.jar

build.gradle file


dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.projectlombok:lombok:1.18.20'
    implementation 'junit:junit:4.13.1'
    compileOnly 'org.projectlombok:lombok' // Plugin to remove boilerplate code
    annotationProcessor 'org.projectlombok:lombok'
    implementation 'com.google.cloud:google-cloud-aiplatform:3.24.0'
    implementation 'com.google.code.gson:gson:2.10.1'
    implementation 'org.postgresql:postgresql:42.6.0'
    implementation 'com.pgvector:pgvector:0.1.3'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'com.google.cloud:spring-cloud-gcp-starter-secretmanager:4.7.2'
    implementation 'com.google.cloud:spring-cloud-gcp-starter:4.7.2'

    implementation 'org.apache.commons:commons-dbcp2:2.9.0'
    implementation 'org.apache.tomcat:tomcat-jdbc:10.1.13'

    implementation 'gg.jte:jte:3.1.0'
    implementation 'com.puppycrawl.tools:checkstyle:10.12.3'
    testImplementation 'com.h2database:h2'

    implementation 'org.springframework.boot:spring-boot-starter-validation'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
}

tasks.precompileJte {
    sourceDirectory = Paths.get(project.projectDir.absolutePath, "src", "main", "resources", "jte")
    targetDirectory = Paths.get(project.projectDir.absolutePath, "jte-classes")
    compilePath = sourceSets.main.runtimeClasspath
    contentType = ContentType.Html
}

tasks.precompileJte {
    dependsOn(tasks.compileJava)
}

tasks.test {
    dependsOn(tasks.precompileJte)
}
casid commented 11 months ago

Hi, from the stacktrace I assume the error happens in the TemplateEngine.createPrecompiled branch.

When this happens with Spring Boot it usually is a classloader problem. Can you try to pass the Spring Boot classloader like this? TemplateEngine.createPrecompiled(JTE_PRECOMPILED_FILE_PATH, ContentType.Html, getClass().getClassLoader());

venkatwilliams commented 11 months ago

With this change it is working. TemplateEngine.createPrecompiled(JTE_PRECOMPILED_FILE_PATH, ContentType.Html, getClass().getClassLoader());

Want to know the best practice in managing "jte-classes" code.

  1. Is it good to commit the generated classes in github or generate them every build time?
  2. Is it good practice to include jte generated classes along with src/main/java classes inside the jar file?
  3. If so how do do in this in Gradle project?
  4. If we include jte-classes as part of of jar file what would be the path in place of _JTE_PRECOMPILED_FILEPATH
casid commented 11 months ago

Glad it is working!

Personally, I put the jte source files in something like src/main/jte (like we do it with Java files)

If you want a self contained jar, you can use the generate Gradle task as described here: https://github.com/casid/jte/blob/main/DOCUMENTATION.md#using-the-application-class-loader-since-120. This way the Gradle task only generates a Java file for each jte file and leaves compilation to the regular Java compiler. You can specify which package the jte templates be located in, but you don't have to. With the default config, you would just create the template engine like this: TemplateEngine templateEngine = TemplateEngine.createPrecompiled(ContentType.Html);(I think in this case there won't be a need to specify the Spring classloader either)

venkatwilliams commented 11 months ago

Thank you very much @casid both the suggestions worked fine.

Able to include jte generated classes inside jar file using the gradle task. https://github.com/casid/jte/blob/main/DOCUMENTATION.md#using-the-application-class-loader-since-120