diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4da892c9d..ff719a965 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,9 +31,9 @@ jobs: build: strategy: matrix: - os: [windows-latest, ubuntu-latest] + os: [windows-latest, ubuntu-latest, ubuntu-24.04-arm] runs-on: ${{ matrix.os }} - concurrency: main_tests_${{ github.ref }} + concurrency: main_tests_${{ github.ref }}_${{ matrix.os }} steps: - name: Welcome Message run: 'echo "Started with parameters: ${{ matrix.os }} because ${{ github.event_name }} on ${{ github.ref }}"' @@ -58,6 +58,33 @@ jobs: - name: Run Build id: build_step run: './gradlew "-Pfreemarker.signMethod=none" "-Pfreemarker.allowUnsignedReleaseBuild=true" --continue clean build' + - name: Set up GraalVM 21 + uses: actions/setup-java@v4 + with: + java-version: 21 + distribution: graalvm + # test pipeline to check native support + # + # - GraalVM is added to the runner + # - A simple native project is build and run + # + # At the something like that should be found in the log : + # + # INFO: name : FreeMarker Native Demo, version : 2.3.35-nightly + # Jan 15, 2025 4:28:19 PM freemarker.log._JULLoggerFactory$JULLogger info + # INFO: result : + # + # + # Hello : FreeMarker GraalVM Native Demo + # + # + #

Hello : FreeMarker GraalVM Native Demo

+ #

Test template for Apache FreeMarker GraalVM native support (2.3.35-nightly)

+ # + # + - name: Test GraalVM native support (build and run) + id: native_test + run: './gradlew :freemarker-test-graalvm-native:nativeCompile;./freemarker-test-graalvm-native/build/native/nativeCompile/freemarker-test-graalvm-native' - name: Upload Failed Report uses: actions/upload-artifact@v4 if: failure() && steps.build_step.outcome == 'failure' diff --git a/README.md b/README.md index ad39d10bd..d12866921 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ Apache FreeMarkerâ„¢ {version} ============================ [![Build status](https://github.com/apache/freemarker/actions/workflows/ci.yml/badge.svg)](https://github.com/apache/freemarker/actions/workflows/ci.yml) +![GraalVM Ready](https://img.shields.io/badge/GraalVM-Ready-orange) For the latest version or to report bugs visit: https://freemarker.apache.org/ @@ -252,3 +253,141 @@ 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 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 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 + + ${.version} + ${data.description} + +``` + +#### 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" : "", + "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-core/src/main/java/freemarker/log/Logger.java b/freemarker-core/src/main/java/freemarker/log/Logger.java index 0a197e62c..6eafc2c88 100644 --- a/freemarker-core/src/main/java/freemarker/log/Logger.java +++ b/freemarker-core/src/main/java/freemarker/log/Logger.java @@ -32,7 +32,11 @@ * {@code log4j-over-slf4j} properly installed (means, you have no real Log4j in your class path, and SLF4J has a * backing implementation like {@code logback-classic}), then FreeMarker will use SLF4J directly instead of Log4j (since * FreeMarker 2.3.22). - * + * + * NOTE: When running on GraalVM native image (system property 'org.graalvm.nativeimage.imagecode' set), + * FreeMarker 2.4 behaviour will be anticipated (SLF4J / Apache Commons are auto-detected). + * Additionally, log4j-over-slf4j lookup is skipped. + * *

