Fixed: Configuration.setServletContextForTemplateLoading did not support Jakarta
diff --git a/freemarker-core/src/main/java/freemarker/template/Configuration.java b/freemarker-core/src/main/java/freemarker/template/Configuration.java
index d08f815..1948804 100644
--- a/freemarker-core/src/main/java/freemarker/template/Configuration.java
+++ b/freemarker-core/src/main/java/freemarker/template/Configuration.java
@@ -1636,36 +1636,81 @@
*
* @param servletContext the {@code javax.servlet.ServletContext} object. (The declared type is {@link Object}
* to prevent class loading error when using FreeMarker in an environment where
- * there's no servlet classes available.)
- * @param path the path relative to the ServletContext.
+ * there's no servlet classes available.) Can't be {@code null}.
+ * @param path the path relative to the ServletContext; maybe {@code null}.
*
* @see #setTemplateLoader(TemplateLoader)
*/
public void setServletContextForTemplateLoading(Object servletContext, String path) {
+ NullArgumentException.check("servletContext", servletContext);
+
+ // To not introduce linking-time dependency on servlets, we load all related classes on runtime.
+ Class<?> servletContextClass = null;
+ Boolean jakartaMode = null;
+
+ Exception jakartaServletClassLoadingException = null;
try {
- // Don't introduce linking-time dependency on servlets
- final Class webappTemplateLoaderClass = ClassUtil.forName("freemarker.cache.WebappTemplateLoader");
-
- // Don't introduce linking-time dependency on servlets
- final Class servletContextClass = ClassUtil.forName("javax.servlet.ServletContext");
-
- final Class[] constructorParamTypes;
- final Object[] constructorParams;
- if (path == null) {
- constructorParamTypes = new Class[] { servletContextClass };
- constructorParams = new Object[] { servletContext };
- } else {
- constructorParamTypes = new Class[] { servletContextClass, String.class };
- constructorParams = new Object[] { servletContext, path };
+ servletContextClass = ClassUtil.forName("jakarta.servlet.ServletContext");
+ if (servletContextClass.isInstance(servletContext)) {
+ jakartaMode = true;
}
-
- setTemplateLoader( (TemplateLoader)
- webappTemplateLoaderClass
- .getConstructor(constructorParamTypes)
- .newInstance(constructorParams));
} catch (Exception e) {
- throw new BugException(e);
+ jakartaServletClassLoadingException = e;
}
+
+ Exception javaxServletClassLoadingException = null;
+ if (jakartaMode == null) {
+ try {
+ servletContextClass = ClassUtil.forName("javax.servlet.ServletContext");
+ if (servletContextClass.isInstance(servletContext)) {
+ jakartaMode = false;
+ }
+ } catch (Exception e) {
+ javaxServletClassLoadingException = e;
+ }
+ }
+
+ if (servletContextClass == null) {
+ throw new UnsupportedOperationException("Failed to get ServletContext class; probably Servlet API-s "
+ + "are not supported in this environment, but check the exceptions:"
+ + "\n- When attempted use Jakarta Servlet support: " + jakartaServletClassLoadingException
+ + "\n- When attempted use javax Servlet support: " + javaxServletClassLoadingException);
+ }
+ if (jakartaMode == null) {
+ throw new IllegalArgumentException("servletContext must implement ServletContext, but "
+ + servletContext.getClass().getName() + " does not.");
+ }
+
+ Class<?> webappTemplateLoaderClass;
+ try {
+ webappTemplateLoaderClass = ClassUtil.forName(
+ jakartaMode
+ ? "freemarker.ext.jakarta.servlet.WebappTemplateLoader"
+ : "freemarker.cache.WebappTemplateLoader");
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Failed to get WebappTemplateLoader class", e);
+ }
+
+ final Class<?>[] constructorParamTypes;
+ final Object[] constructorParams;
+ if (path == null) {
+ constructorParamTypes = new Class<?>[] { servletContextClass };
+ constructorParams = new Object[] { servletContext };
+ } else {
+ constructorParamTypes = new Class[] { servletContextClass, String.class };
+ constructorParams = new Object[] { servletContext, path };
+ }
+
+ TemplateLoader templateLoader;
+ try {
+ templateLoader = (TemplateLoader) webappTemplateLoaderClass
+ .getConstructor(constructorParamTypes)
+ .newInstance(constructorParams);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to instantiate " + webappTemplateLoaderClass.getName(), e);
+ }
+
+ setTemplateLoader(templateLoader);
}
/**
diff --git a/freemarker-javax-servlet/src/test/java/freemarker/template/SetServletContextForTemplateLoadingTest.java b/freemarker-javax-servlet/src/test/java/freemarker/template/SetServletContextForTemplateLoadingTest.java
new file mode 100644
index 0000000..4e96cb6
--- /dev/null
+++ b/freemarker-javax-servlet/src/test/java/freemarker/template/SetServletContextForTemplateLoadingTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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 freemarker.template;
+
+import static org.junit.Assert.*;
+
+import javax.servlet.ServletContext;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import freemarker.cache.WebappTemplateLoader;
+
+public class SetServletContextForTemplateLoadingTest {
+ @Test
+ public void testSuccess() {
+ Configuration cfg = new Configuration(Configuration.VERSION_2_3_33);
+ ServletContext servletContext = new MockServletContext();
+ cfg.setServletContextForTemplateLoading(servletContext, "foo");
+ cfg.setServletContextForTemplateLoading(servletContext, null);
+ assertThat(cfg.getTemplateLoader(), Matchers.instanceOf(WebappTemplateLoader.class));
+ }
+
+ @Test
+ public void testIllegalArgument() {
+ Configuration cfg = new Configuration(Configuration.VERSION_2_3_33);
+ try {
+ cfg.setServletContextForTemplateLoading("bad type", "foo");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), Matchers.containsString("ServletContext"));
+ }
+ }
+}