freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [freemarker] 02/04: FREEMARKER-120: BeansWrapper (and it's subclasses like DefaultObjectWrapper) now has two protected methods that can be overridden to monitor the accessing of members: invokeMethod and readField.
Date Sun, 12 Jan 2020 09:50:50 GMT
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 5112b2a37c8ab500d740ba4597dfb6450e42e34d
Author: ddekany <ddekany@apache.org>
AuthorDate: Sun Jan 12 09:57:41 2020 +0100

    FREEMARKER-120: BeansWrapper (and it's subclasses like DefaultObjectWrapper) now has two
protected methods that can be overridden to monitor the accessing of members: invokeMethod
and readField.
---
 src/main/java/freemarker/ext/beans/BeanModel.java  |   2 +-
 .../java/freemarker/ext/beans/BeansWrapper.java    |  48 ++++++--
 .../java/freemarker/ext/beans/StaticModel.java     |   4 +-
 src/manual/en_US/book.xml                          |  10 ++
 .../ext/beans/MemberAccessMonitoringTest.java      | 128 +++++++++++++++++++++
 5 files changed, 182 insertions(+), 10 deletions(-)

diff --git a/src/main/java/freemarker/ext/beans/BeanModel.java b/src/main/java/freemarker/ext/beans/BeanModel.java
index 6c68016..a6c5e4a 100644
--- a/src/main/java/freemarker/ext/beans/BeanModel.java
+++ b/src/main/java/freemarker/ext/beans/BeanModel.java
@@ -232,7 +232,7 @@ implements TemplateHashModelEx, AdapterTemplateModel, WrapperTemplateModel,
Temp
                 // cachedModel remains null, as we don't cache these
             }
         } else if (desc instanceof Field) {
-            resultModel = wrapper.wrap(((Field) desc).get(object));
+            resultModel = wrapper.readField(object, (Field) desc);
             // cachedModel remains null, as we don't cache these
         } else if (desc instanceof Method) {
             Method method = (Method) desc;
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapper.java b/src/main/java/freemarker/ext/beans/BeansWrapper.java
index d3fb070..8190715 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapper.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapper.java
@@ -24,6 +24,7 @@ import java.beans.PropertyDescriptor;
 import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.math.BigDecimal;
@@ -1500,12 +1501,23 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable
{
             }
         }
     }
-    
+
     /**
-     * Invokes the specified method, wrapping the return value. The specialty
-     * of this method is that if the return value is null, and the return type
-     * of the invoked method is void, {@link TemplateModel#NOTHING} is returned.
-     * @param object the object to invoke the method on
+     * Invokes the specified method, wrapping the return value. All method invocations done
in templates should go
+     * through this (assuming the target object was wrapped with this {@link ObjectWrapper}).
+     *
+     * <p>This method is protected since 2.3.30; before that it was package private.
The intended application of
+     * overriding this is monitoring what calls are made from templates. That can be useful
to asses what will be needed
+     * in a {@link WhitelistMemberAccessPolicy} for example. Note that {@link Object#toString}
calls caused by type
+     * conversion (like when you have <code>${myObject}</code>) will not go through
here, as they aren't called by the
+     * template directly (and aren't called via reflection). On the other hand, <code>${myObject[key]}</code>,
+     * if {@code myObject} is not a {@link Map}, will go through here as a {@code get(String|Object)}
method call, if
+     * there's a such method.
+     *
+     * <p>If the return value is null, and the return type of the invoked method is
void,
+     * {@link TemplateModel#NOTHING} is returned.
+     *
+     * @param object the object to invoke the method on ({@code null} may be null for static
methods)
      * @param method the method to invoke 
      * @param args the arguments to the method
      * @return the wrapped return value of the method.
@@ -1516,9 +1528,13 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable
{
      * (this can happen if the wrapper has an outer identity or is subclassed,
      * and the outer identity or the subclass throws an exception. Plain
      * BeansWrapper never throws TemplateModelException).
+     *
+     * @see #readField(Object, Field)
+     *
+     * @since 2.3.30
      */
