micronaut-views icon indicating copy to clipboard operation
micronaut-views copied to clipboard

Thymeleaf and Velocity Map access fails in native image

Open burtbeckwith opened this issue 2 years ago • 3 comments

Expected Behavior

No response

Actual Behaviour

Basically the same error as in https://github.com/micronaut-projects/micronaut-views/issues/257

00:01:05.154 [io-executor-thread-1] ERROR org.thymeleaf.TemplateEngine - [THYMELEAF][io-executor-thread-1] Exception processing template "home": Exception evaluating OGNL expression: "security.attributes.get('foo')" (template: "home" - line 7, col 16)
org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating OGNL expression: "security.attributes.get('foo')" (template: "home" - line 7, col 16)
        at org.thymeleaf.standard.expression.OGNLVariableExpressionEvaluator.evaluate(OGNLVariableExpressionEvaluator.java:191)
        at org.thymeleaf.standard.expression.OGNLVariableExpressionEvaluator.evaluate(OGNLVariableExpressionEvaluator.java:95)
        at org.thymeleaf.standard.expression.VariableExpression.executeVariableExpression(VariableExpression.java:166)
        at org.thymeleaf.standard.expression.SimpleExpression.executeSimple(SimpleExpression.java:66)
        at org.thymeleaf.standard.expression.Expression.execute(Expression.java:109)
        at org.thymeleaf.standard.expression.Expression.execute(Expression.java:138)
        at org.thymeleaf.standard.processor.AbstractStandardExpressionAttributeTagProcessor.doProcess(AbstractStandardExpressionAttributeTagProcessor.java:144)
        at org.thymeleaf.processor.element.AbstractAttributeTagProcessor.doProcess(AbstractAttributeTagProcessor.java:74)
        at org.thymeleaf.processor.element.AbstractElementTagProcessor.process(AbstractElementTagProcessor.java:95)
        at org.thymeleaf.util.ProcessorConfigurationUtils$ElementTagProcessorWrapper.process(ProcessorConfigurationUtils.java:633)
        at org.thymeleaf.engine.ProcessorTemplateHandler.handleOpenElement(ProcessorTemplateHandler.java:1314)
        at org.thymeleaf.engine.OpenElementTag.beHandled(OpenElementTag.java:205)
        at org.thymeleaf.engine.TemplateModel.process(TemplateModel.java:136)
        at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:661)
        at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098)
        at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1067)
        at io.micronaut.views.thymeleaf.ThymeleafViewsRenderer.render(ThymeleafViewsRenderer.java:123)
        at io.micronaut.views.thymeleaf.ThymeleafViewsRenderer.lambda$render$0(ThymeleafViewsRenderer.java:110)
        at io.micronaut.core.io.Writable.writeTo(Writable.java:77)
        at io.micronaut.http.server.netty.RoutingInBoundHandler.lambda$encodeHttpResponse$6(RoutingInBoundHandler.java:946)
        at io.micronaut.scheduling.instrument.InvocationInstrumenterWrappedRunnable.run(InvocationInstrumenterWrappedRunnable.java:47)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at java.lang.Thread.run(Thread.java:829)
        at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:597)
        at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:194)
Caused by: ognl.MethodFailedException: Method "get" failed for object {foo=bar}
        at ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:1932)
        at ognl.ObjectMethodAccessor.callMethod(ObjectMethodAccessor.java:68)
        at ognl.OgnlRuntime.callMethod(OgnlRuntime.java:1996)
        at ognl.ASTMethod.getValueBody(ASTMethod.java:91)
        at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
        at ognl.SimpleNode.getValue(SimpleNode.java:258)
        at ognl.ASTChain.getValueBody(ASTChain.java:141)
        at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
        at ognl.SimpleNode.getValue(SimpleNode.java:258)
        at ognl.Ognl.getValue(Ognl.java:537)
        at ognl.Ognl.getValue(Ognl.java:501)
        at org.thymeleaf.standard.expression.OGNLVariableExpressionEvaluator.executeExpression(OGNLVariableExpressionEvaluator.java:326)
        at org.thymeleaf.standard.expression.OGNLVariableExpressionEvaluator.evaluate(OGNLVariableExpressionEvaluator.java:170)
        ... 25 common frames omitted
Caused by: java.lang.NoSuchMethodException: java.util.HashMap.get(java.lang.String)
        at ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:1873)
        ... 37 common frames omitted
