ultraq / thymeleaf-layout-dialect

A dialect for Thymeleaf that lets you build layouts and reusable templates in order to improve code reuse

Home Page:https://ultraq.github.io/thymeleaf-layout-dialect/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Thymeleaf and Micronaut Views error when using Layout dialect

cosmin-marginean opened this issue · comments

This is a repost of this https://stackoverflow.com/posts/70823659/edit, we're not quite sure yet if this is something that belongs as a ticket here so please feel free to comment and action as necessary.

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.

Upon further investigation, we discovered that this is related to this bug in the shadow jar plugin: johnrengelman/shadow#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 johnrengelman/shadow#490 , there is a workaround, but it's deeply unpleasant.

Closing, as this is clearly not related to this library exclusively.