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 2017/09/27 23:17:45 UTC

[2/2] incubator-freemarker git commit: Improved/fixed TemplateTransformModel behavior (this is a legacy interface that's not used much in user code):

Improved/fixed TemplateTransformModel behavior (this is a legacy interface that's not used much in user code):

- Writer TemplateTransformModel.getWriter(Writer out, Map args) can now return the out parameter as is, as FreeMarker now recognizes that it's the same object and so won't call close() on it after the end tag.
- When incomplatible_improvements is set to 2.3.27 (or higher), and the returned Writer implements TransformControl,  exceptions that are used internally for flow control (for <#return>, <#break>, etc.) won't be passed to TransformControl.onError(Throwable) anymore. Earlier, if onError didn't rethrow the exception (though almost all implementation does), you couldn't use said directives inside the transformed block.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/68630294
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/68630294
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/68630294

Branch: refs/heads/2.3-gae
Commit: 68630294429825f452139ba7be41d34b62d4f2ab
Parents: 560247a
Author: ddekany <dd...@apache.org>
Authored: Thu Sep 28 01:17:34 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Thu Sep 28 01:17:34 2017 +0200

----------------------------------------------------------------------
 src/main/java/freemarker/core/Environment.java  |   9 +-
 .../java/freemarker/template/Configuration.java |  10 ++
 .../template/TemplateTransformModel.java        |  66 +++++----
 src/manual/en_US/book.xml                       |  33 +++++
 .../core/TemplateTransformModelTest.java        | 148 +++++++++++++++++++
 5 files changed, 233 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/68630294/src/main/java/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index 5faacdd..a371a6a 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -492,7 +492,10 @@ public final class Environment extends Configurable {
                 }
             } catch (Throwable t) {
                 try {
-                    if (tc != null) {
+                    if (tc != null
+                            && !(t instanceof FlowControlException
+                                    && getConfiguration().getIncompatibleImprovements().intValue()
+                                    >= _TemplateAPI.VERSION_INT_2_3_27)) {
                         tc.onError(t);
                     } else {
                         throw t;
@@ -515,7 +518,9 @@ public final class Environment extends Configurable {
                 }
             } finally {
                 out = prevOut;
-                tw.close();
+                if (prevOut != tw) {
+                    tw.close();
+                }
             }
         } catch (TemplateException te) {
             handleTemplateException(te);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/68630294/src/main/java/freemarker/template/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index 6e24881..95d4172 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -22,6 +22,7 @@ package freemarker.template;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.Writer;
 import java.lang.reflect.InvocationTargetException;
 import java.net.URLConnection;
 import java.text.DecimalFormat;
@@ -850,6 +851,15 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      *          {@link #setWrapUncheckedExceptions(boolean) wrap_unchecked_exceptions} to {@code true} (see more there),
      *          but this is more backward compatible, as it avoids wrapping unchecked exceptions that the calling
      *          application is likely to catch specifically (like application-specific unchecked exceptions).
+     *       <li><p>
+     *          When the {@link Writer} returned by {@link TemplateTransformModel#getWriter(Writer, Map)} implements
+     *          {@link TransformControl}, exceptions that are used internally by FreeMarker for flow control (for
+     *          {@code <#return>}, {@code <#break>}, etc.) won't be passed to
+     *          {@link TransformControl#onError(Throwable)} anymore. Earlier, if {@code onError} didn't rethrow the
+     *          exception (though almost all implementation does), you couldn't use said directives inside the
+     *          transformed block. It's very unlikely that user code is affected by this, partially because these aren't
+     *          commonly implemented interfaces (especially not {@link TransformControl}), and because it's unlikely
+     *          that templates utilize the the bug that's not fixed.
      *     </ul>
      *   </li>
      * </ul>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/68630294/src/main/java/freemarker/template/TemplateTransformModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/TemplateTransformModel.java b/src/main/java/freemarker/template/TemplateTransformModel.java
index 154bd57..432329f 100644
--- a/src/main/java/freemarker/template/TemplateTransformModel.java
+++ b/src/main/java/freemarker/template/TemplateTransformModel.java
@@ -19,6 +19,7 @@
 
 package freemarker.template;
 
+import java.io.FilterWriter;
 import java.io.IOException;
 import java.io.Writer;
 import java.util.Map;
@@ -28,39 +29,42 @@ import freemarker.template.utility.DeepUnwrap;
 /**
  * "transform" template language data type: user-defined directives 
  * (much like macros) specialized on filtering output; you should rather use the newer {@link TemplateDirectiveModel}
- * instead. This certainly will be deprecated in FreeMarker 2.4.
+ * instead. This interface will certainly be deprecated in FreeMarker 2.4.
  */
 public interface TemplateTransformModel extends TemplateModel {
 
-     /**
-      * Returns a writer that will be used by the engine to feed the
-      * transformation input to the transform. Each call to this method
-      * must return a new instance of the writer so that the transformation
-      * is thread-safe.
-      * 
-      * @param out the character stream to which to write the transformed output
-      * @param args the arguments (if any) passed to the transformation as a 
-      * map of key/value pairs where the keys are strings and the arguments are
-      * TemplateModel instances. This is never null. If you need to convert the
-      * template models to POJOs, you can use the utility methods in the 
-      * {@link DeepUnwrap} class.
-      * 
-      * @return a writer to which the engine will feed the transformation 
-      * input, or null if the transform does not support nested content (body).
-      * The returned writer can implement the {@link TransformControl}
-      * interface if it needs advanced control over the evaluation of the 
-      * transformation body.
-      * 
-      * <p>This method should not throw {@link RuntimeException}, nor {@link IOException} that wasn't caused by writing
-      * to the output. Such exceptions should be catched inside the method and wrapped inside a
-      * {@link TemplateModelException}. (Note that setting {@link Configuration#setWrapUncheckedExceptions(boolean)} to
-      * {@code true} can mitigate the negative effects of implementations that throw {@link RuntimeException}-s.)
-      * 
-      * @throws TemplateModelException If any problem occurs that's not an {@link IOException} during writing the
-      *          template output.
-      * @throws IOException When writing the template output fails. Other {@link IOException}-s should be catched in
-      *          this method and wrapped into {@link TemplateModelException}.   
-      *  
-      */
+    /**
+     * Returns a writer that will be used by the engine to feed the transformation input to the transform. Each call to
+     * this method must return a new instance of the writer so that the transformation is thread-safe.
+     * <p>
+     * This method should not throw {@link RuntimeException}, nor {@link IOException} that wasn't caused by writing to
+     * the output. Such exceptions should be catched inside the method and wrapped inside a
+     * {@link TemplateModelException}. (Note that setting {@link Configuration#setWrapUncheckedExceptions(boolean)} to
+     * {@code true} can mitigate the negative effects of implementations that throw {@link RuntimeException}-s.)
+     * 
+     * @param out
+     *            the character stream to which to write the transformed output
+     * 
+     * @param args
+     *            the arguments (if any) passed to the transformation as a map of key/value pairs where the keys are
+     *            strings and the arguments are {@link TemplateModel} instances. This is never {@code null}. (If you
+     *            need to convert the template models to POJOs, you can use the utility methods in the
+     *            {@link DeepUnwrap} class. Though it's recommended to work with {@link TemplateModel}-s directly.)
+     * 
+     * @return The {@link Writer} to which the engine will write the content to transform, or {@code null} if the
+     *         transform does not support nested content (body). The returned {@link Writer} may implements the
+     *         {@link TransformControl} interface if it needs advanced control over the evaluation of the nested
+     *         content. FreeMarker will call {@link Writer#close()} after the transform end-tag. {@link Writer#close()}
+     *         must not close the {@link Writer} received as the {@code out} parameter (so if you are using a
+     *         {@link FilterWriter}, you must override {@link FilterWriter#close()}, as by default that closes the
+     *         wrapped {@link Writer}). Since 2.3.27 its also allowed to return the {@code out} writer as is, in which
+     *         case it won't be closed.
+     * 
+     * @throws TemplateModelException
+     *             If any problem occurs that's not an {@link IOException} during writing the template output.
+     * @throws IOException
+     *             When writing to {@code out} (the parameter) fails. Other {@link IOException}-s should be catched in
+     *             this method and wrapped into {@link TemplateModelException}.
+     */
      Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException;
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/68630294/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index d9d66af..7ba5852 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -27283,6 +27283,39 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
               <para>Added
               <literal>Constants.EMPTY_KEY_VALUE_PAIR_ITERATOR</literal></para>
             </listitem>
+
+            <listitem>
+              <para>Improved/fixed <literal>TemplateTransformModel</literal>
+              behavior (this is a legacy interface that's not used much in
+              user code):</para>
+
+              <itemizedlist>
+                <listitem>
+                  <para><literal>Writer
+                  TemplateTransformModel.getWriter(Writer out, Map
+                  args)</literal> can now return the <literal>out</literal>
+                  parameter as is, as FreeMarker now recognizes that it's the
+                  same object and so won't call <literal>close()</literal> on
+                  it after the end tag.</para>
+                </listitem>
+
+                <listitem>
+                  <para>When <link
+                  linkend="pgui_config_incompatible_improvements_how_to_set"><literal>incomplatible_improvements</literal></link>
+                  is set to 2.3.27 (or higher), and the returned
+                  <literal>Writer</literal> implements
+                  <literal>TransformControl</literal>, exceptions that are
+                  used internally for flow control (for
+                  <literal>&lt;#return&gt;</literal>,
+                  <literal>&lt;#break&gt;</literal>, etc.) won't be passed to
+                  <literal>TransformControl.onError(Throwable)</literal>
+                  anymore. Earlier, if <literal>onError</literal> didn't
+                  rethrow the exception (though almost all implementation
+                  does), you couldn't use said directives inside the
+                  transformed block.</para>
+                </listitem>
+              </itemizedlist>
+            </listitem>
           </itemizedlist>
         </section>
       </section>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/68630294/src/test/java/freemarker/core/TemplateTransformModelTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/TemplateTransformModelTest.java b/src/test/java/freemarker/core/TemplateTransformModelTest.java
new file mode 100644
index 0000000..d16d7ad
--- /dev/null
+++ b/src/test/java/freemarker/core/TemplateTransformModelTest.java
@@ -0,0 +1,148 @@
+package freemarker.core;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+
+import org.junit.Test;
+
+import freemarker.template.Configuration;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateExceptionHandler;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateTransformModel;
+import freemarker.template.TransformControl;
+import freemarker.test.TemplateTest;
+
+public class TemplateTransformModelTest extends TemplateTest {
+    
+    @Test(expected=IOException.class)
+    public void testFailsWithWrongClosing() throws IOException, TemplateException {
+        addToDataModel("t", WrongTransform.INSTANCE);
+        assertOutput("a<@t...@t>c", "abc");
+    }
+    
+    // Works since 2.3.27
+    @Test
+    public void testEnclosingWriterUser() throws IOException, TemplateException {
+        addToDataModel("t", EnclosingWriterUserTransform.INSTANCE);
+        assertOutput("a<@t...@t>c", "abc");
+    }
+
+    @Test
+    public void testCloseCalled() throws IOException, TemplateException {
+        addToDataModel("t", UpperCaseInParenthesesTransform.INSTANCE);
+        assertOutput("a<@t...@t>c", "a(B)c");
+        assertOutput("<#list 1..2 as _>a<@t...@t>d</#list>.", "a(B)a(B).");
+    }
+    
+    @Test
+    public void testExceptionHandler() throws IOException, TemplateException {
+        Configuration cfg = getConfiguration();
+        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+        
+        addToDataModel("t", ExceptionHandlerTransform.INSTANCE);
+        assertOutput("1<@t...@t>3", "1(2)C3");
+        assertOutput("1<@t...@t>3", "1(2EC3");
+        
+        cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_27);
+        assertOutput("<#list 1..1 as _>1<@t...@t></#list>3", "1(2C3");
+        cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_26);
+        assertOutput("<#list 1..1 as _>1<@t...@t></#list>3", "1(2EC3");
+    }
+    
+    public static final class UpperCaseInParenthesesTransform implements TemplateTransformModel {
+
+        public static final UpperCaseInParenthesesTransform INSTANCE = new UpperCaseInParenthesesTransform();
+        
+        private UpperCaseInParenthesesTransform() {
+            //
+        }
+        
+        public Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException {
+            out.write('(');
+            return new FilterWriter(out) {
+
+                @Override
+                public void write(int c) throws IOException {
+                    out.write(Character.toUpperCase(c));
+                }
+
+                @Override
+                public void write(char[] cbuf, int off, int len) throws IOException {
+                    for (int i = 0; i < cbuf.length; i++) {
+                        cbuf[i] = Character.toUpperCase(cbuf[i]);
+                    }
+                    super.write(cbuf, off, len);
+                }
+
+                @Override
+                public void close() throws IOException {
+                    out.write(')');
+                }
+            };
+        }
+        
+    }    
+
+    public static final class EnclosingWriterUserTransform implements TemplateTransformModel {
+
+        private static final EnclosingWriterUserTransform INSTANCE = new EnclosingWriterUserTransform();
+        
+        private EnclosingWriterUserTransform() {
+            //
+        }
+        
+        public Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException {
+            return out;
+        }
+    }
+    
+    public static final class WrongTransform implements TemplateTransformModel {
+        
+        private static final WrongTransform INSTANCE = new WrongTransform(); 
+
+        public Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException {
+            return new FilterWriter(out) { };  // Deliberately forgot to override close()
+        }
+        
+    }
+    
+    public static final class ExceptionHandlerTransform implements TemplateTransformModel {
+
+        private static final ExceptionHandlerTransform INSTANCE = new ExceptionHandlerTransform(); 
+        
+        public Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException {
+            return new ExceptoinHandlerTransformWriter(out);
+        }
+        
+        class ExceptoinHandlerTransformWriter extends FilterWriter implements TransformControl {
+
+            protected ExceptoinHandlerTransformWriter(Writer out) throws IOException {
+                super(out);
+            }
+            
+            @Override
+            public void close() throws IOException {
+                out.write('C');
+            }
+
+            public int onStart() throws TemplateModelException, IOException {
+                out.write('(');
+                return TransformControl.EVALUATE_BODY;
+            }
+
+            public int afterBody() throws TemplateModelException, IOException {
+                out.write(')');
+                return TransformControl.END_EVALUATION;
+            }
+
+            public void onError(Throwable t) throws Throwable {
+                out.write("E");
+            }
+            
+        }
+        
+    }
+}