* If the auto detection sequence describet above doesn't give you the result that you want, see * {@link #SYSTEM_PROPERTY_NAME_LOGGER_LIBRARY}. @@ -157,6 +161,9 @@ public abstract class Logger { private static final String REAL_LOG4J_PRESENCE_CLASS = "org.apache.log4j.FileAppender"; private static final String LOG4J_OVER_SLF4J_TESTER_CLASS = "freemarker.log._Log4jOverSLF4JTester"; + // it is true if running in a GraalVM native build (issue #229) - see https://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/ImageInfo.html#PROPERTY_IMAGE_CODE_KEY + private static final boolean IS_GRAALVM_NATIVE = System.getProperty( "org.graalvm.nativeimage.imagecode" ) != null; + /** * Order matters! Starts with the lowest priority. */ @@ -193,12 +200,22 @@ private static String getLibraryName(int libraryEnum) { return LIBRARIES_BY_PRIORITY[(libraryEnum - 1) * 2 + 1]; } - private static boolean isAutoDetected(int libraryEnum) { - // 2.4: Remove libraryEnum == LIBRARY_SLF4J || libraryEnum == LIBRARY_COMMONS + // legacy auto-detection (until FreeMarker 2.3.X) + private static boolean isAutoDetectedLegacy( int libraryEnum ) { return !(libraryEnum == LIBRARY_AUTO || libraryEnum == LIBRARY_NONE || libraryEnum == LIBRARY_SLF4J || libraryEnum == LIBRARY_COMMONS); } + // next generation auto-detection (FreeMarker 2.4.X and on) + private static boolean isAutoDetectedNG( int libraryEnum ) { + return !(libraryEnum == LIBRARY_AUTO || libraryEnum == LIBRARY_NONE); + } + + private static boolean isAutoDetected(int libraryEnum) { + // 2.4: Remove libraryEnum == LIBRARY_SLF4J || libraryEnum == LIBRARY_COMMONS (use isAutoDetectedNG()) + return IS_GRAALVM_NATIVE ? isAutoDetectedNG(libraryEnum) : isAutoDetectedLegacy(libraryEnum); + } + private static int libraryEnum; private static LoggerFactory loggerFactory; private static boolean initializedFromSystemProperty; @@ -428,7 +445,8 @@ private static LoggerFactory createLoggerFactory(int libraryEnum) throws ClassNo if (libraryEnum == LIBRARY_AUTO) { for (int libraryEnumToTry = MAX_LIBRARY_ENUM; libraryEnumToTry >= MIN_LIBRARY_ENUM; libraryEnumToTry--) { if (!isAutoDetected(libraryEnumToTry)) continue; - if (libraryEnumToTry == LIBRARY_LOG4J && hasLog4LibraryThatDelegatesToWorkingSLF4J()) { + // skip hasLog4LibraryThatDelegatesToWorkingSLF4J when running in GraalVM native image + if (!IS_GRAALVM_NATIVE && libraryEnumToTry == LIBRARY_LOG4J && hasLog4LibraryThatDelegatesToWorkingSLF4J()) { libraryEnumToTry = LIBRARY_SLF4J; } @@ -443,7 +461,7 @@ private static LoggerFactory createLoggerFactory(int libraryEnum) throws ClassNo e); } } - logWarnInLogger("Auto detecton couldn't set up any logger libraries; FreeMarker logging suppressed."); + logWarnInLogger("Auto detection couldn't set up any logger libraries; FreeMarker logging suppressed."); return new _NullLoggerFactory(); } else { return createLoggerFactoryForNonAuto(libraryEnum); diff --git a/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/native-image.properties b/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/native-image.properties new file mode 100644 index 000000000..75b3bdea1 --- /dev/null +++ b/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/native-image.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +Args = --initialize-at-run-time=freemarker.ext.jython.JythonWrapper,\ + --initialize-at-run-time=freemarker.ext.jython.JythonModel \ No newline at end of file diff --git a/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/reflect-config.json b/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/reflect-config.json new file mode 100644 index 000000000..9cf36441f --- /dev/null +++ b/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/reflect-config.json @@ -0,0 +1,82 @@ +[ { + "condition" : { + "typeReachable" : "freemarker.template.Configuration" + }, + "name" : "freemarker.log._SLF4JLoggerFactory", + "methods" : [ { + "name" : "", + "parameterTypes" : [ ] + } ] +}, { + "condition" : { + "typeReachable" : "freemarker.template.Configuration" + }, + "name" : "freemarker.log.SLF4JLoggerFactory", + "methods" : [ { + "name" : "", + "parameterTypes" : [ ] + } ] +}, { + "condition" : { + "typeReachable" : "freemarker.template.Configuration" + }, + "name" : "freemarker.log._AvalonLoggerFactory.java", + "methods" : [ { + "name" : "", + "parameterTypes" : [ ] + } ] +}, { + "condition" : { + "typeReachable" : "freemarker.template.Configuration" + }, + "name" : "freemarker.log._CommonsLoggingLoggerFactory.java", + "methods" : [ { + "name" : "", + "parameterTypes" : [ ] + } ] +}, { + "condition" : { + "typeReachable" : "freemarker.template.Configuration" + }, + "name" : "freemarker.log._JULLoggerFactory.java", + "methods" : [ { + "name" : "", + "parameterTypes" : [ ] + } ] +}, { + "condition" : { + "typeReachable" : "freemarker.template.Configuration" + }, + "name" : "freemarker.log._Log4jLoggerFactory.java", + "methods" : [ { + "name" : "", + "parameterTypes" : [ ] + } ] +}, { + "condition" : { + "typeReachable" : "freemarker.template.Configuration" + }, + "name" : "freemarker.log._Log4jOverSLF4JTester.java", + "methods" : [ { + "name" : "", + "parameterTypes" : [ ] + } ] +}, { + "condition" : { + "typeReachable" : "freemarker.template.Configuration" + }, + "name" : "freemarker.log._NullLoggerFactory.java", + "methods" : [ { + "name" : "", + "parameterTypes" : [ ] + } ] +}, { + "condition" : { + "typeReachable" : "freemarker.template.Configuration" + }, + "name" : "freemarker.log.CommonsLoggingLoggerFactory.java", + "methods" : [ { + "name" : "", + "parameterTypes" : [ ] + } ] +} ] \ No newline at end of file diff --git a/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/resource-config.json b/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/resource-config.json new file mode 100644 index 000000000..02908d662 --- /dev/null +++ b/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/resource-config.json @@ -0,0 +1,25 @@ +{ + "bundles": [], + "resources": { + "includes": [ + { + "pattern": "\\Qfreemarker/ext/beans/DefaultMemberAccessPolicy-rules\\E", + "condition": { + "typeReachable": "freemarker.ext.beans.DefaultMemberAccessPolicy" + } + }, + { + "pattern": "\\Qfreemarker/ext/beans/unsafeMethods.properties\\E", + "condition": { + "typeReachable": "freemarker.ext.beans.LegacyDefaultMemberAccessPolicy" + } + }, + { + "pattern": "\\Qfreemarker/version.properties\\E", + "condition": { + "typeReachable": "freemarker.template.utility.ClassUtil" + } + } + ] + } +} \ No newline at end of file diff --git a/freemarker-test-graalvm-native/README.md b/freemarker-test-graalvm-native/README.md new file mode 100644 index 000000000..269045cc1 --- /dev/null +++ b/freemarker-test-graalvm-native/README.md @@ -0,0 +1,48 @@ +# freemarker-test-graalvm-natice + +Test project for GraalVM support + +Requirements : + +- GraalVM 21+ + +## Quickstart + +1. Build the main Apache FreeMarker proejct : + +```shell +./gradlew "-Pfreemarker.signMethod=none" "-Pfreemarker.allowUnsignedReleaseBuild=true" clean build +``` + +2. Build the test module native image with GraalVM : + +```shell +./gradlew :freemarker-test-graalvm-native:nativeCompile +``` + +3. Run the project : + +```shell +./freemarker-test-graalvm-native/build/native/nativeCompile/freemarker-test-graalvm-native +``` + +Output should be similar to : + +```txt +INFO: name : FreeMarker Native Demo, version : 2.3.35-nightly +Jan 15, 2025 4:28:19 PM freemarker.log._JULLoggerFactory$JULLogger info +INFO: result : + + + Hello : FreeMarker GraalVM Native Demo + + +

