This is an automated email from the ASF dual-hosted git repository. ddekany pushed a commit to branch 2.3-gae in repository https://gitbox.apache.org/repos/asf/freemarker.git
commit 33d5094b85160c7fcc94ef6c9708c83aca161230 Author: ddekany <[email protected]> AuthorDate: Thu Nov 13 18:27:13 2025 +0100 GraalVM native PR followup: Moved documentation from the README into the Manual. Also reworked it. --- README.md | 139 --------------------- freemarker-manual/src/main/docgen/en_US/book.xml | 152 ++++++++++++++++++++++- 2 files changed, 148 insertions(+), 143 deletions(-) diff --git a/README.md b/README.md index d1286692..9702df4b 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,6 @@ issue `.\gradlew publish`. Note that for this the following Gradle properties mu (in `gradle.properties`, or pass them via `-P<name>=<value>` arguments): `freemarker.signMethod`, `freemarker.deploy.apache.user`, `freemarker.deploy.apache.password`. - ### FreeMarker website related build tasks The website (the FreeMarker homepage) is build by the `freemarker-site` project, not this project (`freemaker`). Except, @@ -253,141 +252,3 @@ Gradle project. After that, it's recommended to set these preferences (based on - Project -> Properties -> FindBugs -> [x] Run Automatically - There should 0 errors. But sometimes the plugin fails to take the @SuppressFBWarnings annotations into account; then use Project -> Clean. - -### GraalVM Native Support - -Apache FreeMarker is compatible with Ahead-of-Time (AOT) compilation using GraalVM as of version 2.3.35. However, any custom Java objects or resources used in the data model must still be registered for reflection. - -Refer to the [GraalVM documentation](https://www.graalvm.org/latest/docs/) for more details, especially: - -- [Reflection in Native Image](https://www.graalvm.org/jdk21/reference-manual/native-image/dynamic-features/Reflection/) -- [Accessing Resources in Native Image](https://www.graalvm.org/jdk21/reference-manual/native-image/dynamic-features/Resources/) - -**TIP:** You can find many configuration samples in the [graalvm-reachability-metadata](https://github.com/oracle/graalvm-reachability-metadata) repository. - -Here is a sample usage guide for ApacheFreeMarker + GraalVM. - -To run the sample in classic Just In Time Way, we only need : - -* FreeMarkerGraalVMSample.java -* sample.ftl - -But for the Ahead Of Time application with GraalVM some additional configuration is required : - -* custom-reflect-config.json - -#### FreeMarkerGraalVMSample.java sample class - -```java -import freemarker.log.Logger; -import freemarker.template.Configuration; -import freemarker.template.Template; -import freemarker.template.TemplateException; - -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.util.HashMap; -import java.util.Map; - -public class FreeMarkerGraalVMSample { - - private final static Logger LOG = Logger.getLogger(FreeMarkerGraalVMSample.class.getName()); - - /* data model */ - public class Data { - private String description; - public String getDescription() { - return description; - } - public void setDescription(String description) { - this.description = description; - } - } - - private void handleTemplate(Writer writer, String templatePath, Map<String, Object> dataModel) throws IOException, TemplateException { - Configuration cfg = new Configuration( Configuration.VERSION_2_3_34 ); - cfg.setClassForTemplateLoading( FreeMarkerGraalVMSample.class, "/templates" ); - Template template = cfg.getTemplate( templatePath ); - template.process( dataModel, writer ); - } - - public void runSample() { - try ( StringWriter writer = new StringWriter() ) { - Map<String, Object> dataModel = new HashMap<>(); - Data data = new Data(); - data.setDescription( "FreeMarkerGraalVMSample" ); - dataModel.put("data", data); - handleTemplate( writer, "sample.ftl", dataModel ); - LOG.info( writer.toString() ); - } catch (Exception e) { - LOG.error( e.getMessage(), e ); - } - } - - public static void main(String[] args) { - FreeMarkerGraalVMSample sample = new FreeMarkerGraalVMSample(); - sample.runSample(); - } - -} -``` - -#### Apache FreeMarker template - -```ftl -<freemarker-graalvm-sample> - <freemarker-version>${.version}</freemarker-version> - <description>${data.description}</description> -</freemarker-graalvm-sample> -``` - -#### Reflection configuration, custom-reflect-config.json - -Refers to [Reflection in Native Image](https://www.graalvm.org/jdk21/reference-manual/native-image/dynamic-features/Reflection/) guide - -```json -[{ - "name" : "FreeMarkerGraalVMSample$Data", - "methods" : [ { - "name" : "<init>", - "parameterTypes" : [ ] - },{ - "name" : "getDescription", - "parameterTypes" : [ ] - } ] -}] -``` - -#### Build the native image - -```shell -#!/bin/bash - -# setting up environment -export BASEDIR=. -export CP=./lib/freemarker-gae-2.3.35-SNAPSHOT.jar:. - -# just in time application build -javac -cp ${CP} -d build ./src/FreeMarkerGraalVMSample.java - -# ahead of time application build -# -# -H:IncludeResources=^templates/.* -# will make the templates available to the native-image -# -# -H:ReflectionConfigurationFiles=./config/custom-reflect-config.json -# will setup reflection custom configuration -native-image \ - -cp "${CP}:build" \ - -H:Path=build \ - -H:Class=FreeMarkerGraalVMSample \ - -H:IncludeResources=^templates/.* \ - -H:+UnlockExperimentalVMOptions \ - -H:ReflectionConfigurationFiles=./config/custom-reflect-config.json \ - --no-fallback \ - --report-unsupported-elements-at-runtime - -# running the application -./build/freemarkergraalvmsample -``` \ No newline at end of file diff --git a/freemarker-manual/src/main/docgen/en_US/book.xml b/freemarker-manual/src/main/docgen/en_US/book.xml index 16803701..3c970135 100644 --- a/freemarker-manual/src/main/docgen/en_US/book.xml +++ b/freemarker-manual/src/main/docgen/en_US/book.xml @@ -20,10 +20,7 @@ <book conformance="docgen" version="5.0" xml:lang="en" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:ns5="http://www.w3.org/2000/svg" - xmlns:ns4="http://www.w3.org/1998/Math/MathML" - xmlns:ns3="http://www.w3.org/1999/xhtml" - xmlns:ns="http://docbook.org/ns/docbook"> +> <info> <title>Apache FreeMarker Manual</title> @@ -11568,6 +11565,134 @@ TemplateHashModel roundingModeEnums = </section> </section> + <section xml:id="pgui_misc_graalvm_native"> + <title>GraalVM native support</title> + + <para>Apache FreeMarker is compatible with Ahead-of-Time (AOT) + compilation using GraalVM as of version 2.3.35, but with two big + caveats:</para> + + <itemizedlist> + <listitem> + <para>Custom Java classes that are exposed to the templates via + the data model must be explicitly listed in the GraalVM native + reflection configuration, or else the FreeMarker templates will + not see their members. That's because the templates are not + compiled, and has to be able to discover the exposed classes on + runtime via Java reflection.</para> + </listitem> + + <listitem> + <para>If templates are stored as Java resources, the resources + also must be explicitly configured to be accessible on runtime, or + else FreeMarker will not find the templates.</para> + </listitem> + </itemizedlist> + + <para>Let's say you have this class:</para> + + <programlisting role="unspecified">package com.example; + +public class User { + private String name; + private String email; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +}</programlisting> + + <para>Then you put an instance of that into the data model with name + <literal>user</literal>, and access it from a template like this, + which you put into + <literal>src/main/resources/templates</literal>:</para> + + <programlisting role="template">Hello ${user.name}! + +Your e-mail address is: ${user.email}</programlisting> + + <para>Then you configure FreeMarker to load the templates from the + class path:</para> + + <programlisting role="unspecified">Configuration cfg = new Configuration(Configuration.VERSION_...); +... +cfg.setClassForTemplateLoading(this.getClass(), "/templates");</programlisting> + + <para>Under normal Java, all this would just work. But under GraalVM + native, if it finds the template at all, the template processing will + fail with <literal>freemarker.core.InvalidReferenceException</literal> + saying that <quote>user.name has evaluated to null or missing</quote>. + That's because FreeMarker tries to discover the methods of the + <literal>User</literal> class via Java reflection, but such + information is by default not available in GraalVM native. You need to + add a reflection configuration file like this, which we could put for + example into + <literal>src/main/graalvm-native-config/reflection-config.json</literal>:</para> + + <programlisting role="unspecified">[ + { + "name": "com.example.User", + "allPublicMethods": true + } +]</programlisting> + + <para>Then this has to be referred in the GraalVM native build. For + example in Gradle it would be something like this:</para> + + <programlisting role="unspecified">graalvmNative { + binaries.all { + ... + configurationFileDirectories.from(file("src/main/graalvm-native-config")) + resources.autodetect() // Needed to find the templates + ... + } +}</programlisting> + + <para>If you are running <literal>native-image</literal> form command + line instead, then the corresponding command-line options are (here we + assume that template are in + <literal>src/main/resources/templates</literal>):</para> + + <itemizedlist> + <listitem> + <para><literal>-H:ReflectionConfigurationFiles=./src/main/graalvm-native-config/reflection-config.json</literal></para> + </listitem> + + <listitem> + <para><literal>-H:IncludeResources=^templates/.*</literal></para> + </listitem> + </itemizedlist> + + <para>After these, the templates should now work.</para> + + <note> + <para>This example was written for GraalVM 21. GraalVM and its + Gradle plugin is still evolving, so above can become outdated, or + not the best practice anymore. Feel free to report any such + issue!</para> + </note> + + <para>You can also find a working example in the FreeMarker source + code; see the <literal>freemarker-test-graalvm-native</literal> + subproject there!</para> + + <para>Please see the <link + xlink:href="https://www.graalvm.org/latest/docs/">GraalVM + documentation</link> for more details!</para> + </section> + <section xml:id="pgui_misc_servlet"> <title>Using FreeMarker with servlets</title> @@ -30459,6 +30584,25 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting> <appendix role="changelog" xml:id="app_versions"> <title>Version history</title> + <section xml:id="versions_2_3_35"> + <title>2.3.35</title> + + <para>Release date: [TODO]</para> + + <section> + <title>Changes on the Java side</title> + + <itemizedlist> + <listitem> + <para>Added GraalVM native (AOT) support. But it doesn't + <quote>just work</quote>, as you have to explicitly configure + reflection and resource access for your application; <link + linkend="pgui_misc_graalvm_native">see more here</link>!</para> + </listitem> + </itemizedlist> + </section> + </section> + <section xml:id="versions_2_3_34"> <title>2.3.34</title>
