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 2020/01/01 19:10:47 UTC
[freemarker] branch 2.3-gae updated (1ea65e8 -> 889d411)
This is an automated email from the ASF dual-hosted git repository.
ddekany pushed a change to branch 2.3-gae
in repository https://gitbox.apache.org/repos/asf/freemarker.git.
from 1ea65e8 Added freemarker.ext.beans.MemberAccessPolicy interface, and the memberAccessPolicy property to BeansWrapper, and subclasses like DefaultObjectWrapper. This allows users to implement their own program logic to decide what members of classes will be exposed to the templates. The legacy "unsafe methods" mechanism also builds on the same now, and by setting a custom MemberAccessPolicy you completely replace that.
new a504e6e Fixed withArgs Manual tests
new 8ff93ba Added WhitelistMemberAccessPolicy and related internal classes
new 889d411 Deprecated FreemarkerTag (a custom JSP tag that probably nobody uses)
The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../java/freemarker/ext/beans/BeansWrapper.java | 25 +-
.../freemarker/ext/beans/ClassIntrospector.java | 79 +--
.../ext/beans/ClassIntrospectorBuilder.java | 6 +
.../freemarker/ext/beans/ConstructorMatcher.java} | 16 +-
.../ext/beans/ExecutableMemberSignature.java | 69 +++
.../java/freemarker/ext/beans/FieldMatcher.java} | 16 +-
.../freemarker/ext/beans/MemberAccessPolicy.java | 39 +-
.../java/freemarker/ext/beans/MemberMatcher.java | 111 ++++
...tPropertyDescriptor.java => MethodMatcher.java} | 32 +-
.../freemarker/ext/beans/TemplateAccessible.java | 45 ++
.../ext/beans/WhitelistMemberAccessPolicy.java | 411 +++++++++++++++
.../java/freemarker/ext/beans/_MethodUtil.java | 141 ++++++
.../java/freemarker/ext/jsp/FreemarkerTag.java | 4 +
.../java/freemarker/ext/jsp/JspContextModel.java | 4 +
.../freemarker/template/DefaultObjectWrapper.java | 10 +-
.../freemarker/template/utility/ClassUtil.java | 37 +-
src/manual/en_US/book.xml | 74 ++-
...DefaultObjectWrapperMemberAccessPolicyTest.java | 115 ++++-
.../freemarker/ext/beans/MethodMatcherTest.java | 179 +++++++
.../java/freemarker/ext/beans/MethodUtilTest.java | 156 ++++++
.../ext/beans/WhitelistMemberAccessPolicyTest.java | 558 +++++++++++++++++++++
.../template/DefaultObjectWrapperTest.java | 35 ++
...thArgsExamples-usingWithArgsSpecialVariable.ftl | 18 +
...gsExamples-usingWithArgsSpecialVariable.ftl.out | 18 +
.../freemarker/manual/WithArgsLastExamples.ftl | 18 +
.../WithArgsLastExamples.ftl.out} | 48 +-
26 files changed, 2126 insertions(+), 138 deletions(-)
copy src/{test/java/freemarker/ext/beans/BridgeMethodsBean.java => main/java/freemarker/ext/beans/ConstructorMatcher.java} (71%)
create mode 100644 src/main/java/freemarker/ext/beans/ExecutableMemberSignature.java
copy src/{test/java/freemarker/ext/beans/BridgeMethodsBean.java => main/java/freemarker/ext/beans/FieldMatcher.java} (77%)
create mode 100644 src/main/java/freemarker/ext/beans/MemberMatcher.java
copy src/main/java/freemarker/ext/beans/{FastPropertyDescriptor.java => MethodMatcher.java} (59%)
create mode 100644 src/main/java/freemarker/ext/beans/TemplateAccessible.java
create mode 100644 src/main/java/freemarker/ext/beans/WhitelistMemberAccessPolicy.java
create mode 100644 src/test/java/freemarker/ext/beans/MethodMatcherTest.java
create mode 100644 src/test/java/freemarker/ext/beans/MethodUtilTest.java
create mode 100644 src/test/java/freemarker/ext/beans/WhitelistMemberAccessPolicyTest.java
copy src/test/resources/freemarker/{test/templatesuite/expected/listhashliteral-ici-2.3.20.txt => manual/WithArgsLastExamples.ftl.out} (75%)
[freemarker] 01/03: Fixed withArgs Manual tests
Posted by dd...@apache.org.
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 a504e6e42e922de483adb0048ef2a31508000b6b
Author: ddekany <dd...@apache.org>
AuthorDate: Tue Dec 31 00:56:21 2019 +0100
Fixed withArgs Manual tests
---
...thArgsExamples-usingWithArgsSpecialVariable.ftl | 18 ++++++++
...gsExamples-usingWithArgsSpecialVariable.ftl.out | 18 ++++++++
.../freemarker/manual/WithArgsLastExamples.ftl | 18 ++++++++
.../freemarker/manual/WithArgsLastExamples.ftl.out | 49 ++++++++++++++++++++++
4 files changed, 103 insertions(+)
diff --git a/src/test/resources/freemarker/manual/WithArgsExamples-usingWithArgsSpecialVariable.ftl b/src/test/resources/freemarker/manual/WithArgsExamples-usingWithArgsSpecialVariable.ftl
index 9819fa0..583b444 100644
--- a/src/test/resources/freemarker/manual/WithArgsExamples-usingWithArgsSpecialVariable.ftl
+++ b/src/test/resources/freemarker/manual/WithArgsExamples-usingWithArgsSpecialVariable.ftl
@@ -1,3 +1,21 @@
+<#--
+ 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.
+-->
<#macro m1 a b c>
m1 does things with ${a}, ${b}, ${c}
</#macro>
diff --git a/src/test/resources/freemarker/manual/WithArgsExamples-usingWithArgsSpecialVariable.ftl.out b/src/test/resources/freemarker/manual/WithArgsExamples-usingWithArgsSpecialVariable.ftl.out
index 54488c3..aa3a8ab 100644
--- a/src/test/resources/freemarker/manual/WithArgsExamples-usingWithArgsSpecialVariable.ftl.out
+++ b/src/test/resources/freemarker/manual/WithArgsExamples-usingWithArgsSpecialVariable.ftl.out
@@ -1,3 +1,21 @@
+/*
+ * 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.
+ */
m2 does things with 1, 2, 3
Delegate to m1:
diff --git a/src/test/resources/freemarker/manual/WithArgsLastExamples.ftl b/src/test/resources/freemarker/manual/WithArgsLastExamples.ftl
index 4494b13..9cea85f 100644
--- a/src/test/resources/freemarker/manual/WithArgsLastExamples.ftl
+++ b/src/test/resources/freemarker/manual/WithArgsLastExamples.ftl
@@ -1,3 +1,21 @@
+<#--
+ 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.
+-->
<#function f a b c d>
<#return "a=${a}, b=${b}, c=${c}, d=${d}">
</#function>
diff --git a/src/test/resources/freemarker/manual/WithArgsLastExamples.ftl.out b/src/test/resources/freemarker/manual/WithArgsLastExamples.ftl.out
new file mode 100644
index 0000000..c9d17b6
--- /dev/null
+++ b/src/test/resources/freemarker/manual/WithArgsLastExamples.ftl.out
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+a=2, b=3, c=1, d=2
+a=1, b=2, c=2, d=3
+
+ a=1
+ b=2
+ others:
+ e = 5
+ f = 6
+ c = 3
+ d = 4
+ a=1
+ b=2
+ others:
+ c = 3
+ d = 4
+ e = 5
+ f = 6
+
+ a = 1
+ b = 2
+ e = 5
+ f = 6
+ c = 3
+ d = 4
+ a = 1
+ b = 2
+ c = 3
+ d = 4
+ e = 5
+ f = 6
[freemarker] 02/03: Added WhitelistMemberAccessPolicy and related
internal classes
Posted by dd...@apache.org.
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 8ff93ba04b87caa67aa203c952ec3f2797bcae3f
Author: ddekany <dd...@apache.org>
AuthorDate: Tue Dec 31 01:08:41 2019 +0100
Added WhitelistMemberAccessPolicy and related internal classes
---
.../java/freemarker/ext/beans/BeansWrapper.java | 25 +-
.../freemarker/ext/beans/ClassIntrospector.java | 79 +--
.../ext/beans/ClassIntrospectorBuilder.java | 6 +
...erAccessPolicy.java => ConstructorMatcher.java} | 18 +-
.../ext/beans/ExecutableMemberSignature.java | 69 +++
.../{MemberAccessPolicy.java => FieldMatcher.java} | 18 +-
.../freemarker/ext/beans/MemberAccessPolicy.java | 39 +-
.../java/freemarker/ext/beans/MemberMatcher.java | 111 ++++
...{MemberAccessPolicy.java => MethodMatcher.java} | 22 +-
.../freemarker/ext/beans/TemplateAccessible.java | 45 ++
.../ext/beans/WhitelistMemberAccessPolicy.java | 411 +++++++++++++++
.../java/freemarker/ext/beans/_MethodUtil.java | 141 ++++++
.../freemarker/template/DefaultObjectWrapper.java | 10 +-
.../freemarker/template/utility/ClassUtil.java | 37 +-
src/manual/en_US/book.xml | 74 ++-
...DefaultObjectWrapperMemberAccessPolicyTest.java | 115 ++++-
.../freemarker/ext/beans/MethodMatcherTest.java | 179 +++++++
.../java/freemarker/ext/beans/MethodUtilTest.java | 156 ++++++
.../ext/beans/WhitelistMemberAccessPolicyTest.java | 558 +++++++++++++++++++++
.../template/DefaultObjectWrapperTest.java | 35 ++
20 files changed, 2031 insertions(+), 117 deletions(-)
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapper.java b/src/main/java/freemarker/ext/beans/BeansWrapper.java
index d014a69..d67c3ac 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapper.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapper.java
@@ -658,6 +658,29 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
}
}
+ /**
+ * @since 2.3.30
+ */
+ public MemberAccessPolicy getMemberAccessPolicy() {
+ return classIntrospector.getMemberAccessPolicy();
+ }
+
+ /**
+ * Used to customize what members will be hidden;
+ * see {@link BeansWrapperBuilder#setMemberAccessPolicy(MemberAccessPolicy)} for more.
+ *
+ * @since 2.3.30
+ */
+ public void setMemberAccessPolicy(MemberAccessPolicy memberAccessPolicy) {
+ checkModifiable();
+
+ if (classIntrospector.getMemberAccessPolicy() != memberAccessPolicy) {
+ ClassIntrospectorBuilder builder = classIntrospector.createBuilder();
+ builder.setMemberAccessPolicy(memberAccessPolicy);
+ replaceClassIntrospector(builder);
+ }
+ }
+
MethodSorter getMethodSorter() {
return classIntrospector.getMethodSorter();
}
@@ -1567,7 +1590,7 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
Object ctors = classIntrospector.get(clazz).get(ClassIntrospector.CONSTRUCTORS_KEY);
if (ctors == null) {
throw new TemplateModelException("Class " + clazz.getName() +
- " has no public constructors.");
+ " has no exposed constructors.");
}
Constructor<?> ctor = null;
Object[] objargs;
diff --git a/src/main/java/freemarker/ext/beans/ClassIntrospector.java b/src/main/java/freemarker/ext/beans/ClassIntrospector.java
index 72f26cb..630bf95 100644
--- a/src/main/java/freemarker/ext/beans/ClassIntrospector.java
+++ b/src/main/java/freemarker/ext/beans/ClassIntrospector.java
@@ -78,6 +78,11 @@ class ClassIntrospector {
private static final String JREBEL_INTEGRATION_ERROR_MSG
= "Error initializing JRebel integration. JRebel integration disabled.";
+ private static final ExecutableMemberSignature GET_STRING_SIGNATURE =
+ new ExecutableMemberSignature("get", new Class[] { String.class });
+ private static final ExecutableMemberSignature GET_OBJECT_SIGNATURE =
+ new ExecutableMemberSignature("get", new Class[] { Object.class });
+
/**
* When this property is true, some things are stricter. This is mostly to catch suspicious things in development
* that can otherwise be valid situations.
@@ -205,7 +210,7 @@ class ClassIntrospector {
return new ClassIntrospectorBuilder(this);
}
- // ------------------------------------------------------------------------------------------------------------------
+ // -----------------------------------------------------------------------------------------------------------------
// Introspection:
/**
@@ -273,7 +278,7 @@ class ClassIntrospector {
addFieldsToClassIntrospectionData(introspData, clazz, classMemberAccessPolicy);
}
- final Map<MethodSignature, List<Method>> accessibleMethods = discoverAccessibleMethods(clazz);
+ final Map<ExecutableMemberSignature, List<Method>> accessibleMethods = discoverAccessibleMethods(clazz);
addGenericGetToClassIntrospectionData(introspData, accessibleMethods, classMemberAccessPolicy);
@@ -312,7 +317,8 @@ class ClassIntrospector {
}
private void addBeanInfoToClassIntrospectionData(
- Map<Object, Object> introspData, Class<?> clazz, Map<MethodSignature, List<Method>> accessibleMethods,
+ Map<Object, Object> introspData, Class<?> clazz,
+ Map<ExecutableMemberSignature, List<Method>> accessibleMethods,
ClassMemberAccessPolicy classMemberAccessPolicy) throws IntrospectionException {
BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
List<PropertyDescriptor> pdas = getPropertyDescriptors(beanInfo, clazz);
@@ -660,7 +666,8 @@ class ClassIntrospector {
private void addPropertyDescriptorToClassIntrospectionData(Map<Object, Object> introspData,
PropertyDescriptor pd,
- Map<MethodSignature, List<Method>> accessibleMethods, ClassMemberAccessPolicy classMemberAccessPolicy) {
+ Map<ExecutableMemberSignature, List<Method>> accessibleMethods,
+ ClassMemberAccessPolicy classMemberAccessPolicy) {
Method readMethod = getMatchingAccessibleMethod(pd.getReadMethod(), accessibleMethods);
if (readMethod != null && !isMethodExposed(classMemberAccessPolicy, readMethod)) {
readMethod = null;
@@ -687,12 +694,11 @@ class ClassIntrospector {
}
private void addGenericGetToClassIntrospectionData(Map<Object, Object> introspData,
- Map<MethodSignature, List<Method>> accessibleMethods, ClassMemberAccessPolicy classMemberAccessPolicy) {
- Method genericGet = getFirstAccessibleMethod(
- MethodSignature.GET_STRING_SIGNATURE, accessibleMethods);
+ Map<ExecutableMemberSignature, List<Method>> accessibleMethods,
+ ClassMemberAccessPolicy classMemberAccessPolicy) {
+ Method genericGet = getFirstAccessibleMethod(GET_STRING_SIGNATURE, accessibleMethods);
if (genericGet == null) {
- genericGet = getFirstAccessibleMethod(
- MethodSignature.GET_OBJECT_SIGNATURE, accessibleMethods);
+ genericGet = getFirstAccessibleMethod(GET_OBJECT_SIGNATURE, accessibleMethods);
}
if (genericGet != null && isMethodExposed(classMemberAccessPolicy, genericGet)) {
introspData.put(GENERIC_GET_KEY, genericGet);
@@ -730,23 +736,24 @@ class ClassIntrospector {
}
/**
- * Retrieves mapping of {@link MethodSignature}-s to a {@link List} of accessible methods for a class. In case the
- * class is not public, retrieves methods with same signature as its public methods from public superclasses and
- * interfaces. Basically upcasts every method to the nearest accessible method.
+ * Retrieves mapping of {@link ExecutableMemberSignature}-s to a {@link List} of accessible methods for a class. In
+ * case the class is not public, retrieves methods with same signature as its public methods from public
+ * superclasses and interfaces. Basically upcasts every method to the nearest accessible method.
*/
- private static Map<MethodSignature, List<Method>> discoverAccessibleMethods(Class<?> clazz) {
- Map<MethodSignature, List<Method>> accessibles = new HashMap<MethodSignature, List<Method>>();
+ private static Map<ExecutableMemberSignature, List<Method>> discoverAccessibleMethods(Class<?> clazz) {
+ Map<ExecutableMemberSignature, List<Method>> accessibles = new HashMap<ExecutableMemberSignature, List<Method>>();
discoverAccessibleMethods(clazz, accessibles);
return accessibles;
}
- private static void discoverAccessibleMethods(Class<?> clazz, Map<MethodSignature, List<Method>> accessibles) {
+ private static void discoverAccessibleMethods(
+ Class<?> clazz, Map<ExecutableMemberSignature, List<Method>> accessibles) {
if (Modifier.isPublic(clazz.getModifiers())) {
try {
Method[] methods = clazz.getMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
- MethodSignature sig = new MethodSignature(method);
+ ExecutableMemberSignature sig = new ExecutableMemberSignature(method);
// Contrary to intuition, a class can actually have several
// different methods with same signature *but* different
// return types. These can't be constructed using Java the
@@ -785,11 +792,11 @@ class ClassIntrospector {
}
}
- private static Method getMatchingAccessibleMethod(Method m, Map<MethodSignature, List<Method>> accessibles) {
+ private static Method getMatchingAccessibleMethod(Method m, Map<ExecutableMemberSignature, List<Method>> accessibles) {
if (m == null) {
return null;
}
- MethodSignature sig = new MethodSignature(m);
+ ExecutableMemberSignature sig = new ExecutableMemberSignature(m);
List<Method> ams = accessibles.get(sig);
if (ams == null) {
return null;
@@ -802,7 +809,8 @@ class ClassIntrospector {
return null;
}
- private static Method getFirstAccessibleMethod(MethodSignature sig, Map<MethodSignature, List<Method>> accessibles) {
+ private static Method getFirstAccessibleMethod(
+ ExecutableMemberSignature sig, Map<ExecutableMemberSignature, List<Method>> accessibles) {
List<Method> ams = accessibles.get(sig);
if (ams == null || ams.isEmpty()) {
return null;
@@ -853,39 +861,6 @@ class ClassIntrospector {
return argTypes;
}
- private static final class MethodSignature {
- private static final MethodSignature GET_STRING_SIGNATURE =
- new MethodSignature("get", new Class[] { String.class });
- private static final MethodSignature GET_OBJECT_SIGNATURE =
- new MethodSignature("get", new Class[] { Object.class });
-
- private final String name;
- private final Class<?>[] args;
-
- private MethodSignature(String name, Class<?>[] args) {
- this.name = name;
- this.args = args;
- }
-
- MethodSignature(Method method) {
- this(method.getName(), method.getParameterTypes());
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof MethodSignature) {
- MethodSignature ms = (MethodSignature) o;
- return ms.name.equals(name) && Arrays.equals(args, ms.args);
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return name.hashCode() ^ args.length; // TODO That's a poor quality hash... isn't this a problem?
- }
- }
-
// -----------------------------------------------------------------------------------------------------------------
// Cache management:
diff --git a/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java b/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java
index 1f2d5e0..e2847ab 100644
--- a/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java
+++ b/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java
@@ -156,6 +156,12 @@ final class ClassIntrospectorBuilder implements Cloneable {
return memberAccessPolicy;
}
+ /**
+ * Sets the {@link MemberAccessPolicy}; default is {@link DefaultMemberAccessPolicy#getInstance(Version)}, which
+ * is not appropriate if template editors aren't trusted.
+ *
+ * @since 2.3.30
+ */
public void setMemberAccessPolicy(MemberAccessPolicy memberAccessPolicy) {
NullArgumentException.check(memberAccessPolicy);
this.memberAccessPolicy = memberAccessPolicy;
diff --git a/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java b/src/main/java/freemarker/ext/beans/ConstructorMatcher.java
similarity index 53%
copy from src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
copy to src/main/java/freemarker/ext/beans/ConstructorMatcher.java
index 5d72fea..2c94576 100644
--- a/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
+++ b/src/main/java/freemarker/ext/beans/ConstructorMatcher.java
@@ -19,18 +19,16 @@
package freemarker.ext.beans;
+import java.lang.reflect.Constructor;
+
/**
- * Implement this to specify what class members are accessible from templates. Implementations must be thread
- * safe, and instances should be generally singletons on JVM level. The last is because FreeMarker tries to cache
- * class introspectors in a global (static, JVM-scope) cache for reuse, and that's only possible if the
- * {@link MemberAccessPolicy} instances used at different places in the JVM are equal according to
- * {@link #equals(Object) (and the singleton object of course {@link #equals(Object)} with itself).
+ * {@link MemberMatcher} for constructors.
*
* @since 2.3.30
*/
-public interface MemberAccessPolicy {
- /**
- * Returns the {@link ClassMemberAccessPolicy} that encapsulates the member access policy for a given class.
- */
- ClassMemberAccessPolicy forClass(Class<?> containingClass);
+final class ConstructorMatcher extends MemberMatcher<Constructor<?>, ExecutableMemberSignature> {
+ @Override
+ protected ExecutableMemberSignature toMemberSignature(Constructor<?> member) {
+ return new ExecutableMemberSignature(member);
+ }
}
diff --git a/src/main/java/freemarker/ext/beans/ExecutableMemberSignature.java b/src/main/java/freemarker/ext/beans/ExecutableMemberSignature.java
new file mode 100644
index 0000000..dfba692
--- /dev/null
+++ b/src/main/java/freemarker/ext/beans/ExecutableMemberSignature.java
@@ -0,0 +1,69 @@
+/*
+ * 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.ext.beans;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Used as a key in a {@link Map} or {@link Set} of methods or constructors.
+ *
+ * @since 2.3.30
+ */
+final class ExecutableMemberSignature {
+ private final String name;
+ private final Class<?>[] args;
+
+ ExecutableMemberSignature(String name, Class<?>[] args) {
+ this.name = name;
+ this.args = args;
+ }
+
+ /**
+ * Uses the method name, and the parameter types.
+ */
+ ExecutableMemberSignature(Method method) {
+ this(method.getName(), method.getParameterTypes());
+ }
+
+ /**
+ * Doesn't use the constructor name, only the parameter types.
+ */
+ ExecutableMemberSignature(Constructor<?> constructor) {
+ this("<init>", constructor.getParameterTypes());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof ExecutableMemberSignature) {
+ ExecutableMemberSignature ms = (ExecutableMemberSignature) o;
+ return ms.name.equals(name) && Arrays.equals(args, ms.args);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode() + args.length * 31;
+ }
+}
diff --git a/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java b/src/main/java/freemarker/ext/beans/FieldMatcher.java
similarity index 53%
copy from src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
copy to src/main/java/freemarker/ext/beans/FieldMatcher.java
index 5d72fea..f67bf14 100644
--- a/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
+++ b/src/main/java/freemarker/ext/beans/FieldMatcher.java
@@ -19,18 +19,16 @@
package freemarker.ext.beans;
+import java.lang.reflect.Field;
+
/**
- * Implement this to specify what class members are accessible from templates. Implementations must be thread
- * safe, and instances should be generally singletons on JVM level. The last is because FreeMarker tries to cache
- * class introspectors in a global (static, JVM-scope) cache for reuse, and that's only possible if the
- * {@link MemberAccessPolicy} instances used at different places in the JVM are equal according to
- * {@link #equals(Object) (and the singleton object of course {@link #equals(Object)} with itself).
+ * {@link MemberMatcher} for fields.
*
* @since 2.3.30
*/
-public interface MemberAccessPolicy {
- /**
- * Returns the {@link ClassMemberAccessPolicy} that encapsulates the member access policy for a given class.
- */
- ClassMemberAccessPolicy forClass(Class<?> containingClass);
+final class FieldMatcher extends MemberMatcher<Field, String> {
+ @Override
+ protected String toMemberSignature(Field member) {
+ return member.getName();
+ }
}
diff --git a/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java b/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
index 5d72fea..c8de56d 100644
--- a/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
+++ b/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
@@ -19,18 +19,45 @@
package freemarker.ext.beans;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.DefaultObjectWrapperBuilder;
+import freemarker.template.ObjectWrapper;
+import freemarker.template.TemplateModel;
+
/**
- * Implement this to specify what class members are accessible from templates. Implementations must be thread
- * safe, and instances should be generally singletons on JVM level. The last is because FreeMarker tries to cache
- * class introspectors in a global (static, JVM-scope) cache for reuse, and that's only possible if the
- * {@link MemberAccessPolicy} instances used at different places in the JVM are equal according to
- * {@link #equals(Object) (and the singleton object of course {@link #equals(Object)} with itself).
+ * Implement this to specify what class members are accessible from templates.
+ *
+ * <p>The instance is usually set via {@link BeansWrapperBuilder#setMemberAccessPolicy(MemberAccessPolicy)} (or if
+ * you use {@link DefaultObjectWrapper}, with
+ * {@link DefaultObjectWrapperBuilder#setMemberAccessPolicy(MemberAccessPolicy)}).
+ *
+ * <p>As {@link BeansWrapper}, and its subclasses like {@link DefaultObjectWrapper}, only discover public
+ * members, it's pointless to whitelist non-public members. An {@link MemberAccessPolicy} is a filter applied to
+ * the set of members that {@link BeansWrapper} intends to expose on the first place. (Also, while public members
+ * declared in non-public classes are discovered by {@link BeansWrapper}, Java reflection will not allow accessing those
+ * normally, so generally it's not useful to whitelist those either.)
+ *
+ * <p>Note that if you add {@link TemplateModel}-s directly to the data-model, those are not wrapped by the
+ * {@link ObjectWrapper}, and so the {@link MemberAccessPolicy} won't affect those.
+ *
+ * <p>Implementations must be thread-safe, and instances generally should be singletons on JVM level. FreeMarker
+ * caches its class metadata in a global (static, JVM-scope) cache for shared use, and the {@link MemberAccessPolicy}
+ * used is part of the cache key. Thus {@link MemberAccessPolicy} instances used at different places in the JVM
+ * should be equal according to {@link Object#equals(Object)}, as far as they implement exactly the same policy. It's
+ * not recommended to override {@link Object#equals(Object)}; use singletons and the default
+ * {@link Object#equals(Object)} implementation if possible.
*
* @since 2.3.30
*/
public interface MemberAccessPolicy {
/**
* Returns the {@link ClassMemberAccessPolicy} that encapsulates the member access policy for a given class.
+ * {@link ClassMemberAccessPolicy} implementations need not be thread-safe. Because class introspection results are
+ * cached, and so this method is usually only called once for a given class, the {@link ClassMemberAccessPolicy}
+ * instances shouldn't be cached by the implementation of this method.
+ *
+ * @param contextClass
+ * The exact class of object from which members will be get in the templates.
*/
- ClassMemberAccessPolicy forClass(Class<?> containingClass);
+ ClassMemberAccessPolicy forClass(Class<?> contextClass);
}
diff --git a/src/main/java/freemarker/ext/beans/MemberMatcher.java b/src/main/java/freemarker/ext/beans/MemberMatcher.java
new file mode 100644
index 0000000..2b52178
--- /dev/null
+++ b/src/main/java/freemarker/ext/beans/MemberMatcher.java
@@ -0,0 +1,111 @@
+/*
+ * 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.ext.beans;
+
+import java.lang.reflect.Member;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * For implementing a whitelist or blacklist of class members in {@link MemberAccessPolicy} implementations.
+ * A {@link MemberMatcher} filters by name and/or signature, but not by by visibility, as
+ * the visibility condition is orthogonal to the whitelist or blacklist content.
+ *
+ * @since 2.3.30
+ */
+abstract class MemberMatcher<M extends Member, S> {
+ private final Map<S, Types> signaturesToUpperBoundTypes = new HashMap<S, Types>();
+
+ private static class Types {
+ private final Set<Class<?>> set = new HashSet<Class<?>>();
+ private boolean containsInterfaces;
+ }
+
+ /**
+ * Returns the {@link Map} lookup key used to match the member.
+ */
+ protected abstract S toMemberSignature(M member);
+
+ /**
+ * Adds a member that this {@link MemberMatcher} will match.
+ *
+ * @param upperBoundType
+ * The type of the actual object that contains the member must {@code instanceof} this.
+ * @param member
+ * The member that should match (when the upper bound class condition is also fulfilled). Only the name
+ * and/or signature of the member will be used for the condition, not the actual member object.
+ */
+ void addMatching(Class<?> upperBoundType, M member) {
+ Class<?> declaringClass = member.getDeclaringClass();
+ if (!declaringClass.isAssignableFrom(upperBoundType)) {
+ throw new IllegalArgumentException("Upper bound class " + upperBoundType.getName() + " is not the same "
+ + "type or a subtype of the declaring type of member " + member + ".");
+ }
+
+ S memberSignature = toMemberSignature(member);
+ Types upperBoundTypes = signaturesToUpperBoundTypes.get(memberSignature);
+ if (upperBoundTypes == null) {
+ upperBoundTypes = new Types();
+ signaturesToUpperBoundTypes.put(memberSignature, upperBoundTypes);
+ }
+ upperBoundTypes.set.add(upperBoundType);
+ if (upperBoundType.isInterface()) {
+ upperBoundTypes.containsInterfaces = true;
+ }
+ }
+
+ /**
+ * Returns if the given member, if it's referred through the given class, is matched by this {@link MemberMatcher}.
+ *
+ * @param contextClass The actual class through which we access the member
+ * @param member The member that we intend to access
+ *
+ * @return If there was match in this {@link MemberMatcher}.
+ */
+ boolean matches(Class<?> contextClass, M member) {
+ S memberSignature = toMemberSignature(member);
+ Types upperBoundTypes = signaturesToUpperBoundTypes.get(memberSignature);
+
+ return upperBoundTypes != null && containsTypeOrSuperType(upperBoundTypes, contextClass);
+ }
+
+ private static boolean containsTypeOrSuperType(Types types, Class<?> c) {
+ if (c == null) {
+ return false;
+ }
+
+ if (types.set.contains(c)) {
+ return true;
+ }
+ if (containsTypeOrSuperType(types, c.getSuperclass())) {
+ return true;
+ }
+ if (types.containsInterfaces) {
+ for (Class<?> anInterface : c.getInterfaces()) {
+ if (containsTypeOrSuperType(types, anInterface)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java b/src/main/java/freemarker/ext/beans/MethodMatcher.java
similarity index 53%
copy from src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
copy to src/main/java/freemarker/ext/beans/MethodMatcher.java
index 5d72fea..7df56be 100644
--- a/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
+++ b/src/main/java/freemarker/ext/beans/MethodMatcher.java
@@ -19,18 +19,20 @@
package freemarker.ext.beans;
+import java.lang.reflect.Method;
+
/**
- * Implement this to specify what class members are accessible from templates. Implementations must be thread
- * safe, and instances should be generally singletons on JVM level. The last is because FreeMarker tries to cache
- * class introspectors in a global (static, JVM-scope) cache for reuse, and that's only possible if the
- * {@link MemberAccessPolicy} instances used at different places in the JVM are equal according to
- * {@link #equals(Object) (and the singleton object of course {@link #equals(Object)} with itself).
+ * {@link MemberMatcher} for methods.
+ *
+ * <p>The return type (and visibility) of the methods will be ignored, only the method name and its parameter types
+ * matter. (The {@link MemberAccessPolicy}, and even {@link BeansWrapper} itself will still filter by visibility, it's
+ * just not the duty of the {@link MemberMatcher}.)
*
* @since 2.3.30
*/
-public interface MemberAccessPolicy {
- /**
- * Returns the {@link ClassMemberAccessPolicy} that encapsulates the member access policy for a given class.
- */
- ClassMemberAccessPolicy forClass(Class<?> containingClass);
+final class MethodMatcher extends MemberMatcher<Method, ExecutableMemberSignature> {
+ @Override
+ protected ExecutableMemberSignature toMemberSignature(Method member) {
+ return new ExecutableMemberSignature(member);
+ }
}
diff --git a/src/main/java/freemarker/ext/beans/TemplateAccessible.java b/src/main/java/freemarker/ext/beans/TemplateAccessible.java
new file mode 100644
index 0000000..5e07873
--- /dev/null
+++ b/src/main/java/freemarker/ext/beans/TemplateAccessible.java
@@ -0,0 +1,45 @@
+/*
+ * 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.ext.beans;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.ObjectWrapper;
+
+/**
+ * Indicates that the the annotated member can be exposed to templates; if the annotated member will be actually
+ * exposed depends on the {@link ObjectWrapper} in use, and how that was configured. When used with
+ * {@link BeansWrapper} or its subclasses, most notably with {@link DefaultObjectWrapper}, and you also set the
+ * {@link MemberAccessPolicy} to a {@link WhitelistMemberAccessPolicy}, it will acts as if the members annotated with
+ * this are in the whitelist. Note that adding something to the whitelist doesn't necessary make it visible from
+ * templates; see {@link WhitelistMemberAccessPolicy} documentation.
+ *
+ * @since 2.3.30
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD})
+public @interface TemplateAccessible {
+}
diff --git a/src/main/java/freemarker/ext/beans/WhitelistMemberAccessPolicy.java b/src/main/java/freemarker/ext/beans/WhitelistMemberAccessPolicy.java
new file mode 100644
index 0000000..5e8945a
--- /dev/null
+++ b/src/main/java/freemarker/ext/beans/WhitelistMemberAccessPolicy.java
@@ -0,0 +1,411 @@
+/*
+ * 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.ext.beans;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import freemarker.log.Logger;
+import freemarker.template.ObjectWrapper;
+import freemarker.template.utility.ClassUtil;
+import freemarker.template.utility.NullArgumentException;
+
+/**
+ * Whitelist-based member access policy, that is, only members that you have explicitly whitelisted will be accessible.
+ * The whitelist content is application specific, and can be significant work to put together, but it's the only way
+ * you can achieve any practical safety if you don't fully trust the users who can edit templates. Of course, this only
+ * can deal with the {@link ObjectWrapper} aspect of safety; please check the Manual to see what else is needed. Also,
+ * since this is related to security, read the documentation of {@link MemberAccessPolicy}, to know about the
+ * pitfalls and edge cases related to {@link MemberAccessPolicy}-es in general.
+ *
+ * <p>There are two ways you can add members to the whitelist:
+ * <ul>
+ * <li>Via a list of member selectors passed to the constructor
+ * <li>Via {@link TemplateAccessible} annotation
+ * </ul>
+ *
+ * <p>When a member is whitelisted, it's identified by the following data (with the example of
+ * {@code com.example.MyClass.myMethod(int, int)} being whitelisted):
+ * <ul>
+ * <li>Upper bound class ({@code com.example.MyClass} in the example)
+ * <li>Member name ({@code myMethod} in the example), except for constructors where it's unused
+ * <li>Parameter types ({@code int, int} in the example), except for fields where it's unused
+ * </ul>
+ *
+ * <p>Once you have whitelisted a member in the upper bound class, it will be automatically whitelisted in all
+ * subclasses of that, even if the whitelisted member is a field or constructor (which doesn't support overriding, but
+ * it will be treated as such if the field name or constructor parameter types match).
+ * It's called "upper bound" class, because the member will only be whitelisted in classes that are {@code instanceof}
+ * the upper bound class. That restriction stands even if the member was inherited from another class or
+ * interface, and it wasn't even overridden in the upper bound class; the member won't be whitelisted in the
+ * class/interface where it was inherited from, if that type is more generic than the upper bound class.
+ *
+ * <p>Note that the return type of methods aren't used in any way. So if you whitelist {@code myMethod(int, int)}, and
+ * it has multiple variants with different return types (which is possible on the bytecode level), then you have
+ * whitelisted all variants of it.
+ *
+ * @since 2.3.30
+ */
+public class WhitelistMemberAccessPolicy implements MemberAccessPolicy {
+ private static final Logger LOG = Logger.getLogger("freemarker.beans");
+
+ private final MethodMatcher methodMatcher;
+ private final ConstructorMatcher constructorMatcher;
+ private final FieldMatcher fieldMatcher;
+
+ /**
+ * A condition that matches some type members. See {@link WhitelistMemberAccessPolicy} documentation for more.
+ * Exactly one of these will be non-{@code null}:
+ * {@link #getMethod()}, {@link #getConstructor()}, {@link #getField()}, {@link #getException()}.
+ *
+ * @since 2.3.30
+ */
+ public final static class MemberSelector {
+ private final Class<?> upperBoundType;
+ private final Method method;
+ private final Constructor<?> constructor;
+ private final Field field;
+ private final Exception exception;
+ private final String exceptionMemberSelectorString;
+
+ /**
+ * Use if you want to match methods similar to the specified one, in types that are {@code instanceof} of
+ * the specified upper bound type. When methods are matched, only the name and the parameter types matter.
+ */
+ public MemberSelector(Class<?> upperBoundType, Method method) {
+ NullArgumentException.check("upperBoundType", upperBoundType);
+ NullArgumentException.check("method", method);
+ this.upperBoundType = upperBoundType;
+ this.method = method;
+ this.constructor = null;
+ this.field = null;
+ this.exception = null;
+ this.exceptionMemberSelectorString = null;
+ }
+
+ /**
+ * Use if you want to match constructors similar to the specified one, in types that are {@code instanceof} of
+ * the specified upper bound type. When constructors are matched, only the parameter types matter.
+ */
+ public MemberSelector(Class<?> upperBoundType, Constructor<?> constructor) {
+ NullArgumentException.check("upperBoundType", upperBoundType);
+ NullArgumentException.check("constructor", constructor);
+ this.upperBoundType = upperBoundType;
+ this.method = null;
+ this.constructor = constructor;
+ this.field = null;
+ this.exception = null;
+ this.exceptionMemberSelectorString = null;
+ }
+
+ /**
+ * Use if you want to match fields similar to the specified one, in types that are {@code instanceof} of
+ * the specified upper bound type. When fields are matched, only the name matters.
+ */
+ public MemberSelector(Class<?> upperBoundType, Field field) {
+ NullArgumentException.check("upperBoundType", upperBoundType);
+ NullArgumentException.check("field", field);
+ this.upperBoundType = upperBoundType;
+ this.method = null;
+ this.constructor = null;
+ this.field = field;
+ this.exception = null;
+ this.exceptionMemberSelectorString = null;
+ }
+
+ /**
+ * Used to store the result of a parsing that's failed for a reason that we can skip on runtime (typically,
+ * when a missing class or member was referred).
+ *
+ * @param upperBoundType {@code null} if resolving the upper bound type itself failed.
+ * @param exception Not {@code null}
+ * @param exceptionMemberSelectorString Not {@code null}; the selector whose resolution has failed, used in
+ * the log message.
+ */
+ public MemberSelector(Class<?> upperBoundType, Exception exception, String exceptionMemberSelectorString) {
+ NullArgumentException.check("exception", exception);
+ NullArgumentException.check("exceptionMemberSelectorString", exceptionMemberSelectorString);
+ this.upperBoundType = upperBoundType;
+ this.method = null;
+ this.constructor = null;
+ this.field = null;
+ this.exception = exception;
+ this.exceptionMemberSelectorString = exceptionMemberSelectorString;
+ }
+
+ /**
+ * Maybe {@code null} if {@link #getException()} is non-{@code null}.
+ */
+ public Class<?> getUpperBoundType() {
+ return upperBoundType;
+ }
+
+ /**
+ * Maybe {@code null};
+ * set if the selector matches methods similar to the returned one, and there was no exception.
+ */
+ public Method getMethod() {
+ return method;
+ }
+
+ /**
+ * Maybe {@code null};
+ * set if the selector matches constructors similar to the returned one, and there was no exception.
+ */
+ public Constructor<?> getConstructor() {
+ return constructor;
+ }
+
+ /**
+ * Maybe {@code null};
+ * set if the selector matches fields similar to the returned one, and there was no exception.
+ */
+ public Field getField() {
+ return field;
+ }
+
+ /**
+ * Maybe {@code null}
+ */
+ public Exception getException() {
+ return exception;
+ }
+
+ /**
+ * Maybe {@code null}
+ */
+ public String getExceptionMemberSelectorString() {
+ return exceptionMemberSelectorString;
+ }
+
+ /**
+ * Parses a member selector that was specified with a string.
+ *
+ * @param classLoader
+ * Used to resolve class names in the member selectors. Generally you want to pick a class that belongs to
+ * you application (not to a 3rd party library, like FreeMarker), and then call
+ * {@link Class#getClassLoader()} on that. Note that the resolution of the classes is not lazy, and so the
+ * {@link ClassLoader} won't be stored after this method returns.
+ * @param memberSelectorString
+ * Describes the member (method, constructor, field) which you want to whitelist. Starts with the full
+ * qualified name of the member, like {@code com.example.MyClass.myMember}. Unless it's a field, the
+ * name is followed by comma separated list of the parameter types inside parentheses, like in
+ * {@code com.example.MyClass.myMember(java.lang.String, boolean)}. The parameter type names must be
+ * also full qualified names, except primitive type names. Array types must be indicated with one or
+ * more {@code []}-s after the type name. Varargs arguments shouldn't be marked with {@code ...}, but with
+ * {@code []}. In the member name, like {@code com.example.MyClass.myMember}, the class refers to the so
+ * called "upper bound class". Regarding that and inheritance rules see the class level documentation.
+ *
+ * @return The {@link MemberSelector}, which might has non-{@code null} {@link MemberSelector#exception}.
+ */
+ public static MemberSelector parse(String memberSelectorString, ClassLoader classLoader) {
+ if (memberSelectorString.contains("<") || memberSelectorString.contains(">")
+ || memberSelectorString.contains("...") || memberSelectorString.contains(";")) {
+ throw new IllegalArgumentException(
+ "Malformed whitelist entry (shouldn't contain \"<\", \">\", \"...\", or \";\"): "
+ + memberSelectorString);
+ }
+ String cleanedStr = memberSelectorString.trim().replaceAll("\\s*([\\.,\\(\\)\\[\\]])\\s*", "$1");
+
+ int postMemberNameIdx;
+ boolean hasArgList;
+ {
+ int openParenIdx = cleanedStr.indexOf('(');
+ hasArgList = openParenIdx != -1;
+ postMemberNameIdx = hasArgList ? openParenIdx : cleanedStr.length();
+ }
+
+ final int postClassDotIdx = cleanedStr.lastIndexOf('.', postMemberNameIdx);
+ if (postClassDotIdx == -1) {
+ throw new IllegalArgumentException("Malformed whitelist entry (missing dot): " + memberSelectorString);
+ }
+
+ Class<?> upperBoundClass;
+ String upperBoundClassStr = cleanedStr.substring(0, postClassDotIdx);
+ if (!isWellFormedClassName(upperBoundClassStr)) {
+ throw new IllegalArgumentException("Malformed whitelist entry (malformed upper bound class name): "
+ + memberSelectorString);
+ }
+ try {
+ upperBoundClass = classLoader.loadClass(upperBoundClassStr);
+ } catch (ClassNotFoundException e) {
+ return new MemberSelector(null, e, cleanedStr);
+ }
+
+ String memberName = cleanedStr.substring(postClassDotIdx + 1, postMemberNameIdx);
+ if (!isWellFormedJavaIdentifier(memberName)) {
+ throw new IllegalArgumentException(
+ "Malformed whitelist entry (malformed member name): " + memberSelectorString);
+ }
+
+ if (hasArgList) {
+ if (cleanedStr.charAt(cleanedStr.length() - 1) != ')') {
+ throw new IllegalArgumentException("Malformed whitelist entry (missing closing ')'): "
+ + memberSelectorString);
+ }
+ String argsSpec = cleanedStr.substring(postMemberNameIdx + 1, cleanedStr.length() - 1);
+ StringTokenizer tok = new StringTokenizer(argsSpec, ",");
+ int argCount = tok.countTokens();
+ Class<?>[] argTypes = new Class[argCount];
+ for (int i = 0; i < argCount; i++) {
+ String argClassName = tok.nextToken();
+ int arrayDimensions = 0;
+ while (argClassName.endsWith("[]")) {
+ arrayDimensions++;
+ argClassName = argClassName.substring(0, argClassName.length() - 2);
+ }
+ Class<?> argClass;
+ Class<?> primArgClass = ClassUtil.resolveIfPrimitiveTypeName(argClassName);
+ if (primArgClass != null) {
+ argClass = primArgClass;
+ } else {
+ if (!isWellFormedClassName(argClassName)) {
+ throw new IllegalArgumentException(
+ "Malformed whitelist entry (malformed argument class name): " + memberSelectorString);
+ }
+ try {
+ argClass = classLoader.loadClass(argClassName);
+ } catch (ClassNotFoundException e) {
+ return new MemberSelector(upperBoundClass, e, cleanedStr);
+ } catch (SecurityException e) {
+ return new MemberSelector(upperBoundClass, e, cleanedStr);
+ }
+ }
+ argTypes[i] = ClassUtil.getArrayClass(argClass, arrayDimensions);
+ }
+ try {
+ return memberName.equals(upperBoundClass.getSimpleName())
+ ? new MemberSelector(upperBoundClass, upperBoundClass.getConstructor(argTypes))
+ : new MemberSelector(upperBoundClass, upperBoundClass.getMethod(memberName, argTypes));
+ } catch (NoSuchMethodException e) {
+ return new MemberSelector(upperBoundClass, e, cleanedStr);
+ } catch (SecurityException e) {
+ return new MemberSelector(upperBoundClass, e, cleanedStr);
+ }
+ } else {
+ try {
+ return new MemberSelector(upperBoundClass, upperBoundClass.getField(memberName));
+ } catch (NoSuchFieldException e) {
+ return new MemberSelector(upperBoundClass, e, cleanedStr);
+ } catch (SecurityException e) {
+ return new MemberSelector(upperBoundClass, e, cleanedStr);
+ }
+ }
+ }
+
+ /**
+ * Convenience method to parse all member selectors in the collection; see {@link #parse(String, ClassLoader)}.
+ */
+ public static List<MemberSelector> parse(Collection<String> memberSelectors,
+ ClassLoader classLoader) {
+ List<MemberSelector> parsedMemberSelectors = new ArrayList<MemberSelector>(memberSelectors.size());
+ for (String memberSelector : memberSelectors) {
+ parsedMemberSelectors.add(parse(memberSelector, classLoader));
+ }
+ return parsedMemberSelectors;
+ }
+ }
+
+ public WhitelistMemberAccessPolicy(Collection<MemberSelector> memberSelectors) {
+ methodMatcher = new MethodMatcher();
+ constructorMatcher = new ConstructorMatcher();
+ fieldMatcher = new FieldMatcher();
+ for (MemberSelector memberSelector : memberSelectors) {
+ Class<?> upperBoundClass = memberSelector.upperBoundType;
+ if (memberSelector.exception != null) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Member selector ignored due to error: " + memberSelector.getExceptionMemberSelectorString(),
+ memberSelector.exception);
+ }
+ } else if (memberSelector.constructor != null) {
+ constructorMatcher.addMatching(upperBoundClass, memberSelector.constructor);
+ } else if (memberSelector.method != null) {
+ methodMatcher.addMatching(upperBoundClass, memberSelector.method);
+ } else if (memberSelector.field != null) {
+ fieldMatcher.addMatching(upperBoundClass, memberSelector.field);
+ } else {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ public ClassMemberAccessPolicy forClass(final Class<?> contextClass) {
+ return new ClassMemberAccessPolicy() {
+ public boolean isMethodExposed(Method method) {
+ return methodMatcher.matches(contextClass, method)
+ || _MethodUtil.getInheritableAnnotation(contextClass, method, TemplateAccessible.class) != null;
+ }
+
+ public boolean isConstructorExposed(Constructor<?> constructor) {
+ return constructorMatcher.matches(contextClass, constructor)
+ || _MethodUtil.getInheritableAnnotation(contextClass, constructor, TemplateAccessible.class)
+ != null;
+ }
+
+ public boolean isFieldExposed(Field field) {
+ return fieldMatcher.matches(contextClass, field)
+ || _MethodUtil.getInheritableAnnotation(contextClass, field, TemplateAccessible.class) != null;
+ }
+ };
+ }
+
+ private static boolean isWellFormedClassName(String s) {
+ if (s.length() == 0) {
+ return false;
+ }
+ int identifierStartIdx = 0;
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (i == identifierStartIdx) {
+ if (!Character.isJavaIdentifierStart(c)) {
+ return false;
+ }
+ } else if (c == '.' && i != s.length() - 1) {
+ identifierStartIdx = i + 1;
+ } else {
+ if (!Character.isJavaIdentifierPart(c)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private static boolean isWellFormedJavaIdentifier(String s) {
+ if (s.length() == 0) {
+ return false;
+ }
+ if (!Character.isJavaIdentifierStart(s.charAt(0))) {
+ return false;
+ }
+ for (int i = 1; i < s.length(); i++) {
+ if (!Character.isJavaIdentifierPart(s.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/src/main/java/freemarker/ext/beans/_MethodUtil.java b/src/main/java/freemarker/ext/beans/_MethodUtil.java
index 782b944..9f743bc 100644
--- a/src/main/java/freemarker/ext/beans/_MethodUtil.java
+++ b/src/main/java/freemarker/ext/beans/_MethodUtil.java
@@ -18,7 +18,9 @@
*/
package freemarker.ext.beans;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
@@ -317,4 +319,143 @@ public final class _MethodUtil {
.toString();
}
+ /**
+ * Similar to {@link Method#getAnnotation(Class)}, but will also search the annotation in the implemented
+ * interfaces and in the ancestor classes.
+ */
+ public static <T extends Annotation> T getInheritableAnnotation(Class<?> contextClass, Method method, Class<T> annotationClass) {
+ T result = method.getAnnotation(annotationClass);
+ if (result != null) {
+ return result;
+ }
+ return getInheritableMethodAnnotation(
+ contextClass, method.getName(), method.getParameterTypes(), true, annotationClass);
+ }
+
+ private static <T extends Annotation> T getInheritableMethodAnnotation(
+ Class<?> contextClass, String methodName, Class<?>[] methodParamTypes,
+ boolean skipCheckingDirectMethod,
+ Class<T> annotationClass) {
+ if (!skipCheckingDirectMethod) {
+ Method similarMethod;
+ try {
+ similarMethod = contextClass.getMethod(methodName, methodParamTypes);
+ } catch (NoSuchMethodException e) {
+ similarMethod = null;
+ }
+ if (similarMethod != null) {
+ T result = similarMethod.getAnnotation(annotationClass);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ for (Class<?> anInterface : contextClass.getInterfaces()) {
+ if (!anInterface.getName().startsWith("java.")) {
+ Method similarInterfaceMethod;
+ try {
+ similarInterfaceMethod = anInterface.getMethod(methodName, methodParamTypes);
+ } catch (NoSuchMethodException e) {
+ similarInterfaceMethod = null;
+ }
+ if (similarInterfaceMethod != null) {
+ T result = similarInterfaceMethod.getAnnotation(annotationClass);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ }
+ Class<?> superClass = contextClass.getSuperclass();
+ if (superClass == Object.class || superClass == null) {
+ return null;
+ }
+ return getInheritableMethodAnnotation(superClass, methodName, methodParamTypes, false, annotationClass);
+ }
+
+ /**
+ * Similar to {@link Constructor#getAnnotation(Class)}, but will also search the annotation in the implemented
+ * interfaces and in the ancestor classes.
+ */
+ public static <T extends Annotation> T getInheritableAnnotation(
+ Class<?> contextClass, Constructor<?> constructor, Class<T> annotationClass) {
+ T result = constructor.getAnnotation(annotationClass);
+ if (result != null) {
+ return result;
+ }
+
+ Class<?>[] paramTypes = constructor.getParameterTypes();
+ while (true) {
+ contextClass = contextClass.getSuperclass();
+ if (contextClass == Object.class || contextClass == null) {
+ return null;
+ }
+ try {
+ constructor = contextClass.getConstructor(paramTypes);
+ } catch (NoSuchMethodException e) {
+ constructor = null;
+ }
+ if (constructor != null) {
+ result = constructor.getAnnotation(annotationClass);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ }
+
+ /**
+ * Similar to {@link Field#getAnnotation(Class)}, but will also search the annotation in the implemented
+ * interfaces and in the ancestor classes.
+ */
+ public static <T extends Annotation> T getInheritableAnnotation(Class<?> contextClass, Field field, Class<T> annotationClass) {
+ T result = field.getAnnotation(annotationClass);
+ if (result != null) {
+ return result;
+ }
+ return getInheritableFieldAnnotation(
+ contextClass, field.getName(), true, annotationClass);
+ }
+
+ private static <T extends Annotation> T getInheritableFieldAnnotation(
+ Class<?> contextClass, String fieldName,
+ boolean skipCheckingDirectField,
+ Class<T> annotationClass) {
+ if (!skipCheckingDirectField) {
+ Field similarField;
+ try {
+ similarField = contextClass.getField(fieldName);
+ } catch (NoSuchFieldException e) {
+ similarField = null;
+ }
+ if (similarField != null) {
+ T result = similarField.getAnnotation(annotationClass);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ for (Class<?> anInterface : contextClass.getInterfaces()) {
+ if (!anInterface.getName().startsWith("java.")) {
+ Field similarInterfaceField;
+ try {
+ similarInterfaceField = anInterface.getField(fieldName);
+ } catch (NoSuchFieldException e) {
+ similarInterfaceField = null;
+ }
+ if (similarInterfaceField != null) {
+ T result = similarInterfaceField.getAnnotation(annotationClass);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ }
+ Class<?> superClass = contextClass.getSuperclass();
+ if (superClass == Object.class || superClass == null) {
+ return null;
+ }
+ return getInheritableFieldAnnotation(superClass, fieldName, false, annotationClass);
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/freemarker/template/DefaultObjectWrapper.java b/src/main/java/freemarker/template/DefaultObjectWrapper.java
index 4c5a39d..8333a00 100644
--- a/src/main/java/freemarker/template/DefaultObjectWrapper.java
+++ b/src/main/java/freemarker/template/DefaultObjectWrapper.java
@@ -32,6 +32,7 @@ import org.w3c.dom.Node;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.BeansWrapperConfiguration;
+import freemarker.ext.beans.DefaultMemberAccessPolicy;
import freemarker.ext.beans.EnumerationModel;
import freemarker.ext.dom.NodeModel;
import freemarker.log.Logger;
@@ -252,7 +253,8 @@ public class DefaultObjectWrapper extends freemarker.ext.beans.BeansWrapper {
* Called for an object that isn't considered to be of a "basic" Java type, like for an application specific type,
* or for a W3C DOM node. In its default implementation, W3C {@link Node}-s will be wrapped as {@link NodeModel}-s
* (allows DOM tree traversal), Jython objects will be delegated to the {@code JythonWrapper}, others will be
- * wrapped using {@link BeansWrapper#wrap(Object)}.
+ * wrapped using {@link BeansWrapper#wrap(Object)}. Note that if {@link #getMemberAccessPolicy()} doesn't return
+ * a {@link DefaultMemberAccessPolicy}, then Jython wrapper will be skipped for security reasons.
*
* <p>
* When you override this method, you should first decide if you want to wrap the object in a custom way (and if so
@@ -263,8 +265,10 @@ public class DefaultObjectWrapper extends freemarker.ext.beans.BeansWrapper {
if (obj instanceof Node) {
return wrapDomNode(obj);
}
- if (JYTHON_WRAPPER != null && JYTHON_OBJ_CLASS.isInstance(obj)) {
- return JYTHON_WRAPPER.wrap(obj);
+ if (getMemberAccessPolicy() instanceof DefaultMemberAccessPolicy) {
+ if (JYTHON_WRAPPER != null && JYTHON_OBJ_CLASS.isInstance(obj)) {
+ return JYTHON_WRAPPER.wrap(obj);
+ }
}
return super.wrap(obj);
}
diff --git a/src/main/java/freemarker/template/utility/ClassUtil.java b/src/main/java/freemarker/template/utility/ClassUtil.java
index ad19750..95fd3b8 100644
--- a/src/main/java/freemarker/template/utility/ClassUtil.java
+++ b/src/main/java/freemarker/template/utility/ClassUtil.java
@@ -21,8 +21,11 @@ package freemarker.template.utility;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.reflect.Array;
import java.net.URL;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Properties;
import java.util.Set;
@@ -88,7 +91,39 @@ public class ClassUtil {
// Fall back to the defining class loader of the FreeMarker classes
return Class.forName(className);
}
-
+
+ private static final Map<String, Class<?>> PRIMITIVE_CLASSES_BY_NAME;
+ static {
+ PRIMITIVE_CLASSES_BY_NAME = new HashMap<String, Class<?>>();
+ PRIMITIVE_CLASSES_BY_NAME.put("boolean", boolean.class);
+ PRIMITIVE_CLASSES_BY_NAME.put("byte", byte.class);
+ PRIMITIVE_CLASSES_BY_NAME.put("char", char.class);
+ PRIMITIVE_CLASSES_BY_NAME.put("short", short.class);
+ PRIMITIVE_CLASSES_BY_NAME.put("int", int.class);
+ PRIMITIVE_CLASSES_BY_NAME.put("long", long.class);
+ PRIMITIVE_CLASSES_BY_NAME.put("float", float.class);
+ PRIMITIVE_CLASSES_BY_NAME.put("double", double.class);
+ }
+
+ /**
+ * Returns the {@link Class} for a primitive type name, or {@code null} if it's not the name of a primitive type.
+ *
+ * @since 2.3.30
+ */
+ public static Class<?> resolveIfPrimitiveTypeName(String typeName) {
+ return PRIMITIVE_CLASSES_BY_NAME.get(typeName);
+ }
+
+ /**
+ * Returns the array type that corresponds to the element type and the given number of array dimensions.
+ * If the dimension is 0, it just returns the element type as is.
+ *
+ * @since 2.3.30
+ */
+ public static Class<?> getArrayClass(Class<?> elementType, int dimensions) {
+ return dimensions == 0 ? elementType : Array.newInstance(elementType, new int[dimensions]).getClass();
+ }
+
/**
* Same as {@link #getShortClassName(Class, boolean) getShortClassName(pClass, false)}.
*
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 4b10a92..a2732b6 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/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>
@@ -28887,10 +28884,11 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting>
<answer>
<para>In general you shouldn't allow that, unless those users are
- system administrators or other trusted personnel. Consider
- templates as part of the source code just like
- <literal>*.java</literal> files are. If you still want to allow
- users to upload templates, here are what to consider:</para>
+ application developers, system administrators, or other highly
+ trusted personnel. Consider templates as part of the source code
+ just like <literal>*.java</literal> files are. If you still want
+ to allow users to upload templates, here's what to
+ consider:</para>
<itemizedlist>
<listitem>
@@ -28915,11 +28913,17 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting>
<literal>List</literal>-s, <literal>Array</literal>-s,
<literal>String</literal>-s, <literal>Number</literal>-s,
<literal>Boolean</literal>-s and <literal>Date</literal>-s.
- For many application though that's too restrictive, and
- instead you need to implement your own extremely restrictive
- <literal>ObjectWrapper</literal>, which, for example, only
- exposes those members of POJO-s that were explicitly marked to
- be safe (opt-in approach).</para>
+ But for many application that's too restrictive, and instead
+ you have to create a
+ <literal>WhitelistMemberAccessPolicy</literal>, and create a
+ <literal>DefaultObjectWrapper</literal> (or other
+ <literal>BeansWrapper</literal> subclass that you would use)
+ that uses that. See the Java API documentation of
+ <literal>WhitelistMemberAccessPolicy</literal> for more. (Or,
+ you can roll your own <literal>MemberAccessPolicy</literal>
+ implementation, or even your own restrictive
+ <literal>ObjectWrapper</literal> implementation of
+ course.)</para>
<para>Also, don't forget about the <link
linkend="ref_buitin_api_and_has_api"><literal>?api</literal>
@@ -28935,16 +28939,23 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting>
the <literal>ObjectWrapper</literal> is still in control, as
it decides what objects support <literal>?api</literal>, and
what will <literal>?api</literal> expose for them (it usually
- exposes the same as for a generic POJO).</para>
+ exposes the same as for a generic POJO). Members not allowed
+ by the <literal>MemberAccessPolicy</literal> also won't be
+ visible with <literal>?api</literal> (assuming you are using a
+ well behaving <literal>ObjectWrapper</literal>, like
+ <literal>DefaultObjectWrapper</literal> is, hopefully.)
+ </para>
<para>Last not least, some maybe aware of that the standard
object wrappers filters out some well known
<quote>unsafe</quote> methods, like
- <literal>System.exit</literal>. Do not ever rely on this as
- your only line of defense, since it only blocks the methods
- that's in a predefined list. Thus, for example, if a new Java
- version adds a new problematic method, it won't be filtered
- out.</para>
+ <literal>System.exit</literal>. Do not ever rely on that,
+ since it only blocks the methods that's in a small predefined
+ list (for some historical reasons). The standard Java API is
+ huge and ever growing, and then there are the 3rd party
+ libraries, and the API-s of your own application. Clearly it's
+ impossible to blacklist all the problematic members in
+ those.</para>
</listitem>
<listitem>
@@ -28986,7 +28997,13 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting>
<literal>TemplateClassResolver</literal> that restricts the
accessible classes (possibly based on which template asks for
them), such as
- <literal>TemplateClassResolver.ALLOWS_NOTHING_RESOLVER</literal>.</para>
+ <literal>TemplateClassResolver.ALLOWS_NOTHING_RESOLVER</literal>.
+ Note that if, and only if your
+ <literal>ObjectWrapper</literal> is a
+ <literal>BeansWrapper</literal> or a subclass of it (typically
+ <literal>DefaultObjectWrapper</literal>), constructors not
+ allowed by the <literal>MemberAccessPolicy</literal> also
+ won't be accessible for <literal>?new</literal>. </para>
</listitem>
<listitem>
@@ -29197,6 +29214,23 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting>
<listitem>
<para>Added
+ <literal>freemarker.ext.beans.WhitelistMemberAccessPolicy</literal>,
+ which is a <literal>MemberAccessPolicy</literal> for use cases
+ where you want to allow editing templates to users who shouldn't
+ have the same rights as the developers (the same rights as the
+ Java application). Earlier, the only out of the box solution for
+ that was <literal>SimpleObjectWrapper</literal>, but that's too
+ restrictive for most applications where FreeMarker is used.
+ <literal>WhitelistMemberAccessPolicy</literal> works with
+ <literal>DefaultObjectWrapper</literal> (or any other
+ <literal>BeansWrapper</literal>), allowing you to use all
+ features of it, but it will only allow accessing members that
+ were explicitly listed by the developers, or was annotated with
+ <literal>@TemplateAccessible</literal>.</para>
+ </listitem>
+
+ <listitem>
+ <para>Added
<literal>Environment.getDataModelOrSharedVariable(String)</literal>.</para>
</listitem>
diff --git a/src/test/java/freemarker/ext/beans/DefaultObjectWrapperMemberAccessPolicyTest.java b/src/test/java/freemarker/ext/beans/DefaultObjectWrapperMemberAccessPolicyTest.java
index 355a769..eafbaf2 100644
--- a/src/test/java/freemarker/ext/beans/DefaultObjectWrapperMemberAccessPolicyTest.java
+++ b/src/test/java/freemarker/ext/beans/DefaultObjectWrapperMemberAccessPolicyTest.java
@@ -22,24 +22,32 @@ package freemarker.ext.beans;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
+import java.io.IOException;
+import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import org.junit.Test;
+import com.google.common.collect.ImmutableMap;
+
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.DefaultObjectWrapperBuilder;
import freemarker.template.ObjectWrapperAndUnwrapper;
import freemarker.template.SimpleNumber;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateNumberModel;
public class DefaultObjectWrapperMemberAccessPolicyTest {
@@ -166,7 +174,7 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
public void testMethodsWithCustomMemberAccessPolicy() throws TemplateModelException {
DefaultObjectWrapperBuilder owb = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30);
owb.setMemberAccessPolicy(new MemberAccessPolicy() {
- public ClassMemberAccessPolicy forClass(Class<?> containingClass) {
+ public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
return new ClassMemberAccessPolicy() {
public boolean isMethodExposed(Method method) {
String name = method.getName();
@@ -208,7 +216,7 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
DefaultObjectWrapperBuilder owb = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30);
owb.setExposeFields(true);
owb.setMemberAccessPolicy(new MemberAccessPolicy() {
- public ClassMemberAccessPolicy forClass(Class<?> containingClass) {
+ public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
return new ClassMemberAccessPolicy() {
public boolean isMethodExposed(Method method) {
return true;
@@ -238,7 +246,7 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
public void testGenericGetWithCustomMemberAccessPolicy() throws TemplateModelException {
DefaultObjectWrapperBuilder owb = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30);
owb.setMemberAccessPolicy(new MemberAccessPolicy() {
- public ClassMemberAccessPolicy forClass(Class<?> containingClass) {
+ public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
return new ClassMemberAccessPolicy() {
public boolean isMethodExposed(Method method) {
return false;
@@ -264,7 +272,7 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
public void testConstructorsWithCustomMemberAccessPolicy() throws TemplateModelException {
DefaultObjectWrapperBuilder owb = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30);
owb.setMemberAccessPolicy(new MemberAccessPolicy() {
- public ClassMemberAccessPolicy forClass(Class<?> containingClass) {
+ public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
return new ClassMemberAccessPolicy() {
public boolean isMethodExposed(Method method) {
return true;
@@ -305,6 +313,93 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
Collections.singletonList(new SimpleNumber(1))).getClass());
}
+ @Test
+ public void testMemberAccessPolicyAndApiBI() throws IOException, TemplateException {
+ DefaultObjectWrapperBuilder owb = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30);
+ owb.setMemberAccessPolicy(new MemberAccessPolicy() {
+ public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
+ return new ClassMemberAccessPolicy() {
+ public boolean isMethodExposed(Method method) {
+ return method.getName().equals("size");
+ }
+
+ public boolean isConstructorExposed(Constructor<?> constructor) {
+ return true;
+ }
+
+ public boolean isFieldExposed(Field field) {
+ return true;
+ }
+ };
+ }
+ });
+ DefaultObjectWrapper ow = owb.build();
+
+ Map<String, Object> dataModel = ImmutableMap.<String, Object>of("m", ImmutableMap.of("k", "v"));
+
+ Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);
+ cfg.setObjectWrapper(ow);
+ cfg.setAPIBuiltinEnabled(true);
+ Template template = new Template(null, "size=${m?api.size()} get=${(m?api.get('k'))!'hidden'}", cfg);
+
+ {
+ StringWriter out = new StringWriter();
+ template.process(dataModel, out);
+ assertEquals("size=1 get=hidden", out.toString());
+ }
+
+ cfg.setObjectWrapper(new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30).build());
+ {
+ StringWriter out = new StringWriter();
+ template.process(dataModel, out);
+ assertEquals("size=1 get=v", out.toString());
+ }
+ }
+
+ @Test
+ public void testMemberAccessPolicyAndNewBI() throws IOException, TemplateException {
+ DefaultObjectWrapperBuilder owb = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30);
+ owb.setMemberAccessPolicy(new MemberAccessPolicy() {
+ public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
+ return new ClassMemberAccessPolicy() {
+ public boolean isMethodExposed(Method method) {
+ return true;
+ }
+
+ public boolean isConstructorExposed(Constructor<?> constructor) {
+ return constructor.getDeclaringClass().equals(CustomModel.class);
+ }
+
+ public boolean isFieldExposed(Field field) {
+ return true;
+ }
+ };
+ }
+ });
+ DefaultObjectWrapper ow = owb.build();
+
+ Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);
+ cfg.setObjectWrapper(ow);
+ cfg.setAPIBuiltinEnabled(true);
+ Template template = new Template(null,
+ "${'" + CustomModel.class.getName() + "'?new()} "
+ + "<#attempt>${'" + OtherCustomModel.class.getName() + "'?new()}<#recover>failed</#attempt>",
+ cfg);
+
+ {
+ StringWriter out = new StringWriter();
+ template.process(null, out);
+ assertEquals("1 failed", out.toString());
+ }
+
+ cfg.setObjectWrapper(new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30).build());
+ {
+ StringWriter out = new StringWriter();
+ template.process(null, out);
+ assertEquals("1 2", out.toString());
+ }
+ }
+
private static DefaultObjectWrapper createDefaultMemberAccessPolicyObjectWrapper() {
return new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30).build();
}
@@ -406,4 +501,16 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
}
}
+ public static class CustomModel implements TemplateNumberModel {
+ public Number getAsNumber() {
+ return 1;
+ }
+ }
+
+ public static class OtherCustomModel implements TemplateNumberModel {
+ public Number getAsNumber() {
+ return 2;
+ }
+ }
+
}
diff --git a/src/test/java/freemarker/ext/beans/MethodMatcherTest.java b/src/test/java/freemarker/ext/beans/MethodMatcherTest.java
new file mode 100644
index 0000000..9ac9ca9
--- /dev/null
+++ b/src/test/java/freemarker/ext/beans/MethodMatcherTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.ext.beans;
+
+import static org.junit.Assert.*;
+
+import java.lang.reflect.Method;
+
+import org.junit.Test;
+
+public class MethodMatcherTest {
+
+ @Test
+ public void testReturnTypeOverload() throws NoSuchMethodException {
+ MethodMatcher matcher = new MethodMatcher();
+ Method genericM = TestReturnTypeOverloadGeneric.class.getMethod("m");
+ assertEquals(Object.class, genericM.getReturnType());
+ matcher.addMatching(TestReturnTypeOverloadGeneric.class, genericM);
+
+ Method stringM = TestReturnTypeOverloadString.class.getMethod("m");
+ assertEquals(String.class, stringM.getReturnType());
+
+ assertTrue(matcher.matches(TestReturnTypeOverloadGeneric.class, genericM));
+ assertTrue(matcher.matches(TestReturnTypeOverloadString.class, genericM));
+ assertTrue(matcher.matches(TestReturnTypeOverloadString.class, stringM));
+ }
+
+ public static class TestReturnTypeOverloadGeneric<T> {
+ public T m() {
+ return null;
+ };
+ }
+
+ public static class TestReturnTypeOverloadString extends TestReturnTypeOverloadGeneric<String> {
+ public String m() {
+ return "";
+ };
+ }
+
+ /** Mostly to test upper bound classes. */
+ @Test
+ public void testInheritance() throws NoSuchMethodException {
+ {
+ MethodMatcher matcher = new MethodMatcher();
+ Method m = TestInheritanceC2.class.getMethod("m1");
+ assertEquals(m, TestInheritanceC1.class.getMethod("m1"));
+ matcher.addMatching(TestInheritanceC2.class, m);
+ assertFalse(matcher.matches(TestInheritanceC1.class, m));
+ assertTrue(matcher.matches(TestInheritanceC2.class, m));
+ assertTrue(matcher.matches(TestInheritanceC3.class, m));
+ }
+ {
+ MethodMatcher matcher = new MethodMatcher();
+ Method m = TestInheritanceC2.class.getMethod("m2");
+ assertNotEquals(m, TestInheritanceC1.class.getMethod("m2"));
+ matcher.addMatching(TestInheritanceC2.class, m);
+ assertFalse(matcher.matches(TestInheritanceC1.class, m));
+ assertTrue(matcher.matches(TestInheritanceC2.class, m));
+ assertTrue(matcher.matches(TestInheritanceC3.class, m));
+ }
+ {
+ // m2 again, but with a non-same-instance but "equal" method.
+ MethodMatcher matcher = new MethodMatcher();
+ Method m = TestInheritanceC1.class.getMethod("m2");
+ matcher.addMatching(TestInheritanceC2.class, m);
+ assertFalse(matcher.matches(TestInheritanceC1.class, m));
+ assertTrue(matcher.matches(TestInheritanceC2.class, m));
+ assertTrue(matcher.matches(TestInheritanceC3.class, m));
+ }
+ {
+ MethodMatcher matcher = new MethodMatcher();
+ Method m = TestInheritanceC2.class.getMethod("m3");
+ assertEquals(m, TestInheritanceC1.class.getMethod("m3"));
+ assertNotEquals(m, TestInheritanceC3.class.getMethod("m3"));
+ matcher.addMatching(TestInheritanceC2.class, m);
+ assertFalse(matcher.matches(TestInheritanceC1.class, m));
+ assertTrue(matcher.matches(TestInheritanceC2.class, m));
+ assertTrue(matcher.matches(TestInheritanceC3.class, m));
+ }
+ }
+
+ public static class TestInheritanceC1 {
+ public void m1() {
+ }
+
+ public void m2() {
+ }
+
+ public void m3() {
+ }
+ }
+
+ public static class TestInheritanceC2 extends TestInheritanceC1 {
+ @Override
+ public void m2() {
+ }
+ }
+
+ public static class TestInheritanceC3 extends TestInheritanceC2 {
+ @Override
+ public void m3() {
+ }
+ }
+
+ /** Mostly to test when same method associated to multiple unrelated classes. */
+ @Test
+ public void testInheritance2() throws NoSuchMethodException {
+ MethodMatcher matcher = new MethodMatcher();
+ Method m = Runnable.class.getMethod("run");
+ matcher.addMatching(TestInheritance2SafeRunnable1.class, m);
+ matcher.addMatching(TestInheritance2SafeRunnable2.class, m);
+
+ assertTrue(matcher.matches(
+ TestInheritance2SafeRunnable1.class, TestInheritance2SafeRunnable1.class.getMethod("run")));
+ assertTrue(matcher.matches(
+ TestInheritance2SafeRunnable2.class, TestInheritance2SafeRunnable2.class.getMethod("run")));
+ assertFalse(matcher.matches(
+ TestInheritance2UnsafeRunnable.class, TestInheritance2UnsafeRunnable.class.getMethod("run")));
+ }
+
+ public static class TestInheritance2SafeRunnable1 implements Runnable {
+ public void run() {
+ }
+ }
+
+ public static class TestInheritance2SafeRunnable2 implements Runnable {
+ public void run() {
+ }
+ }
+
+ public static class TestInheritance2UnsafeRunnable implements Runnable {
+ public void run() {
+ }
+ }
+
+ @Test
+ public void testOverloads() throws NoSuchMethodException {
+ Method mInt = TestOverloads.class.getMethod("m", int.class);
+ Method mIntInt = TestOverloads.class.getMethod("m", int.class, int.class);
+ {
+ MethodMatcher matcher = new MethodMatcher();
+ matcher.addMatching(TestOverloads.class, mInt);
+ assertTrue(matcher.matches(TestOverloads.class, mInt));
+ assertFalse(matcher.matches(TestOverloads.class, mIntInt));
+ }
+ {
+ MethodMatcher matcher = new MethodMatcher();
+ matcher.addMatching(TestOverloads.class, mIntInt);
+ assertFalse(matcher.matches(TestOverloads.class, mInt));
+ assertTrue(matcher.matches(TestOverloads.class, mIntInt));
+ }
+ }
+
+ public static class TestOverloads {
+ public void m(int x) {
+ }
+
+ public void m(int x, int y) {
+ }
+ }
+
+}
diff --git a/src/test/java/freemarker/ext/beans/MethodUtilTest.java b/src/test/java/freemarker/ext/beans/MethodUtilTest.java
new file mode 100644
index 0000000..24291b3
--- /dev/null
+++ b/src/test/java/freemarker/ext/beans/MethodUtilTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.ext.beans;
+
+import static org.junit.Assert.*;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+
+import org.junit.Test;
+
+public class MethodUtilTest {
+
+ @Test
+ public void testMethodBasic() throws NoSuchMethodException, NoSuchFieldException {
+ assertNotNull(_MethodUtil.getInheritableAnnotation(
+ C1.class, C1.class.getMethod("m1"), TemplateAccessible.class));
+ assertNull(_MethodUtil.getInheritableAnnotation(
+ C1.class, C1.class.getMethod("m2"), TemplateAccessible.class));
+
+ assertNotNull(_MethodUtil.getInheritableAnnotation(
+ C1.class, C1.class.getConstructor(int.class), TemplateAccessible.class));
+ assertNull(_MethodUtil.getInheritableAnnotation(
+ C1.class, C1.class.getConstructor(int.class, int.class), TemplateAccessible.class));
+
+ assertNotNull(_MethodUtil.getInheritableAnnotation(
+ C1.class, C1.class.getField("f1"), TemplateAccessible.class));
+ assertNull(_MethodUtil.getInheritableAnnotation(
+ C1.class, C1.class.getField("f3"), TemplateAccessible.class));
+ }
+
+ @Test
+ public void testMethodInheritance() throws NoSuchMethodException, NoSuchFieldException {
+ assertNotNull(_MethodUtil.getInheritableAnnotation(
+ C2.class, C2.class.getMethod("m1"), TemplateAccessible.class));
+ assertNotNull(_MethodUtil.getInheritableAnnotation(
+ C2.class, C2.class.getMethod("m2"), TemplateAccessible.class));
+ assertNotNull(_MethodUtil.getInheritableAnnotation(
+ C2.class, C2.class.getMethod("m3"), TemplateAccessible.class));
+ assertNotNull(_MethodUtil.getInheritableAnnotation(
+ C2.class, C2.class.getMethod("m4"), TemplateAccessible.class));
+ assertNotNull(_MethodUtil.getInheritableAnnotation(
+ C2.class, C2.class.getMethod("m5"), TemplateAccessible.class));
+
+ assertNotNull(_MethodUtil.getInheritableAnnotation(
+ C2.class, C2.class.getConstructor(int.class), TemplateAccessible.class));
+ assertNull(_MethodUtil.getInheritableAnnotation(
+ C2.class, C2.class.getConstructor(), TemplateAccessible.class));
+
+ assertNotNull(_MethodUtil.getInheritableAnnotation(
+ C2.class, C2.class.getField("f1"), TemplateAccessible.class));
+ assertNotNull(_MethodUtil.getInheritableAnnotation(
+ C2.class, C2.class.getField("f2"), TemplateAccessible.class));
+ assertNull(_MethodUtil.getInheritableAnnotation(
+ C2.class, C2.class.getField("f3"), TemplateAccessible.class));
+ assertNotNull(_MethodUtil.getInheritableAnnotation(
+ C2.class, C2.class.getField("f4"), TemplateAccessible.class));
+ }
+
+ @Test
+ public void testMethodInheritanceWithSyntheticMethod() {
+ for (Method method : D2.class.getMethods()) {
+ if (method.getName().equals("m1")) {
+ assertNotNull(_MethodUtil.getInheritableAnnotation(
+ C2.class, method, TemplateAccessible.class));
+ }
+ }
+ }
+
+ static public class C1 implements Serializable {
+ @TemplateAccessible
+ public int f1;
+
+ @TemplateAccessible
+ public int f2;
+
+ public int f3;
+
+ public int f4;
+
+ @TemplateAccessible
+ public C1(int x) {}
+
+ public C1(int x, int y) {}
+
+ @TemplateAccessible
+ public void m1() {}
+
+ public void m2() {}
+
+ public void m3() {}
+
+ @TemplateAccessible
+ public void m4() {}
+
+ @TemplateAccessible
+ public void m5() {}
+ }
+
+ static public class C2 extends C1 implements I1 {
+ public long f2;
+
+ public C2() {
+ super(0);
+ }
+
+ public C2(int x) {
+ super(x);
+ }
+
+ @Override
+ public void m1() {}
+
+ @TemplateAccessible
+ @Override
+ public void m3() {}
+ }
+
+ public interface I1 {
+ @TemplateAccessible
+ int f4 = 0;
+
+ @TemplateAccessible
+ void m2();
+
+ void m5();
+ }
+
+ public static class D1<T> {
+ @TemplateAccessible
+ public T m1() { return null; }
+ }
+
+ public static class D2 extends D1<String> {
+ @Override
+ public String m1() { return ""; }
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/freemarker/ext/beans/WhitelistMemberAccessPolicyTest.java b/src/test/java/freemarker/ext/beans/WhitelistMemberAccessPolicyTest.java
new file mode 100644
index 0000000..0edee07
--- /dev/null
+++ b/src/test/java/freemarker/ext/beans/WhitelistMemberAccessPolicyTest.java
@@ -0,0 +1,558 @@
+/*
+ * 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.ext.beans;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.junit.Test;
+
+import freemarker.template.Configuration;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateHashModel;
+
+public class WhitelistMemberAccessPolicyTest {
+
+ @Test
+ public void testEmpty() throws NoSuchMethodException, NoSuchFieldException {
+ WhitelistMemberAccessPolicy policy = newWhitelistMemberAccessPolicy();
+ ClassMemberAccessPolicy classPolicy = policy.forClass(C1.class);
+ assertFalse(classPolicy.isConstructorExposed(C1.class.getConstructor()));
+ assertFalse(classPolicy.isMethodExposed(C1.class.getMethod("m1")));
+ assertFalse(classPolicy.isFieldExposed(C1.class.getField("f1")));
+ }
+
+ @Test
+ public void testBasics() throws NoSuchMethodException, NoSuchFieldException {
+ WhitelistMemberAccessPolicy policy = newWhitelistMemberAccessPolicy(
+ C1.class.getName() + "." + C1.class.getSimpleName() + "()",
+ C1.class.getName() + ".m1()",
+ C1.class.getName() + ".m2(int)",
+ C1.class.getName() + ".f1");
+
+ {
+ ClassMemberAccessPolicy c1Policy = policy.forClass(C1.class);
+ assertTrue(c1Policy.isConstructorExposed(C1.class.getConstructor()));
+ assertTrue(c1Policy.isMethodExposed(C1.class.getMethod("m1")));
+ assertTrue(c1Policy.isMethodExposed(C1.class.getMethod("m2", int.class)));
+ assertTrue(c1Policy.isFieldExposed(C1.class.getField("f1")));
+ }
+
+ {
+ ClassMemberAccessPolicy d1Policy = policy.forClass(D1.class);
+ assertFalse(d1Policy.isMethodExposed(D1.class.getMethod("m1")));
+ assertFalse(d1Policy.isFieldExposed(D1.class.getField("f1")));
+ }
+ }
+
+ @Test
+ public void testInheritanceAndMoreOverloads() throws NoSuchMethodException, NoSuchFieldException {
+ WhitelistMemberAccessPolicy policy = newWhitelistMemberAccessPolicy(
+ C1.class.getName() + ".m2(int)",
+ C1.class.getName() + ".f1",
+ C2.class.getName() + "." + C2.class.getSimpleName() + "(int)",
+ C2.class.getName() + ".m1()",
+ C2.class.getName() + ".m2(boolean)",
+ C3.class.getName() + ".f2",
+ C3.class.getName() + "." + C3.class.getSimpleName() + "()",
+ C3.class.getName() + ".m4()",
+ C3.class.getName() + ".f3"
+ );
+ ClassMemberAccessPolicy c1Policy = policy.forClass(C1.class);
+ ClassMemberAccessPolicy c2Policy = policy.forClass(C2.class);
+ ClassMemberAccessPolicy c3Policy = policy.forClass(C3.class);
+
+ assertTrue(c1Policy.isMethodExposed(C1.class.getMethod("m2", int.class)));
+ assertTrue(c2Policy.isMethodExposed(C2.class.getMethod("m2", int.class)));
+ assertTrue(c3Policy.isMethodExposed(C3.class.getMethod("m2", int.class)));
+
+ assertTrue(c1Policy.isFieldExposed(C1.class.getField("f1")));
+ assertTrue(c2Policy.isFieldExposed(C2.class.getField("f1")));
+ assertTrue(c3Policy.isFieldExposed(C3.class.getField("f1")));
+
+ assertFalse(c1Policy.isConstructorExposed(C1.class.getConstructor(int.class)));
+ assertTrue(c2Policy.isConstructorExposed(C2.class.getConstructor(int.class)));
+ assertTrue(c3Policy.isConstructorExposed(C3.class.getConstructor(int.class)));
+
+ assertFalse(c1Policy.isMethodExposed(C1.class.getMethod("m1")));
+ assertTrue(c2Policy.isMethodExposed(C2.class.getMethod("m1")));
+ assertTrue(c3Policy.isMethodExposed(C3.class.getMethod("m1")));
+
+ assertFalse(c1Policy.isMethodExposed(C2.class.getMethod("m2", boolean.class))); // Doesn't exist in C1
+ assertTrue(c2Policy.isMethodExposed(C2.class.getMethod("m2", boolean.class)));
+ assertTrue(c3Policy.isMethodExposed(C3.class.getMethod("m2", boolean.class)));
+
+ assertFalse(c1Policy.isFieldExposed(C1.class.getField("f2")));
+ assertFalse(c2Policy.isFieldExposed(C2.class.getField("f2")));
+ assertTrue(c3Policy.isFieldExposed(C3.class.getField("f2")));
+
+ assertFalse(c1Policy.isConstructorExposed(C1.class.getConstructor()));
+ assertFalse(c2Policy.isConstructorExposed(C1.class.getConstructor())); // Doesn't exist in C2
+ assertTrue(c3Policy.isConstructorExposed(C3.class.getConstructor()));
+
+ assertFalse(c1Policy.isMethodExposed(C2.class.getMethod("m4"))); // Doesn't exist in C1
+ assertFalse(c2Policy.isMethodExposed(C2.class.getMethod("m4")));
+ assertTrue(c3Policy.isMethodExposed(C3.class.getMethod("m4")));
+
+ assertFalse(c1Policy.isFieldExposed(C2.class.getField("f3"))); // Doesn't exist in C1
+ assertFalse(c2Policy.isFieldExposed(C2.class.getField("f3")));
+ assertTrue(c3Policy.isFieldExposed(C3.class.getField("f3")));
+ }
+
+ @Test
+ public void testInterfaces() throws NoSuchMethodException, NoSuchFieldException {
+ {
+ WhitelistMemberAccessPolicy policy = newWhitelistMemberAccessPolicy(
+ I1.class.getName() + ".m1()",
+ I1.class.getName() + ".f1"
+ );
+ ClassMemberAccessPolicy d1Policy = policy.forClass(D1.class);
+ ClassMemberAccessPolicy d2Policy = policy.forClass(D2.class);
+ ClassMemberAccessPolicy e1Policy = policy.forClass(E1.class);
+ ClassMemberAccessPolicy e2Policy = policy.forClass(E2.class);
+ assertTrue(d1Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertTrue(d2Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertTrue(e1Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertTrue(e2Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertTrue(d1Policy.isFieldExposed(I1.class.getField("f1")));
+ assertTrue(d2Policy.isFieldExposed(I1.class.getField("f1")));
+ assertTrue(e1Policy.isFieldExposed(I1.class.getField("f1")));
+ assertTrue(e2Policy.isFieldExposed(I1.class.getField("f1")));
+ }
+ {
+ WhitelistMemberAccessPolicy policy = newWhitelistMemberAccessPolicy(
+ I1Sub.class.getName() + ".m1()",
+ I1Sub.class.getName() + ".m2()",
+ I1Sub.class.getName() + ".f1"
+ );
+ ClassMemberAccessPolicy d1Policy = policy.forClass(D1.class);
+ ClassMemberAccessPolicy d2Policy = policy.forClass(D2.class);
+ ClassMemberAccessPolicy e1Policy = policy.forClass(E1.class);
+ ClassMemberAccessPolicy e2Policy = policy.forClass(E2.class);
+ assertFalse(d1Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertFalse(d2Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertTrue(e1Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertTrue(e2Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertFalse(d1Policy.isMethodExposed(I1Sub.class.getMethod("m2")));
+ assertFalse(d2Policy.isMethodExposed(I1Sub.class.getMethod("m2")));
+ assertTrue(e1Policy.isMethodExposed(I1Sub.class.getMethod("m2")));
+ assertTrue(e2Policy.isMethodExposed(I1Sub.class.getMethod("m2")));
+ assertFalse(d1Policy.isFieldExposed(I1.class.getField("f1")));
+ assertFalse(d2Policy.isFieldExposed(I1.class.getField("f1")));
+ assertTrue(e1Policy.isFieldExposed(I1.class.getField("f1")));
+ assertTrue(e2Policy.isFieldExposed(I1.class.getField("f1")));
+ }
+ {
+ WhitelistMemberAccessPolicy policy = newWhitelistMemberAccessPolicy(
+ I1.class.getName() + ".m1()",
+ I1.class.getName() + ".f1"
+ );
+ ClassMemberAccessPolicy d1Policy = policy.forClass(D1.class);
+ ClassMemberAccessPolicy d2Policy = policy.forClass(D2.class);
+ ClassMemberAccessPolicy e1Policy = policy.forClass(E1.class);
+ ClassMemberAccessPolicy e2Policy = policy.forClass(E2.class);
+ assertTrue(d1Policy.isMethodExposed(I1Sub.class.getMethod("m1")));
+ assertTrue(d2Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertTrue(e1Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertTrue(e2Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertFalse(d1Policy.isMethodExposed(I1Sub.class.getMethod("m2")));
+ assertFalse(d2Policy.isMethodExposed(I1Sub.class.getMethod("m2")));
+ assertFalse(e1Policy.isMethodExposed(I1Sub.class.getMethod("m2")));
+ assertFalse(e2Policy.isMethodExposed(I1Sub.class.getMethod("m2")));
+ assertTrue(d1Policy.isFieldExposed(I1Sub.class.getField("f1")));
+ assertTrue(d2Policy.isFieldExposed(I1.class.getField("f1")));
+ assertTrue(e1Policy.isFieldExposed(I1.class.getField("f1")));
+ assertTrue(e2Policy.isFieldExposed(I1.class.getField("f1")));
+ }
+ {
+ WhitelistMemberAccessPolicy policy = newWhitelistMemberAccessPolicy(
+ D2.class.getName() + ".m1()",
+ D2.class.getName() + ".f1"
+ );
+ ClassMemberAccessPolicy d1Policy = policy.forClass(D1.class);
+ ClassMemberAccessPolicy d2Policy = policy.forClass(D2.class);
+ assertFalse(d1Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertTrue(d2Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertFalse(d1Policy.isFieldExposed(I1.class.getField("f1")));
+ assertTrue(d2Policy.isFieldExposed(I1.class.getField("f1")));
+ }
+ {
+ WhitelistMemberAccessPolicy policy = newWhitelistMemberAccessPolicy(
+ I1Sub.class.getName() + ".m1()",
+ D2.class.getName() + ".m1()",
+ I1Sub.class.getName() + ".m2()",
+ J1.class.getName() + ".m2()",
+ I1.class.getName() + ".f1",
+ I1Sub.class.getName() + ".f1"
+ );
+ ClassMemberAccessPolicy d1Policy = policy.forClass(D1.class);
+ ClassMemberAccessPolicy d2Policy = policy.forClass(D2.class);
+ ClassMemberAccessPolicy e1Policy = policy.forClass(E1.class);
+ ClassMemberAccessPolicy e2Policy = policy.forClass(E2.class);
+ ClassMemberAccessPolicy f1Policy = policy.forClass(F1.class);
+ assertFalse(d1Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertTrue(d2Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertTrue(e1Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertTrue(e2Policy.isMethodExposed(I1.class.getMethod("m1")));
+ assertFalse(d1Policy.isMethodExposed(J1.class.getMethod("m2")));
+ assertFalse(d2Policy.isMethodExposed(J1.class.getMethod("m2")));
+ assertTrue(e1Policy.isMethodExposed(J1.class.getMethod("m2")));
+ assertTrue(e2Policy.isMethodExposed(J1.class.getMethod("m2")));
+ assertTrue(f1Policy.isMethodExposed(J1.class.getMethod("m2")));
+ assertTrue(d1Policy.isFieldExposed(I1.class.getField("f1")));
+ assertTrue(d2Policy.isFieldExposed(I1.class.getField("f1")));
+ assertTrue(e1Policy.isFieldExposed(I1.class.getField("f1")));
+ assertTrue(e2Policy.isFieldExposed(I1.class.getField("f1")));
+ }
+ }
+
+ @Test
+ public void testArrayArgs() throws NoSuchMethodException {
+ {
+ WhitelistMemberAccessPolicy policy = newWhitelistMemberAccessPolicy(
+ CArrayArgs.class.getName() + ".m1(java.lang.String)",
+ CArrayArgs.class.getName() + ".m1(java.lang.String[])",
+ CArrayArgs.class.getName() + ".m1(java.lang.String[][])",
+ CArrayArgs.class.getName() + ".m2(" + C1.class.getName() + "[])",
+ CArrayArgs.class.getName() + ".m2("
+ + C1.class.getName() + "[], "
+ + C1.class.getName() + "[], "
+ + C1.class.getName() + ")"
+ );
+ ClassMemberAccessPolicy classPolicy = policy.forClass(CArrayArgs.class);
+ assertTrue(classPolicy.isMethodExposed(CArrayArgs.class.getMethod("m1", String.class)));
+ assertTrue(classPolicy.isMethodExposed(CArrayArgs.class.getMethod("m1", String[].class)));
+ assertTrue(classPolicy.isMethodExposed(CArrayArgs.class.getMethod("m1", String[][].class)));
+ assertTrue(classPolicy.isMethodExposed(CArrayArgs.class.getMethod("m2", C1[].class)));
+ assertTrue(classPolicy.isMethodExposed(
+ CArrayArgs.class.getMethod("m2", C1[].class, C1[].class, C1.class)));
+ }
+ {
+ WhitelistMemberAccessPolicy policy = newWhitelistMemberAccessPolicy(
+ CArrayArgs.class.getName() + ".m1(java.lang.String)",
+ CArrayArgs.class.getName() + ".m1(java.lang.String[][])"
+ );
+ ClassMemberAccessPolicy classPolicy = policy.forClass(CArrayArgs.class);
+ assertTrue(classPolicy.isMethodExposed(CArrayArgs.class.getMethod("m1", String.class)));
+ assertFalse(classPolicy.isMethodExposed(CArrayArgs.class.getMethod("m1", String[].class)));
+ assertTrue(classPolicy.isMethodExposed(CArrayArgs.class.getMethod("m1", String[][].class)));
+ assertFalse(classPolicy.isMethodExposed(CArrayArgs.class.getMethod("m2", C1[].class)));
+ assertFalse(classPolicy.isMethodExposed(
+ CArrayArgs.class.getMethod("m2", C1[].class, C1[].class, C1.class)));
+ }
+ }
+
+ @Test
+ public void memberSelectorParserIgnoresWhitespace() throws NoSuchMethodException {
+ WhitelistMemberAccessPolicy policy = newWhitelistMemberAccessPolicy(
+ (CArrayArgs.class.getName() + ".m1(java.lang.String)").replace(".", "\n\t. "),
+ CArrayArgs.class.getName() + ".m2("
+ + C1.class.getName() + " [ ]\t,"
+ + C1.class.getName() + "[] ,\n "
+ + C1.class.getName() + " )"
+ );
+ ClassMemberAccessPolicy classPolicy = policy.forClass(CArrayArgs.class);
+ assertTrue(classPolicy.isMethodExposed(CArrayArgs.class.getMethod("m1", String.class)));
+ assertTrue(classPolicy.isMethodExposed(
+ CArrayArgs.class.getMethod("m2", C1[].class, C1[].class, C1.class)));
+ }
+
+ @Test
+ public void memberSelectorParsingErrorsTest() {
+ try {
+ newWhitelistMemberAccessPolicy("foo()");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), containsString("missing dot"));
+ }
+ try {
+ newWhitelistMemberAccessPolicy("com.example.Foo-bar.m()");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), containsString("malformed upper bound class name"));
+ }
+ try {
+ newWhitelistMemberAccessPolicy("java.util.Date.m-x()");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), containsString("malformed member name"));
+ }
+ try {
+ newWhitelistMemberAccessPolicy("java.util.Date.to string()");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), containsString("malformed member name"));
+ }
+ try {
+ newWhitelistMemberAccessPolicy("java.util.Date.toString(");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), containsString("missing closing ')'"));
+ }
+ try {
+ newWhitelistMemberAccessPolicy("java.util.Date.m(com.x-y)");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), containsString("malformed argument class name"));
+ }
+ try {
+ newWhitelistMemberAccessPolicy("java.util.Date.m(int[)");
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage(), containsString("malformed argument class name"));
+ }
+ }
+
+ @Test
+ public void testAnnotation() throws NoSuchFieldException, NoSuchMethodException {
+ WhitelistMemberAccessPolicy policy = newWhitelistMemberAccessPolicy(
+ CAnnotationsTest2.class.getName() + ".f2",
+ CAnnotationsTest2.class.getName() + ".f3",
+ CAnnotationsTest2.class.getName() + ".m2()",
+ CAnnotationsTest2.class.getName() + ".m3()",
+ CAnnotationsTest2.class.getName() + "." + CAnnotationsTest2.class.getSimpleName() + "(int)",
+ CAnnotationsTest2.class.getName() + "." + CAnnotationsTest2.class.getSimpleName() + "(int, int)"
+ );
+ ClassMemberAccessPolicy classPolicy = policy.forClass(CAnnotationsTest2.class);
+
+ assertFalse(classPolicy.isFieldExposed(CAnnotationsTest2.class.getField("f1")));
+ assertTrue(classPolicy.isFieldExposed(CAnnotationsTest2.class.getField("f2")));
+ assertTrue(classPolicy.isFieldExposed(CAnnotationsTest2.class.getField("f3")));
+ assertTrue(classPolicy.isFieldExposed(CAnnotationsTest2.class.getField("f4")));
+ assertTrue(classPolicy.isFieldExposed(CAnnotationsTest2.class.getField("f5")));
+ assertTrue(classPolicy.isFieldExposed(CAnnotationsTest2.class.getField("f6")));
+
+ assertFalse(classPolicy.isMethodExposed(CAnnotationsTest2.class.getMethod("m1")));
+ assertTrue(classPolicy.isMethodExposed(CAnnotationsTest2.class.getMethod("m2")));
+ assertTrue(classPolicy.isMethodExposed(CAnnotationsTest2.class.getMethod("m3")));
+ assertTrue(classPolicy.isMethodExposed(CAnnotationsTest2.class.getMethod("m4")));
+ assertTrue(classPolicy.isMethodExposed(CAnnotationsTest2.class.getMethod("m5")));
+ assertTrue(classPolicy.isMethodExposed(CAnnotationsTest2.class.getMethod("m6")));
+
+ assertTrue(classPolicy.isConstructorExposed(
+ CAnnotationsTest2.class.getConstructor()));
+ assertTrue(classPolicy.isConstructorExposed(
+ CAnnotationsTest2.class.getConstructor(int.class)));
+ assertTrue(classPolicy.isConstructorExposed(
+ CAnnotationsTest2.class.getConstructor(int.class, int.class)));
+ assertTrue(classPolicy.isConstructorExposed(
+ CAnnotationsTest2.class.getConstructor(int.class, int.class, int.class)));
+ assertFalse(classPolicy.isConstructorExposed(
+ CAnnotationsTest2.class.getConstructor(int.class, int.class, int.class, int.class)));
+ }
+
+ public static final MemberAccessPolicy CONFIG_TEST_MEMBER_ACCESS_POLICY =
+ newWhitelistMemberAccessPolicy(
+ C1.class.getName() + ".m1()",
+ C1.class.getName() + ".m3()");
+
+ @Test
+ public void stringBasedConfigurationTest() throws TemplateException {
+ Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);
+ cfg.setSetting(
+ "objectWrapper",
+ "DefaultObjectWrapper(2.3.30, " +
+ "memberAccessPolicy="
+ + WhitelistMemberAccessPolicyTest.class.getName() + ".CONFIG_TEST_MEMBER_ACCESS_POLICY"
+ + ")");
+ TemplateHashModel m = (TemplateHashModel) cfg.getObjectWrapper().wrap(new C1());
+ assertNotNull(m.get("m1"));
+ assertNull(m.get("m2"));
+ assertNotNull(m.get("m3"));
+ }
+
+ private static WhitelistMemberAccessPolicy newWhitelistMemberAccessPolicy(String... memberSelectors) {
+ return new WhitelistMemberAccessPolicy(
+ WhitelistMemberAccessPolicy.MemberSelector.parse(
+ Arrays.asList(memberSelectors),
+ WhitelistMemberAccessPolicyTest.class.getClassLoader()));
+ }
+
+ public static class C1 {
+ public int f1;
+ public int f2;
+
+ public C1() {
+ }
+
+ public C1(int x) {
+ }
+
+ public void m1() {
+ }
+
+ public void m2() {
+ }
+
+ public void m2(int x) {
+ }
+
+ public void m2(double x) {
+ }
+
+ public void m3() {
+ }
+ }
+
+ public static class C2 extends C1 {
+ public int f3;
+
+ public C2(int x) {
+ super(x);
+ }
+
+ public void m2(boolean x) {
+ }
+
+ public void m4() {
+ }
+ }
+
+ public static class C3 extends C2 {
+ public C3() {
+ super(0);
+ }
+
+ public C3(int x) {
+ super(x);
+ }
+ }
+
+ public static class D1 implements I1 {
+ public int f1;
+ public void m1() {
+ }
+ }
+
+ public static class D2 extends D1 {
+ }
+
+ public static class E1 implements I1Sub {
+ public void m1() {
+
+ }
+
+ public void m2() {
+ }
+ }
+
+ public static class E2 extends E1 implements J1 {
+ }
+
+ public static class F1 implements J1 {
+ public void m2() {
+ }
+ }
+
+ interface I1 {
+ int f1 = 1;
+ void m1();
+ }
+
+ interface I1Sub extends Serializable, I1 {
+ void m2();
+ }
+
+ interface J1 {
+ void m2();
+ }
+
+ public class CArrayArgs {
+ public void m1(String arg) {
+ }
+
+ public void m1(String[] arg) {
+ }
+
+ public void m1(String[][] arg) {
+ }
+
+ public void m2(C1[] arg) {
+ }
+
+ public void m2(C1[] arg1, C1[] arg2, C1 arg3) {
+ }
+ }
+
+ public static class CAnnotationsTest1 {
+ @TemplateAccessible
+ public int f5;
+
+ @TemplateAccessible
+ public CAnnotationsTest1() {}
+
+ @TemplateAccessible
+ public void m5() {}
+ }
+
+ public interface IAnnotationTest {
+ @TemplateAccessible
+ int f6 = 0;
+
+ @TemplateAccessible
+ void m6();
+ }
+
+ public static class CAnnotationsTest2 extends CAnnotationsTest1 implements IAnnotationTest {
+ public int f1;
+
+ public int f2;
+
+ @TemplateAccessible
+ public int f3;
+
+ @TemplateAccessible
+ public int f4;
+
+ public int f5;
+
+ public int f6;
+
+ public CAnnotationsTest2() {}
+
+ public CAnnotationsTest2(int x) {}
+
+ @TemplateAccessible
+ public CAnnotationsTest2(int x, int y) {}
+
+ @TemplateAccessible
+ public CAnnotationsTest2(int x, int y, int z) {}
+
+ public CAnnotationsTest2(int x, int y, int z, int a) {}
+
+ public void m1() {}
+
+ public void m2() {}
+
+ @TemplateAccessible
+ public void m3() {}
+
+ @TemplateAccessible
+ public void m4() {}
+
+ public void m5() {}
+
+ public void m6() {}
+ }
+
+}
diff --git a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
index 86d14bb..12f4954 100644
--- a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
+++ b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
@@ -27,6 +27,7 @@ import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
@@ -56,6 +57,7 @@ import com.google.common.collect.ImmutableMap;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.EnumerationModel;
import freemarker.ext.beans.HashAdapter;
+import freemarker.ext.beans.WhitelistMemberAccessPolicy;
import freemarker.ext.util.WrapperTemplateModel;
public class DefaultObjectWrapperTest {
@@ -316,6 +318,30 @@ public class DefaultObjectWrapperTest {
assertTrue(bw.wrap(new PureIterable()) instanceof DefaultIterableAdapter);
}
+
+ {
+ DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.getVersion());
+
+ DefaultObjectWrapper bwDefault = builder.build();
+ assertSame(bwDefault, builder.build());
+
+ WhitelistMemberAccessPolicy memberAccessPolicy =
+ new WhitelistMemberAccessPolicy(
+ WhitelistMemberAccessPolicy.MemberSelector.parse(
+ Arrays.asList(SomeBean.class.getName() + ".getX()"),
+ DefaultObjectWrapperTest.class.getClassLoader()));
+ builder.setMemberAccessPolicy(memberAccessPolicy);
+ DefaultObjectWrapper bw = builder.build();
+ assertNotSame(bw, bwDefault);
+ assertSame(bw, builder.build());
+ assertSame(bw.getMemberAccessPolicy(), memberAccessPolicy);
+
+ TemplateHashModel m = (TemplateHashModel) bw.wrap(new SomeBean());
+ assertNotNull(m.get("x"));
+ assertNotNull(m.get("getX"));
+ assertNull(m.get("y"));
+ assertNull(m.get("getY"));
+ }
}
@Test
@@ -1191,5 +1217,14 @@ public class DefaultObjectWrapperTest {
}
};
+
+ public static class SomeBean {
+ public int getX() {
+ return 1;
+ }
+ public int getY() {
+ return 1;
+ }
+ }
}
[freemarker] 03/03: Deprecated FreemarkerTag (a custom JSP tag that
probably nobody uses)
Posted by dd...@apache.org.
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 889d411d547e99e9a1c59f3b357ddd55f49b9527
Author: ddekany <dd...@apache.org>
AuthorDate: Wed Jan 1 20:06:41 2020 +0100
Deprecated FreemarkerTag (a custom JSP tag that probably nobody uses)
---
src/main/java/freemarker/ext/jsp/FreemarkerTag.java | 4 ++++
src/main/java/freemarker/ext/jsp/JspContextModel.java | 4 ++++
2 files changed, 8 insertions(+)
diff --git a/src/main/java/freemarker/ext/jsp/FreemarkerTag.java b/src/main/java/freemarker/ext/jsp/FreemarkerTag.java
index 837ce3f..a4de5e4 100644
--- a/src/main/java/freemarker/ext/jsp/FreemarkerTag.java
+++ b/src/main/java/freemarker/ext/jsp/FreemarkerTag.java
@@ -28,6 +28,7 @@ import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTag;
import javax.servlet.jsp.tagext.Tag;
+import freemarker.template.Configuration;
import freemarker.template.SimpleHash;
import freemarker.template.Template;
@@ -35,7 +36,10 @@ import freemarker.template.Template;
* Simple implementation of JSP tag to allow use of FreeMarker templates in
* JSP. Inspired by similar class in Velocity template engine developed by
* <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ *
+ * @Deprecated This feature is not supported anymore, also, it uses the deprecated default {@link Configuration}.
*/
+@Deprecated
public class FreemarkerTag implements BodyTag {
private Tag parent;
private BodyContent bodyContent;
diff --git a/src/main/java/freemarker/ext/jsp/JspContextModel.java b/src/main/java/freemarker/ext/jsp/JspContextModel.java
index 3c28468..bd824f0 100644
--- a/src/main/java/freemarker/ext/jsp/JspContextModel.java
+++ b/src/main/java/freemarker/ext/jsp/JspContextModel.java
@@ -26,6 +26,10 @@ import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
+/**
+ * @Deprecated This is used by the deprecated {@link FreemarkerTag}.
+ */
+@Deprecated
class JspContextModel
implements
TemplateHashModel {