-    TemplateModel invokeMethod(Object object, Method method, Object[] args)
-    throws InvocationTargetException,
+    protected TemplateModel invokeMethod(Object object, Method method, Object[] args)
+            throws InvocationTargetException,
         IllegalAccessException,
         TemplateModelException {
         // [2.4]: Java's Method.invoke truncates numbers if the target type has not enough
bits to hold the value.
@@ -1530,6 +1546,24 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable
{
             : getOuterIdentity().wrap(retval); 
     }
 
+    /**
+     * Reads the specified field, returns its value as {@link TemplateModel}.  All field
reading done in templates
+     * should go through this (assuming the target object was wrapped with this {@link ObjectWrapper}).
+     *
+     * <p>Just like in the case of {@link #invokeMethod(Object, Method, Object[])},
overriding this can be useful if you
+     * want to monitor what members are accessed by templates. However, it has the caveat
that final field values are
+     * possibly cached, so you won't see all reads. Furthermore, at least static models pre-read
final fields, so
+     * they will be read even if the templates don't read them.
+     *
+     * @see #invokeMethod(Object, Method, Object[])
+     *
+     * @since 2.3.30
+     */
+    protected TemplateModel readField(Object object, Field field)
+            throws IllegalAccessException, TemplateModelException {
+        return getOuterIdentity().wrap(field.get(object));
+    }
+
    /**
      * Returns a hash model that represents the so-called class static models.
      * Every class static model is itself a hash through which you can call
diff --git a/src/main/java/freemarker/ext/beans/StaticModel.java b/src/main/java/freemarker/ext/beans/StaticModel.java
index 99e9329..fc7504d 100644
--- a/src/main/java/freemarker/ext/beans/StaticModel.java
+++ b/src/main/java/freemarker/ext/beans/StaticModel.java
@@ -65,7 +65,7 @@ final class StaticModel implements TemplateHashModelEx {
         // Non-final field; this must be evaluated on each call.
         if (model instanceof Field) {
             try {
-                return wrapper.getOuterIdentity().wrap(((Field) model).get(null));
+                return wrapper.readField(null, (Field) model);
             } catch (IllegalAccessException e) {
                 throw new TemplateModelException(
                     "Illegal access for field " + key + " of class " + clazz.getName());
@@ -114,7 +114,7 @@ final class StaticModel implements TemplateHashModelEx {
                     try {
                         // public static final fields are evaluated once and
                         // stored in the map
-                        map.put(field.getName(), wrapper.getOuterIdentity().wrap(field.get(null)));
+                        map.put(field.getName(), wrapper.readField(null, field));
                     } catch (IllegalAccessException e) {
                         // Intentionally ignored
                     }
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 433ac9a..4d3ef5a 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -29259,6 +29259,16 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para><link
+              xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-120">FREEMARKER-120</link>:
+              <literal>BeansWrapper</literal> (and it's subclasses like
+              <literal>DefaultObjectWrapper</literal>) now has two protected
+              methods that can be overridden to monitor the accessing of
+              members: <literal>invokeMethod</literal> and
+              <literal>readField</literal>.</para>
+            </listitem>
+
+            <listitem>
               <para>Added
               <literal>Environment.getDataModelOrSharedVariable(String)</literal>.</para>
             </listitem>
diff --git a/src/test/java/freemarker/ext/beans/MemberAccessMonitoringTest.java b/src/test/java/freemarker/ext/beans/MemberAccessMonitoringTest.java
new file mode 100644
index 0000000..b53d033
--- /dev/null
+++ b/src/test/java/freemarker/ext/beans/MemberAccessMonitoringTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.DefaultObjectWrapperBuilder;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.test.TemplateTest;
+
+public class MemberAccessMonitoringTest extends TemplateTest {
+
+    private final MonitoredDefaultObjectWrapper ow = new MonitoredDefaultObjectWrapper();
+
+    @Override
+    protected Configuration createConfiguration() throws Exception {
+        Configuration configuration = super.createConfiguration();
+        configuration.setObjectWrapper(ow);
+        return configuration;
+    }
+
+    @Test
+    public void test() throws TemplateException, IOException {
+        addToDataModel("C1", ow.getStaticModels().get(C1.class.getName()));
+        addToDataModel("c2", new C2());
+
+        assertOutput(
+                "${C1.m1()} ${C1.F1} ${C1.F2} ${c2.m1()} ${c2.f1} ${c2.f2} ${c2['abc']}",
+                "1 11 111 2 22 222 3");
+        assertEquals(
+                ImmutableSet.of("C1.m1()", "C1.F1", "C1.F2", "C2.m1()", "C2.f1", "C2.f2",
"C2.get()"),
+                ow.getAccessedMembers());
+    }
+
+    public static class C1 {
+        public static final int F1 = 11;
+        public static int F2 = 111;
+
+        public static int m1() {
+            return 1;
+        }
+
+        public static int get(String k) {
+            return k.length();
+        }
+    }
+
+    public static class C2 {
+        public final int f1 = 22;
+        public int f2 = 222;
+
+        public int m1() {
+            return 2;
+        }
+
+        public int get(String k) {
+            return k.length();
+        }
+    }
+
+    public static class MonitoredDefaultObjectWrapper extends DefaultObjectWrapper {
+        private final Set<String> accessedMembers;
+
+        public MonitoredDefaultObjectWrapper() {
+            super(getBuilder(), true);
+            this.accessedMembers = Collections.synchronizedSet(new HashSet<String>());
+        }
+
+        private static DefaultObjectWrapperBuilder getBuilder() {
+            DefaultObjectWrapperBuilder builder =
+                    new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30);
+            builder.setExposeFields(true);
+            return builder;
+        }
+
+        @Override
+        protected TemplateModel invokeMethod(Object object, Method method, Object[] args)
throws
+                InvocationTargetException, IllegalAccessException, TemplateModelException
{
+            accessedMembers.add(method.getDeclaringClass().getSimpleName() + "." + method.getName()
+ "()");
+            return super.invokeMethod(object, method, args);
+        }
+
+        @Override
+        protected TemplateModel readField(Object object, Field field) throws IllegalAccessException,
+                TemplateModelException {
+            accessedMembers.add(field.getDeclaringClass().getSimpleName() + "." + field.getName());
+            return super.readField(object, field);
+        }
+
+        public Set<String> getAccessedMembers() {
+            return accessedMembers;
+        }
+    }
+
+}


Mime
View raw message