Hello : FreeMarker GraalVM Native Demo

+

Test template for Apache FreeMarker GraalVM native support (2.3.35-nightly)

+ + +``` + +## CI (GitHub workflow) + +GraalVM native test for this module is included in the GitHub [CI](../.github/workflows/ci.yml) worflow. \ No newline at end of file diff --git a/freemarker-test-graalvm-native/build.gradle.kts b/freemarker-test-graalvm-native/build.gradle.kts new file mode 100644 index 000000000..ab3624ed8 --- /dev/null +++ b/freemarker-test-graalvm-native/build.gradle.kts @@ -0,0 +1,71 @@ +import org.graalvm.buildtools.gradle.tasks.BuildNativeImageTask +import java.util.Arrays + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +plugins { + java + application + id("org.graalvm.buildtools.native") version "0.10.3" +} + +group = "org.freemarker" +version = rootProject.version + +val graalSdkVersion = "24.1.1" +val junitJupiterVersion = "5.11.4" + +val profile = findProperty("profile") as String? ?: "default" + +dependencies { + implementation(project(":")) + implementation("org.python:jython:2.5.0") + compileOnly("org.graalvm.sdk:graal-sdk:$graalSdkVersion") + testImplementation("org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion") +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + withSourcesJar() + withJavadocJar() +} + +application { + mainClass.set("org.freemarker.core.graal.HelloFreeMarker") +} + +tasks.test { + useJUnitPlatform() + testLogging { + events("PASSED", "FAILED", "SKIPPED") + } +} + +graalvmNative { + binaries.all { + fallback.set(false) + verbose.set(true) + resources.autodetect() + buildArgs.add( "-H:ReflectionConfigurationFiles=$projectDir/src/main/config/reflect-config.json" ) + jvmArgs() + } +} diff --git a/freemarker-test-graalvm-native/src/main/config/reflect-config.json b/freemarker-test-graalvm-native/src/main/config/reflect-config.json new file mode 100644 index 000000000..f3f506643 --- /dev/null +++ b/freemarker-test-graalvm-native/src/main/config/reflect-config.json @@ -0,0 +1,9 @@ +[ + { + "name": "org.freemarker.core.graal.HelloDataModel", + "methods": [ + { "name": "getName", "parameterTypes": [] }, + { "name": "getVersion", "parameterTypes": [] } + ] + } +] \ No newline at end of file diff --git a/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloDataModel.java b/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloDataModel.java new file mode 100644 index 000000000..3052f0e04 --- /dev/null +++ b/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloDataModel.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.freemarker.core.graal; + +public class HelloDataModel { + + private String name; + + private String version; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloFreeMarker.java b/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloFreeMarker.java new file mode 100644 index 000000000..f35dc1939 --- /dev/null +++ b/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloFreeMarker.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.freemarker.core.graal; + +import freemarker.log.Logger; + +public class HelloFreeMarker { + + private final static Logger log = Logger.getLogger(HelloFreeMarker.class.getName()); + + public static void main( String[] args ) throws Exception { + HelloHandler helloHandler = new HelloHandler(); + helloHandler.sayHello(); + } + +} diff --git a/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloHandler.java b/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloHandler.java new file mode 100644 index 000000000..2cd7271c3 --- /dev/null +++ b/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloHandler.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.freemarker.core.graal; + +import freemarker.log.Logger; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.Version; + +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +/** + * Simple test class, able to say hello. + */ +public class HelloHandler { + + private final static Logger log = Logger.getLogger(HelloHandler.class.getName()); + + public HelloHandler() throws ClassNotFoundException { + // test native configuration + Class.forName("freemarker.ext.jython.JythonModel"); + } + + /** + * This method will say hello by printing an Apache FreeMarker template + * + * @throws Exception if any unexpected situation occurs + */ + public void sayHello() throws Exception { + try (StringWriter buffer = new StringWriter()) { + Version version = new Version(Configuration.getVersion().toString()); // using latest version + // creates FreeMarker configuration + Configuration cfg = new Configuration( version ); + cfg.setClassForTemplateLoading(HelloDataModel.class, "/freemarker-templates/"); + // creates data model + HelloDataModel data = new HelloDataModel(); + data.setName( "FreeMarker GraalVM Native Demo" ); + data.setVersion( version.toString() ); + log.info( String.format( "name : %s, version : %s", data.getName(), data.getVersion() ) ); + Map input = new HashMap<>(); + input.put( "data", data ); + // process template + Template template = cfg.getTemplate( "hello-world.ftl" ); + template.process( input, buffer ); + log.info( String.format( "result :\n%s", buffer ) ); + } + } + +} diff --git a/freemarker-test-graalvm-native/src/main/resources/freemarker-templates/hello-world.ftl b/freemarker-test-graalvm-native/src/main/resources/freemarker-templates/hello-world.ftl new file mode 100644 index 000000000..31cd70d15 --- /dev/null +++ b/freemarker-test-graalvm-native/src/main/resources/freemarker-templates/hello-world.ftl @@ -0,0 +1,27 @@ +<#-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + + + Hello : ${data.name} + + +

Hello : ${data.name}

+

Test template for Apache FreeMarker GraalVM native support (${data.version})

+ + \ No newline at end of file diff --git a/freemarker-test-graalvm-native/src/test/java/org/freemarker/core/graal/HelloFreeMarkerTest.java b/freemarker-test-graalvm-native/src/test/java/org/freemarker/core/graal/HelloFreeMarkerTest.java new file mode 100644 index 000000000..e20256006 --- /dev/null +++ b/freemarker-test-graalvm-native/src/test/java/org/freemarker/core/graal/HelloFreeMarkerTest.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.freemarker.core.graal; + +import freemarker.log.Logger; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class HelloFreeMarkerTest { + + private final static Logger log = Logger.getLogger(HelloFreeMarker.class.getName()); + + @Test + public void testMain() { + try { + HelloFreeMarker.main(new String[0]); + Assertions.assertTrue( Boolean.TRUE ); + } catch (Exception e) { + String message = String.format( "Error : %s", e ); + log.error( message , e ); + Assertions.fail( message ); + } + } + +} diff --git a/rat-excludes b/rat-excludes index 77fd6e6f1..ba7269790 100644 --- a/rat-excludes +++ b/rat-excludes @@ -68,6 +68,10 @@ gradle/** .directory .Trash* +# ignore for freemarker-test-graalvm-native module (only used for GraalVM native support compliance) +freemarker-test-graalvm-native/build/** +freemarker-test-graalvm-native/README.md + # Well known files that need no license note: # ------------------------------------------- diff --git a/settings.gradle.kts b/settings.gradle.kts index e74adfb97..92bfdb546 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,3 +34,5 @@ dependencyResolutionManagement { } } } + +include("freemarker-test-graalvm-native") \ No newline at end of file