Exception in thread "io-executor-thread-1" io.micronaut.views.exceptions.ViewRenderingException: Error rendering Thymeleaf view [home]: Exception evaluating OGNL expression: "security.attributes.get('foo')" (template: "home" - line 7, col 16)
        at io.micronaut.views.thymeleaf.ThymeleafViewsRenderer.render(ThymeleafViewsRenderer.java:125)
        at io.micronaut.views.thymeleaf.ThymeleafViewsRenderer.lambda$render$0(ThymeleafViewsRenderer.java:110)
        at io.micronaut.core.io.Writable.writeTo(Writable.java:77)
        at io.micronaut.http.server.netty.RoutingInBoundHandler.lambda$encodeHttpResponse$6(RoutingInBoundHandler.java:946)
        at io.micronaut.scheduling.instrument.InvocationInstrumenterWrappedRunnable.run(InvocationInstrumenterWrappedRunnable.java:47)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at java.lang.Thread.run(Thread.java:829)
        at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:597)
        at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:194)
Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating OGNL expression: "security.attributes.get('foo')" (template: "home" - line 7, col 16)
        at org.thymeleaf.standard.expression.OGNLVariableExpressionEvaluator.evaluate(OGNLVariableExpressionEvaluator.java:191)
        at org.thymeleaf.standard.expression.OGNLVariableExpressionEvaluator.evaluate(OGNLVariableExpressionEvaluator.java:95)
        at org.thymeleaf.standard.expression.VariableExpression.executeVariableExpression(VariableExpression.java:166)
        at org.thymeleaf.standard.expression.SimpleExpression.executeSimple(SimpleExpression.java:66)
        at org.thymeleaf.standard.expression.Expression.execute(Expression.java:109)
        at org.thymeleaf.standard.expression.Expression.execute(Expression.java:138)
        at org.thymeleaf.standard.processor.AbstractStandardExpressionAttributeTagProcessor.doProcess(AbstractStandardExpressionAttributeTagProcessor.java:144)
        at org.thymeleaf.processor.element.AbstractAttributeTagProcessor.doProcess(AbstractAttributeTagProcessor.java:74)
        at org.thymeleaf.processor.element.AbstractElementTagProcessor.process(AbstractElementTagProcessor.java:95)
        at org.thymeleaf.util.ProcessorConfigurationUtils$ElementTagProcessorWrapper.process(ProcessorConfigurationUtils.java:633)
        at org.thymeleaf.engine.ProcessorTemplateHandler.handleOpenElement(ProcessorTemplateHandler.java:1314)
        at org.thymeleaf.engine.OpenElementTag.beHandled(OpenElementTag.java:205)
        at org.thymeleaf.engine.TemplateModel.process(TemplateModel.java:136)
        at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:661)
        at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098)
        at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1067)
        at io.micronaut.views.thymeleaf.ThymeleafViewsRenderer.render(ThymeleafViewsRenderer.java:123)
        ... 9 more
Caused by: ognl.MethodFailedException: Method "get" failed for object {foo=bar} [java.lang.NoSuchMethodException: java.util.HashMap.get(java.lang.String)]
        at ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:1932)
        at ognl.ObjectMethodAccessor.callMethod(ObjectMethodAccessor.java:68)
        at ognl.OgnlRuntime.callMethod(OgnlRuntime.java:1996)
        at ognl.ASTMethod.getValueBody(ASTMethod.java:91)
        at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
        at ognl.SimpleNode.getValue(SimpleNode.java:258)
        at ognl.ASTChain.getValueBody(ASTChain.java:141)
        at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
        at ognl.SimpleNode.getValue(SimpleNode.java:258)
        at ognl.Ognl.getValue(Ognl.java:537)
        at ognl.Ognl.getValue(Ognl.java:501)
        at org.thymeleaf.standard.expression.OGNLVariableExpressionEvaluator.executeExpression(OGNLVariableExpressionEvaluator.java:326)
        at org.thymeleaf.standard.expression.OGNLVariableExpressionEvaluator.evaluate(OGNLVariableExpressionEvaluator.java:170)
        ... 25 more
Caused by: java.lang.NoSuchMethodException: java.util.HashMap.get(java.lang.String)
        at ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:1873)
        ... 37 more

Steps To Reproduce

  • Create an app with features views-thymeleaf and graalvm mn create-app com.example.demo --features=views-thymeleaf,graalvm

  • Create a controller with a method that creates a model similar to what's available when using security, i.e. a security map containing an attributes map:

package com.example;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.views.View;

import java.util.HashMap;
import java.util.Map;

@Controller
class HomeController {

    @View("home")
    @Get
    Map<String, Object> index() {
        Map<String, Object> attributes = new HashMap<>();
        attributes.put("foo", "bar");

        Map<String, Object> security = new HashMap<>();
        security.put("attributes", attributes);

        Map<String, Object> model = new HashMap<>();
        model.put("security", security);

        return model;
    }
}
  • Create src/main/resources/views/home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<body>

<h2>foo: <span th:text="${security.attributes.get('foo')}"></span></h2>

</body>
</html>
  • Start the app and open http://localhost:8080/ in a browser and the output should be foo: bar
  • Build a native image, run the app and open http://localhost:8080/ and it fails

Environment Information

GraalVM version 22.0.0.2.r11-grl

Example Application

No response

Version

3.3.4

burtbeckwith avatar Mar 08 '22 05:03 burtbeckwith

I tried switching to Velocity and that failed too, but with different output - instead of a stacktrace, the expression is rendered unprocessed to the HTML, e.g. Username: $security.attributes.get('user_displayname')

burtbeckwith avatar Mar 08 '22 05:03 burtbeckwith

You can add:

package com.example;

import io.micronaut.core.annotation.TypeHint;

import java.util.HashMap;
import java.util.Map;

@TypeHint(value = {Map.class, HashMap.class}, accessType = TypeHint.AccessType.ALL_DECLARED_METHODS)
public class ThymleafGraalConfig {
}

which will generate at build/classes/java/main/META-INF/native-image/xxx/xxx/reflect-config.json:

[ {
  "name" : "java.util.Map",
  "allDeclaredMethods" : true
}, {
  "name" : "java.util.HashMap",
  "allDeclaredMethods" : true
} ]

One way to see what you need to generate is to add the following snippet to build.gradle.

run {
    jvmArgs = [
        "-agentlib:native-image-agent=experimental-class-loader-support,config-output-dir=tmp",
        "-Dorg.graalvm.nativeimage.imagecode=agent"
    ]
}

run the app with a GraalVM JDK but JIT:

% sdk use java 22.1.0.r11-grl
% ./gradlew run

A folder tmp is created by the GraalVM agent with information about the necessary reflect-config.json

sdelamo avatar May 25 '22 04:05 sdelamo

Does the snippet with which you see what to generate still apply? I'm not aware of the changes since 1,5y in that area.

gijsleussink avatar Dec 27 '23 12:12 gijsleussink