Thymeleaf and Micronaut Views error when using Layout dialect

Viewed 178

We are running Micronaut with Thymeleaf views and the Layout dialect (we add it manually by overriding Micronaut's ThymeleafFactory). Below are the dependencies (Micronaut version is 3.2.7):

implementation 'io.micronaut.views:micronaut-views-core:3.1.2'
implementation 'io.micronaut.views:micronaut-views-thymeleaf:3.1.2'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.0.0'

The problematic code is this:

<html layout:decorate="~{/layout-top}">

This seems to work fine when running with ./gradlew run, but crashes when running from a fat (shadow) jar using java -jar .... This would point to classpath issues, but we couldn't figure out what would those be.

Below the error message when running the shadow jar:

Caused by: groovy.lang.MissingMethodException: No signature of method: io.micronaut.views.thymeleaf.WebEngineContext.getOrCreate() is applicable for argument types: (String, nz.net.ultraq.thymeleaf.layoutdialect.context.extensions.IContextExtensions$_getPrefixForDialect_closure1) values: [DialectPrefix::org.thymeleaf.standard.StandardDialect, nz.net.ultraq.thymeleaf.layoutdialect.context.extensions.IContextExtensions$_getPrefixForDialect_closure1@26b0c4d0]
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:70)
    at org.codehaus.groovy.runtime.callsite.PojoMetaClassSite.call(PojoMetaClassSite.java:46)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:148)
    at nz.net.ultraq.thymeleaf.layoutdialect.context.extensions.IContextExtensions.getPrefixForDialect(IContextExtensions.groovy:54)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod.invoke(ReflectionMetaMethod.java:54)
    at org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod.invoke(NewInstanceMetaMethod.java:54)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:247)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:56)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:139)
    at nz.net.ultraq.thymeleaf.layoutdialect.models.extensions.IProcessableElementTagExtensions.equalsIgnoreXmlnsAndWith(IProcessableElementTagExtensions.groovy:60)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod.invoke(ReflectionMetaMethod.java:54)
    at org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod.invoke(NewInstanceMetaMethod.java:54)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:247)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:56)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:148)
    at nz.net.ultraq.thymeleaf.layoutdialect.decorators.DecorateProcessor.doProcess(DecorateProcessor.groovy:103)
    at org.thymeleaf.processor.element.AbstractAttributeModelProcessor.doProcess(AbstractAttributeModelProcessor.java:77)

We debugged this and isolated the failing code in nz.net.ultraq.thymeleaf.layoutdialect.context.extensions.IContextExtensions:

    static String getPrefixForDialect(IContext self, Class<IProcessorDialect> dialectClass) {
        return self.getOrCreate(DIALECT_PREFIX_PREFIX + dialectClass.name) { ->
            def dialectConfiguration = self.configuration.dialectConfigurations.find { dialectConfig ->
                return dialectClass.isInstance(dialectConfig.dialect)
            }
            return dialectConfiguration?.prefixSpecified ?
                    dialectConfiguration?.prefix :
                    dialectConfiguration?.dialect?.prefix
        }
    }

It seems that the IContext argument is not what's supposed to be, but we couldn't really find the root cause for this. Nor why this is behaving differently with the two different methods of running the same code.

1 Answers

Upon further investigation, we discovered that this is related to this bug in the shadow jar plugin: https://github.com/johnrengelman/shadow/issues/490

The library thymeleaf-layout-dialect is using a nz.net.ultraq.extensions:groovy-extensions:1.1.0 which, in turn, registers some Groovy extensions through META-INF/services/org.codehaus.groovy.runtime.ExtensionModule

The shadow jar plugin doesn't handle these correctly (it only handles META-INF/groovy/... paths).

As per ticket comments here https://github.com/johnrengelman/shadow/issues/490 , there is a workaround, but it's deeply unpleasant.

Related