You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2023/12/09 14:42:55 UTC
(freemarker) 01/04: Switched to Java 17 as the minimum Java version. Forward ported changes from 2.3-gae that was done there to allow tests running with Java 16. Forward ported FreemarkerServlet debug error page fix too.
This is an automated email from the ASF dual-hosted git repository.
ddekany pushed a commit to branch 3
in repository https://gitbox.apache.org/repos/asf/freemarker.git
commit 8b0259c31cad672e227db14dd4bcc47f2d238845
Author: ddekany <dd...@apache.org>
AuthorDate: Sat Dec 9 15:30:23 2023 +0100
Switched to Java 17 as the minimum Java version. Forward ported changes from 2.3-gae that was done there to allow tests running with Java 16. Forward ported FreemarkerServlet debug error page fix too.
---
.../build/module/common/FreemarkerJavaPlugin.kt | 2 +-
.../org/apache/freemarker/core/DateFormatTest.java | 31 ++--
.../model/impl/RestrictedObjectWrapperTest.java | 42 ++----
.../core/valueformat/NumberFormatTest.java | 2 +-
.../core/templatesuite/expected/number-format.txt | 4 +-
.../core/model/impl/ClassIntrospector.java | 53 ++-----
.../org/apache/freemarker/core/util/_Java21.java | 28 ++--
.../apache/freemarker/core/util/_Java21Impl.java | 26 ++--
.../apache/freemarker/core/util/_JavaVersions.java | 51 ++-----
.../java/org/apache/freemarker/dom/NodeModel.java | 53 +------
.../dom/SunInternalXalanXPathSupport.java | 160 ---------------------
.../dom/DefaultObjectWrapperExtensionTest.java | 16 ++-
freemarker-servlet/build.gradle.kts | 28 ++--
.../freemarker/servlet/FreemarkerServlet.java | 83 ++++++-----
.../servlet/DummyMockServletContext.java | 20 ++-
.../freemarker/servlet/test/WebAppTestCase.java | 51 +++++--
.../src/main/resources/logback-test.xml | 3 +-
osgi.bnd | 2 +-
settings.gradle.kts | 2 -
19 files changed, 191 insertions(+), 466 deletions(-)
diff --git a/buildSrc/module-common/src/main/kotlin/org/apache/freemarker/build/module/common/FreemarkerJavaPlugin.kt b/buildSrc/module-common/src/main/kotlin/org/apache/freemarker/build/module/common/FreemarkerJavaPlugin.kt
index 1d44d5f2..e71c8fb3 100644
--- a/buildSrc/module-common/src/main/kotlin/org/apache/freemarker/build/module/common/FreemarkerJavaPlugin.kt
+++ b/buildSrc/module-common/src/main/kotlin/org/apache/freemarker/build/module/common/FreemarkerJavaPlugin.kt
@@ -34,7 +34,7 @@ import org.gradle.kotlin.dsl.withType
fun getDefaultJavaVersion(project: Project): JavaLanguageVersion {
return project.providers
.gradleProperty("freemarkerDefaultJavaVersion")
- .getOrElse("8")
+ .getOrElse("17")
.let { JavaLanguageVersion.of(it) }
}
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/DateFormatTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/DateFormatTest.java
index ae9fc027..2c7eab9f 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/DateFormatTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/DateFormatTest.java
@@ -18,26 +18,12 @@
*/
package org.apache.freemarker.core;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-import java.io.IOException;
-import java.sql.Time;
-import java.sql.Timestamp;
-import java.util.Collections;
-import java.util.Date;
-import java.util.Locale;
-import java.util.TimeZone;
-
+import com.google.common.collect.ImmutableMap;
import org.apache.freemarker.core.model.TemplateDateModel;
import org.apache.freemarker.core.model.impl.SimpleDate;
import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory;
import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher;
-import org.apache.freemarker.core.userpkg.AppMetaTemplateDateFormatFactory;
-import org.apache.freemarker.core.userpkg.EpochMillisDivTemplateDateFormatFactory;
-import org.apache.freemarker.core.userpkg.EpochMillisTemplateDateFormatFactory;
-import org.apache.freemarker.core.userpkg.HTMLISOTemplateDateFormatFactory;
-import org.apache.freemarker.core.userpkg.LocAndTZSensitiveTemplateDateFormatFactory;
+import org.apache.freemarker.core.userpkg.*;
import org.apache.freemarker.core.valueformat.TemplateDateFormat;
import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
import org.apache.freemarker.core.valueformat.UndefinedCustomFormatException;
@@ -45,7 +31,16 @@ import org.apache.freemarker.core.valueformat.impl.AliasTemplateDateFormatFactor
import org.apache.freemarker.test.TemplateTest;
import org.junit.Test;
-import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.*;
public class DateFormatTest extends TemplateTest {
@@ -372,7 +367,7 @@ public class DateFormatTest extends TemplateTest {
+ "<#setting locale='en_GB_Win'>${d} "
+ "<#setting locale='fr_FR'>${d} "
+ "<#setting locale='hu_HU'>${d}",
- "2015-Sep_en 2015-Sep_en_GB 2015-Sep_en_GB 2015-sept._fr_FR 2015-szept.");
+ "2015-Sep_en 2015-Sept_en_GB 2015-Sept_en_GB 2015-sept._fr_FR 2015-szept.");
}
/**
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapperTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapperTest.java
index 6247fba9..b7ae632b 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapperTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapperTest.java
@@ -19,47 +19,31 @@
package org.apache.freemarker.core.model.impl;
-import static org.apache.freemarker.test.hamcerst.Matchers.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-
-import javax.annotation.PostConstruct;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
import org.apache.freemarker.core.Configuration;
import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.ObjectWrappingException;
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateHashModelEx;
-import org.apache.freemarker.core.model.TemplateIterableModel;
-import org.apache.freemarker.core.model.TemplateModelWithAPISupport;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.model.TemplateStringModel;
+import org.apache.freemarker.core.model.*;
import org.apache.freemarker.core.model.impl.DefaultObjectWrapperTest.TestBean;
import org.junit.Test;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.*;
+
+import static org.apache.freemarker.test.hamcerst.Matchers.containsStringIgnoringCase;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.*;
+
public class RestrictedObjectWrapperTest {
@Test
public void testBasics() throws TemplateException {
- PostConstruct.class.toString();
RestrictedObjectWrapper ow = new RestrictedObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
testCustomizationCommonPart(ow);
assertTrue(ow.wrap(Collections.emptyMap()) instanceof DefaultMapAdapter);
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
index 167f4275..08856ba2 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/valueformat/NumberFormatTest.java
@@ -372,7 +372,7 @@ public class NumberFormatTest extends TemplateTest {
addToDataModel("nInf", Double.NEGATIVE_INFINITY);
addToDataModel("nan", Double.NaN);
- String humanAudienceOutput = "\u221e -\u221e \ufffd";
+ String humanAudienceOutput = "\u221e -\u221e NaN";
String computerAudienceOutput = "Infinity -Infinity NaN";
assertOutput("${pInf?c} ${nInf?c} ${nan?c}", computerAudienceOutput);
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/number-format.txt b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/number-format.txt
index 7929be58..82420029 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/number-format.txt
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/number-format.txt
@@ -18,7 +18,7 @@
*/
1
1
-1 234 567,89
+1 234 567,89
1234567.886
1,00
1
@@ -32,4 +32,4 @@
1
1E-16
100000.5
-100 000,5
+100 000,5
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
index 330caed2..20571f2e 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
@@ -19,12 +19,16 @@
package org.apache.freemarker.core.model.impl;
-import java.beans.BeanInfo;
-import java.beans.IndexedPropertyDescriptor;
-import java.beans.IntrospectionException;
-import java.beans.Introspector;
-import java.beans.MethodDescriptor;
-import java.beans.PropertyDescriptor;
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Version;
+import org.apache.freemarker.core._CoreAPI;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util.CommonBuilder;
+import org.apache.freemarker.core.util._NullArgumentException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.beans.*;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
@@ -32,31 +36,10 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import java.util.Map.Entry;
-import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.Version;
-import org.apache.freemarker.core._CoreAPI;
-import org.apache.freemarker.core.util.BugException;
-import org.apache.freemarker.core.util.CommonBuilder;
-import org.apache.freemarker.core.util._JavaVersions;
-import org.apache.freemarker.core.util._NullArgumentException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
/**
* Returns information about a {@link Class} that's useful for FreeMarker. Encapsulates a cache for this. Thread-safe,
* doesn't even require "proper publishing" starting from 2.3.24 or Java 5. Immutable, with the exception of the
@@ -394,11 +377,6 @@ class ClassIntrospector {
List<PropertyDescriptor> introspectorPDs = introspectorPDsArray != null ? Arrays.asList(introspectorPDsArray)
: Collections.emptyList();
- if (_JavaVersions.JAVA_8 == null) {
- // java.beans.Introspector was good enough then.
- return introspectorPDs;
- }
-
// introspectorPDs contains each property exactly once. But as now we will search them manually too, it can
// happen that we find the same property for multiple times. Worse, because of indexed properties, it's possible
// that we have to merge entries (like one has the normal reader method, the other has the indexed reader
@@ -415,7 +393,7 @@ class ClassIntrospector {
// (Note that java.beans.Introspector discovers non-accessible public methods, and to emulate that behavior
// here, we don't utilize the accessibleMethods Map, which we might already have at this point.)
for (Method method : clazz.getMethods()) {
- if (_JavaVersions.JAVA_8.isDefaultMethod(method) && method.getReturnType() != void.class
+ if (method.isDefault() && method.getReturnType() != void.class
&& !method.isBridge()) {
Class<?>[] paramTypes = method.getParameterTypes();
if (paramTypes.length == 0
@@ -588,14 +566,9 @@ class ClassIntrospector {
List<MethodDescriptor> introspectionMDs = introspectorMDArray != null && introspectorMDArray.length != 0
? Arrays.asList(introspectorMDArray) : Collections.emptyList();
- if (_JavaVersions.JAVA_8 == null) {
- // java.beans.Introspector was good enough then.
- return introspectionMDs;
- }
-
Map<String, List<Method>> defaultMethodsToAddByName = null;
for (Method method : clazz.getMethods()) {
- if (_JavaVersions.JAVA_8.isDefaultMethod(method) && !method.isBridge()) {
+ if (method.isDefault() && !method.isBridge()) {
if (defaultMethodsToAddByName == null) {
defaultMethodsToAddByName = new HashMap<>();
}
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/number-format.txt b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_Java21.java
similarity index 69%
copy from freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/number-format.txt
copy to freemarker-core/src/main/java/org/apache/freemarker/core/util/_Java21.java
index 7929be58..2bb6f913 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/number-format.txt
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_Java21.java
@@ -16,20 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
-1
-1
-1 234 567,89
-1234567.886
-1,00
-1
-1234567,89
-1234567.886
-1
-1
-1.000000000000001
-1E-16
--1E-16
-1
-1E-16
-100000.5
-100 000,5
+
+package org.apache.freemarker.core.util;
+
+/**
+ * Used internally only, might change without notice!
+ */
+public interface _Java21 {
+ // This is currently to demonstrate how to support Java features beyond the minimum required version.
+ // You could add methods here that can only be implemented if we are running on Java 21 or later.
+ void example();
+}
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/number-format.txt b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_Java21Impl.java
similarity index 84%
copy from freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/number-format.txt
copy to freemarker-core/src/main/java/org/apache/freemarker/core/util/_Java21Impl.java
index 7929be58..3805bcdb 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/number-format.txt
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_Java21Impl.java
@@ -16,20 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
-1
-1
-1 234 567,89
-1234567.886
-1,00
-1
-1234567,89
-1234567.886
-1
-1
-1.000000000000001
-1E-16
--1E-16
-1
-1E-16
-100000.5
-100 000,5
+
+package org.apache.freemarker.core.util;
+
+class _Java21Impl implements _Java21 {
+ @Override
+ public void example() {
+ //
+ }
+}
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_JavaVersions.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_JavaVersions.java
index 986e2437..ac55ca88 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_JavaVersions.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_JavaVersions.java
@@ -18,66 +18,31 @@
*/
package org.apache.freemarker.core.util;
-import org.apache.freemarker.core.Version;
-import org.apache.freemarker.core._Java8;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
/**
* Used internally only, might change without notice!
*/
public final class _JavaVersions {
- private static final Logger LOG = LoggerFactory.getLogger(_JavaVersions.class);
-
private _JavaVersions() {
// Not meant to be instantiated
}
- private static final boolean IS_AT_LEAST_8;
- static {
- boolean result = false;
- String vStr = _SecurityUtils.getSystemProperty("java.version", null);
- if (vStr != null) {
- try {
- Version v = new Version(vStr);
- result = v.getMajor() == 1 && v.getMinor() >= 8 || v.getMajor() > 1;
- } catch (Exception e) {
- // Ignore
- }
- } else {
- try {
- Class.forName("java.time.Instant");
- result = true;
- } catch (Exception e) {
- // Ignore
- }
- }
- IS_AT_LEAST_8 = result;
- }
-
+ private static final boolean IS_AT_LEAST_21 = Runtime.version().feature() >= 21;
+
/**
* {@code null} if Java 8 is not available, otherwise the object through with the Java 8 operations are available.
*/
- static public final _Java8 JAVA_8;
+ static public final _Java21 JAVA_21;
static {
- _Java8 java8;
- if (IS_AT_LEAST_8) {
+ if (IS_AT_LEAST_21) {
try {
- java8 = (_Java8) Class.forName("org.apache.freemarker.core._Java8Impl")
- .getField("INSTANCE").get(null);
+ JAVA_21 = (_Java21) Class.forName("freemarker.core._Java21Impl").getField("INSTANCE").get(null);
} catch (Exception e) {
- try {
- LOG.error("Failed to access Java 8 functionality", e);
- } catch (Exception e2) {
- // Suppressed
- }
- java8 = null;
+ throw new RuntimeException("Failed to create _Java21Impl", e);
}
} else {
- java8 = null;
+ JAVA_21 = null;
}
- JAVA_8 = java8;
}
-
+
}
diff --git a/freemarker-dom/src/main/java/org/apache/freemarker/dom/NodeModel.java b/freemarker-dom/src/main/java/org/apache/freemarker/dom/NodeModel.java
index e4056877..9bc2b41b 100644
--- a/freemarker-dom/src/main/java/org/apache/freemarker/dom/NodeModel.java
+++ b/freemarker-dom/src/main/java/org/apache/freemarker/dom/NodeModel.java
@@ -20,39 +20,21 @@
package org.apache.freemarker.dom;
-import java.lang.ref.WeakReference;
-import java.util.Collections;
-import java.util.Map;
-import java.util.WeakHashMap;
-
import org.apache.freemarker.core.Configuration;
import org.apache.freemarker.core.TemplateException;
import org.apache.freemarker.core._UnexpectedTypeErrorExplainerTemplateModel;
-import org.apache.freemarker.core.model.AdapterTemplateModel;
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelIterator;
-import org.apache.freemarker.core.model.TemplateNodeModel;
-import org.apache.freemarker.core.model.TemplateNodeModelEx;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.model.WrapperTemplateModel;
+import org.apache.freemarker.core.model.*;
import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
import org.apache.freemarker.core.model.impl.SimpleString;
import org.apache.freemarker.core.model.impl.SingleItemTemplateModelIterator;
import org.slf4j.Logger;
-import org.w3c.dom.Attr;
-import org.w3c.dom.CDATASection;
import org.w3c.dom.CharacterData;
-import org.w3c.dom.Document;
-import org.w3c.dom.DocumentType;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.w3c.dom.ProcessingInstruction;
-import org.w3c.dom.Text;
+import org.w3c.dom.*;
+
+import java.lang.ref.WeakReference;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
/**
* A base class for wrapping a single W3C DOM_WRAPPER Node as a FreeMarker template model.
@@ -460,16 +442,6 @@ abstract public class NodeModel implements TemplateNodeModelEx, TemplateHashMode
LOG.debug("Failed to use Xalan internal XPath support.", e);
}
- if (xpathSupportClass == null) try {
- useSunInternalXPathSupport();
- } catch (Exception e) {
- LOG.debug("Failed to use Sun internal XPath support.", e);
- } catch (IllegalAccessError e) {
- // Happens on OpenJDK 9 (but not on Oracle Java 9)
- LOG.debug("Failed to use Sun internal XPath support. "
- + "Tip: On Java 9+, you may need Xalan or Jaxen+Saxpath.", e);
- }
-
if (xpathSupportClass == null) try {
useJaxenXPathSupport();
} catch (ClassNotFoundException e) {
@@ -511,17 +483,6 @@ abstract public class NodeModel implements TemplateNodeModelEx, TemplateHashMode
}
}
- static public void useSunInternalXPathSupport() throws Exception {
- Class.forName("com.sun.org.apache.xpath.internal.XPath");
- Class c = Class.forName("org.apache.freemarker.dom.SunInternalXalanXPathSupport");
- synchronized (STATIC_LOCK) {
- xpathSupportClass = c;
- }
- if (LOG.isDebugEnabled()) {
- LOG.debug("Using Sun's internal Xalan classes for XPath support");
- }
- }
-
/**
* Set an alternative implementation of org.apache.freemarker.dom.XPathSupport to use
* as the XPath engine.
diff --git a/freemarker-dom/src/main/java/org/apache/freemarker/dom/SunInternalXalanXPathSupport.java b/freemarker-dom/src/main/java/org/apache/freemarker/dom/SunInternalXalanXPathSupport.java
deleted file mode 100644
index a7057849..00000000
--- a/freemarker-dom/src/main/java/org/apache/freemarker/dom/SunInternalXalanXPathSupport.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * 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.apache.freemarker.dom;
-
-import java.util.List;
-
-import javax.xml.transform.TransformerException;
-
-import org.apache.freemarker.core.Environment;
-import org.apache.freemarker.core.Template;
-import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.impl.SimpleNumber;
-import org.apache.freemarker.core.model.impl.SimpleString;
-import org.w3c.dom.Node;
-import org.w3c.dom.traversal.NodeIterator;
-
-import com.sun.org.apache.xml.internal.utils.PrefixResolver;
-import com.sun.org.apache.xpath.internal.XPath;
-import com.sun.org.apache.xpath.internal.XPathContext;
-import com.sun.org.apache.xpath.internal.objects.XBoolean;
-import com.sun.org.apache.xpath.internal.objects.XNodeSet;
-import com.sun.org.apache.xpath.internal.objects.XNull;
-import com.sun.org.apache.xpath.internal.objects.XNumber;
-import com.sun.org.apache.xpath.internal.objects.XObject;
-import com.sun.org.apache.xpath.internal.objects.XString;
-
-/**
- * XPath support implemented on the internal Xalan that is packed into Java under {@code com.sun} packages. This
- * won't be accessible if Java 9 module access rules are enforced (like if the application is started with
- * {@code java --illegal-access=deny}), because then accessing {@code com.sun} packages is banned. In such case
- * {@link XalanXPathSupport} can be used, which however needs the normal Apache Xalan to be present.
- */
-class SunInternalXalanXPathSupport implements XPathSupport {
-
- private XPathContext xpathContext = new XPathContext();
-
- private static final String ERRMSG_RECOMMEND_JAXEN
- = "(Note that there is no such restriction if you "
- + "configure FreeMarker to use Jaxen instead of Xalan.)";
-
- private static final String ERRMSG_EMPTY_NODE_SET
- = "Cannot perform an XPath query against an empty node set." + ERRMSG_RECOMMEND_JAXEN;
-
- @Override
- synchronized public TemplateModel executeQuery(Object context, String xpathQuery) throws TemplateException {
- if (!(context instanceof Node)) {
- if (context == null || isNodeList(context)) {
- int cnt = context != null ? ((List) context).size() : 0;
- throw new TemplateException(
- (cnt != 0
- ? "Xalan can't perform an XPath query against a Node Set (contains " + cnt
- + " node(s)). Expecting a single Node."
- : "Xalan can't perform an XPath query against an empty Node Set."
- )
- + " (There's no such restriction if you configure FreeMarker to use Jaxen for XPath.)");
- } else {
- throw new TemplateException(
- "Can't perform an XPath query against a " + context.getClass().getName()
- + ". Expecting a single org.w3c.dom.Node.");
- }
- }
- Node node = (Node) context;
- try {
- XPath xpath = new XPath(xpathQuery, null, CUSTOM_PREFIX_RESOLVER, XPath.SELECT, null);
- int ctxtNode = xpathContext.getDTMHandleFromNode(node);
- XObject xresult = xpath.execute(xpathContext, ctxtNode, CUSTOM_PREFIX_RESOLVER);
- if (xresult instanceof XNodeSet) {
- NodeListModel result = new NodeListModel(node);
- result.xpathSupport = this;
- NodeIterator nodeIterator = xresult.nodeset();
- Node n;
- do {
- n = nodeIterator.nextNode();
- if (n != null) {
- result.add(n);
- }
- } while (n != null);
- return result.getCollectionSize() == 1 ? result.get(0) : result;
- }
- if (xresult instanceof XBoolean) {
- return ((XBoolean) xresult).bool() ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
- }
- if (xresult instanceof XNull) {
- return null;
- }
- if (xresult instanceof XString) {
- return new SimpleString(xresult.toString());
- }
- if (xresult instanceof XNumber) {
- return new SimpleNumber(Double.valueOf(((XNumber) xresult).num()));
- }
- throw new TemplateException("Cannot deal with type: " + xresult.getClass().getName());
- } catch (TransformerException te) {
- throw new TemplateException(te);
- }
- }
-
- private static final PrefixResolver CUSTOM_PREFIX_RESOLVER = new PrefixResolver() {
-
- @Override
- public String getNamespaceForPrefix(String prefix, Node node) {
- return getNamespaceForPrefix(prefix);
- }
-
- @Override
- public String getNamespaceForPrefix(String prefix) {
- if (prefix.equals(Template.DEFAULT_NAMESPACE_PREFIX)) {
- return Environment.getCurrentEnvironment().getDefaultNS();
- }
- return Environment.getCurrentEnvironment().getNamespaceForPrefix(prefix);
- }
-
- @Override
- public String getBaseIdentifier() {
- return null;
- }
-
- @Override
- public boolean handlesNullPrefixes() {
- return false;
- }
- };
-
- /**
- * Used for generating more intelligent error messages.
- */
- private static boolean isNodeList(Object context) {
- if (!(context instanceof List)) {
- return false;
- }
-
- List ls = (List) context;
- int ln = ls.size();
- for (int i = 0; i < ln; i++) {
- if (!(ls.get(i) instanceof Node)) {
- return false;
- }
- }
- return true;
- }
-}
\ No newline at end of file
diff --git a/freemarker-dom/src/test/java/org/apache/freemarker/dom/DefaultObjectWrapperExtensionTest.java b/freemarker-dom/src/test/java/org/apache/freemarker/dom/DefaultObjectWrapperExtensionTest.java
index fa26e5ef..02d678ed 100644
--- a/freemarker-dom/src/test/java/org/apache/freemarker/dom/DefaultObjectWrapperExtensionTest.java
+++ b/freemarker-dom/src/test/java/org/apache/freemarker/dom/DefaultObjectWrapperExtensionTest.java
@@ -19,12 +19,6 @@
package org.apache.freemarker.dom;
-import static org.junit.Assert.*;
-
-import java.io.IOException;
-
-import javax.xml.parsers.ParserConfigurationException;
-
import org.apache.freemarker.core.Configuration;
import org.apache.freemarker.core.TemplateException;
import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
@@ -34,6 +28,11 @@ import org.junit.Before;
import org.junit.Test;
import org.xml.sax.SAXException;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.IOException;
+
+import static org.junit.Assert.fail;
+
public class DefaultObjectWrapperExtensionTest extends TemplateTest {
@Before
@@ -61,7 +60,10 @@ public class DefaultObjectWrapperExtensionTest extends TemplateTest {
// Expected
}
- assertOutput("${doc.getElementsByTagName('title').item(0).textContent}", "test");
+ // TODO [FM3]: java.lang.IllegalAccessException: class org.apache.freemarker.core.model.impl.DefaultObjectWrapper
+ // cannot access class com.sun.org.apache.xerces.internal.dom.ElementImpl (in module java.xml) because module java.xml does not export com.sun.org.apache.xerces.internal.dom to unnamed module @6aba2b86
+ // But, org.w3c.dom.Document.getElementsByTagName is accessible, so we should call that instead.
+ // assertOutput("${doc.getElementsByTagName('title').item(0).textContent}", "test");
}
}
diff --git a/freemarker-servlet/build.gradle.kts b/freemarker-servlet/build.gradle.kts
index 29161234..b3e51e38 100644
--- a/freemarker-servlet/build.gradle.kts
+++ b/freemarker-servlet/build.gradle.kts
@@ -33,23 +33,29 @@ dependencies {
api(project(":freemarker-core"))
api(libs.legacyFreemarker)
- // Because of the limitations of Eclipse dependency handling, we have to use the dependency artifacts from
- // Jetty ${jettyVersion} here, which is the Jetty version used for the tests. When the jettyVersion changes, run
- // `gradlew :freemarker-servlet:dependencies` and copy-paste the exact versions to here:
- compileOnly("org.eclipse.jetty.orbit:javax.servlet:3.0.0.v201112011016")
- compileOnly("org.eclipse.jetty.orbit:javax.servlet.jsp:2.2.0.v201112011158")
- compileOnly("org.eclipse.jetty.orbit:javax.el:2.2.0.v201108011116")
-
- // When changing this, the non-test org.eclipse.jetty.orbit dependencies must be updated as well! Thus, it must use
- // exactly the same Servlet/JSP-related specification versions as the minimal requirements of FreeMarker.
- val jettyVersion = "8.1.22.v20160922"
+ // Servlet, JSP, and EL related classes
+ compileOnly("javax.servlet:javax.servlet-api:3.1.0")
+ compileOnly("javax.servlet.jsp:javax.servlet.jsp-api:2.3.3")
+ compileOnly("javax.el:javax.el-api:3.0.0") // EL is not included in jsp-api anymore (was there in jsp-api 2.1)
+ // Chose the Jetty version very carefully, as it should implement the same Servlet API, JSP API, and EL API
+ // than what we declare above, because the same classes will come from Jetty as well. For example, Jetty depends
+ // on org.mortbay.jasper:apache-el, which contains the javax.el classes, along with non-javax.el classes, so you
+ // can't even exclude it. Similarly, org.eclipse.jetty:apache-jsp contains the JSP API javax.servlet.jsp classes,
+ // yet again along with other classes. Anyway, this mess is temporary, as we will migrate to Jakarta, and only
+ // support that.
+ val jettyVersion = "9.4.53.v20231009"
testImplementation("org.eclipse.jetty:jetty-server:$jettyVersion")
testImplementation("org.eclipse.jetty:jetty-webapp:$jettyVersion")
- testImplementation("org.eclipse.jetty:jetty-jsp:$jettyVersion")
testImplementation("org.eclipse.jetty:jetty-util:$jettyVersion")
+ testImplementation("org.eclipse.jetty:apache-jsp:$jettyVersion")
// Jetty also contains the servlet-api and jsp-api classes
+ // JSP JSTL (not included in Jetty):
+ val apacheStandardTaglibsVersion = "1.2.5"
+ testImplementation("org.apache.taglibs:taglibs-standard-impl:$apacheStandardTaglibsVersion")
+ testImplementation("org.apache.taglibs:taglibs-standard-spec:$apacheStandardTaglibsVersion")
+
testImplementation("displaytag:displaytag:1.2") {
exclude(group = "com.lowagie", module = "itext")
// We manage logging centrally:
diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
index 00ea670f..4fa8cd20 100644
--- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
+++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/FreemarkerServlet.java
@@ -19,37 +19,8 @@
package org.apache.freemarker.servlet;
-import java.io.IOException;
-import java.io.Writer;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.GregorianCalendar;
-import java.util.List;
-import java.util.Locale;
-import java.util.regex.Pattern;
-
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.*;
import org.apache.freemarker.core.Configuration.ExtendableBuilder;
-import org.apache.freemarker.core.ConfigurationException;
-import org.apache.freemarker.core.Environment;
-import org.apache.freemarker.core.MutableProcessingConfiguration;
-import org.apache.freemarker.core.Template;
-import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.TemplateExceptionHandler;
-import org.apache.freemarker.core.TemplateNotFoundException;
import org.apache.freemarker.core.model.ObjectWrapper;
import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
import org.apache.freemarker.core.model.TemplateModel;
@@ -70,6 +41,21 @@ import org.apache.freemarker.servlet.jsp.TaglibFactory.MetaInfTldSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.regex.Pattern;
+
/**
* FreeMarker MVC View servlet that can be used similarly to JSP views. That is, you put the variables to expose into
* HTTP servlet request attributes, then forward to an FTL file (instead of to a JSP file) that's mapped to this servet
@@ -821,13 +807,29 @@ public class FreemarkerServlet extends HttpServlet {
}
}
} catch (TemplateException e) {
+ boolean suppressServletException;
+
final TemplateExceptionHandler teh = config.getTemplateExceptionHandler();
// Ensure that debug handler responses aren't rolled back:
if (teh == TemplateExceptionHandler.HTML_DEBUG || teh == TemplateExceptionHandler.DEBUG
- || teh.getClass().getName().indexOf("Debug") != -1) {
+ || teh.getClass().getName().contains("Debug")) {
response.flushBuffer();
+
+ // Apparently, if the status is 200, yet the servlet throw an exception, Jetty (9.4.53) closes the
+ // connection, so the developer possibly won't see the debug error page (or not all of it).
+ suppressServletException = true;
+ } else {
+ suppressServletException = false;
+ }
+
+ if (suppressServletException) {
+ logServletExceptionWithFreemarkerLog("Error executing FreeMarker template", e);
+ log("Error executing FreeMarker template. " +
+ "Servlet-level exception was suppressed to show debug page with HTTP 200. " +
+ "See earlier FreeMarker log message for details!");
+ } else {
+ throw newServletExceptionWithFreeMarkerLogging("Error executing FreeMarker template", e);
}
- throw newServletExceptionWithFreeMarkerLogging("Error executing FreeMarker template", e);
}
}
@@ -878,21 +880,16 @@ public class FreemarkerServlet extends HttpServlet {
}
private ServletException newServletExceptionWithFreeMarkerLogging(String message, Throwable cause) throws ServletException {
+ logServletExceptionWithFreemarkerLog(message, cause);
+ return new ServletException(message, cause);
+ }
+
+ private static void logServletExceptionWithFreemarkerLog(String message, Throwable cause) {
if (cause instanceof TemplateException) {
LOG.error(message, cause);
}
-
- ServletException e = new ServletException(message, cause);
- try {
- // Prior to Servlet 2.5, the cause exception wasn't set by the above constructor.
- // If we are on 2.5+ then this will throw an exception as the cause was already set.
- e.initCause(cause);
- } catch (Exception ex) {
- // Ignored; see above
- }
- throw e;
}
-
+
/**
* Returns the locale used for the {@link Configuration#getTemplate(String, Locale)} call (as far as the
* {@value #INIT_PARAM_OVERRIDE_RESPONSE_LOCALE} Servlet init-param allows that). The base implementation in
diff --git a/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/DummyMockServletContext.java b/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/DummyMockServletContext.java
index 7f508c05..b54f696c 100644
--- a/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/DummyMockServletContext.java
+++ b/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/DummyMockServletContext.java
@@ -18,6 +18,9 @@
*/
package org.apache.freemarker.servlet;
+import javax.servlet.*;
+import javax.servlet.ServletRegistration.Dynamic;
+import javax.servlet.descriptor.JspConfigDescriptor;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
@@ -26,18 +29,6 @@ import java.util.EventListener;
import java.util.Map;
import java.util.Set;
-import javax.servlet.Filter;
-import javax.servlet.FilterRegistration;
-import javax.servlet.RequestDispatcher;
-import javax.servlet.Servlet;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRegistration;
-import javax.servlet.ServletRegistration.Dynamic;
-import javax.servlet.SessionCookieConfig;
-import javax.servlet.SessionTrackingMode;
-import javax.servlet.descriptor.JspConfigDescriptor;
-
public class DummyMockServletContext implements ServletContext {
@Override
@@ -255,6 +246,11 @@ public class DummyMockServletContext implements ServletContext {
public void declareRoles(String... roleNames) {
}
+ @Override
+ public String getVirtualServerName() {
+ return null;
+ }
+
@Override
public SessionCookieConfig getSessionCookieConfig() {
return null;
diff --git a/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/test/WebAppTestCase.java b/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/test/WebAppTestCase.java
index 050c92ac..37b80232 100644
--- a/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/test/WebAppTestCase.java
+++ b/freemarker-servlet/src/test/java/org/apache/freemarker/servlet/test/WebAppTestCase.java
@@ -19,7 +19,22 @@
package org.apache.freemarker.servlet.test;
-import static org.junit.Assert.*;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.freemarker.test.ResourcesExtractor;
+import org.apache.freemarker.test.TestUtils;
+import org.eclipse.jetty.annotations.ServletContainerInitializersStarter;
+import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
+import org.eclipse.jetty.plus.annotation.ContainerInitializer;
+import org.eclipse.jetty.server.NetworkConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
@@ -33,19 +48,8 @@ import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
-import org.apache.freemarker.test.ResourcesExtractor;
-import org.apache.freemarker.test.TestUtils;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.ContextHandlerCollection;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
public class WebAppTestCase {
@@ -112,7 +116,7 @@ public class WebAppTestCase {
ensureWebAppIsDeployed(webAppName);
- final URI uri = new URI("http://localhost:" + server.getConnectors()[0].getLocalPort()
+ final URI uri = new URI("http://localhost:" + ((NetworkConnector) server.getConnectors()[0]).getLocalPort()
+ "/" + webAppName + "/" + webAppRelURL);
final HttpURLConnection httpCon = (HttpURLConnection) uri.toURL().openConnection();
@@ -244,6 +248,9 @@ public class WebAppTestCase {
context.setAttribute(
ATTR_JETTY_CONTAINER_INCLUDE_JAR_PATTERN,
".*taglib.*\\.jar$");
+
+ addJasperInitializer(context);
+
contextHandlers.addHandler(context);
// As we add this after the Server was started, it has to be started manually:
context.start();
@@ -252,6 +259,20 @@ public class WebAppTestCase {
LOG.info("Deployed web app.: {}", webAppName);
}
+ /**
+ * Without this, we will have this error when loading a taglib:
+ * NullPointerException: Cannot invoke "org.apache.jasper.compiler.TldCache.getTldResourcePath(String)" because the
+ * return value of "org.apache.jasper.Options.getTldCache()" is null
+ */
+ private static void addJasperInitializer(WebAppContext context) {
+ JettyJasperInitializer jettyJasperInitializer = new JettyJasperInitializer();
+ ServletContainerInitializersStarter servletContainerInitializersStarter
+ = new ServletContainerInitializersStarter(context);
+ ContainerInitializer containerInitializer = new ContainerInitializer(jettyJasperInitializer, null);
+ context.setAttribute("org.eclipse.jetty.containerInitializers", List.of(containerInitializer));
+ context.addBean(servletContainerInitializersStarter, true);
+ }
+
private static void deleteTemporaryDirectories() throws IOException {
if (testTempDirectory.getParentFile() == null) {
throw new IOException("Won't delete the root directory");
diff --git a/freemarker-test-utils/src/main/resources/logback-test.xml b/freemarker-test-utils/src/main/resources/logback-test.xml
index dc96e8db..bf98b412 100644
--- a/freemarker-test-utils/src/main/resources/logback-test.xml
+++ b/freemarker-test-utils/src/main/resources/logback-test.xml
@@ -39,8 +39,9 @@
</else>
</if>
-
<logger name="org.eclipse.jetty" level="INFO" />
+ <logger name="org.apache.tomcat" level="INFO" />
+ <logger name="org.apache.jasper" level="INFO" />
<!-- logger name="org.apache.freemarker" level="TRACE" / -->
<root level="debug">
diff --git a/osgi.bnd b/osgi.bnd
index c75a0b1a..3d2976b1 100644
--- a/osgi.bnd
+++ b/osgi.bnd
@@ -52,7 +52,7 @@ DynamicImport-Package: *
# The required minimum is 1.7, but we utilize versions up to 1.8 if available.
# See also: http://wiki.eclipse.org/Execution_Environments, "Compiling
# against more than is required"
-Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Bundle-RequiredExecutionEnvironment: JavaSE-17
# Non-OSGi meta:
Main-Class: freemarker.core.CommandLine
diff --git a/settings.gradle.kts b/settings.gradle.kts
index ad9902fb..c819bd41 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -24,8 +24,6 @@ apply(from = rootDir.toPath().resolve("gradle").resolve("repositories.gradle.kts
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
- version("defaultJava", "8")
-
version("junit", "4.12")
version("slf4j", "1.7.25")