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 2015/11/29 12:59:05 UTC

[01/25] incubator-freemarker git commit: FREEMARKER-1 adding OverrideResponseContentType init param in FreemarketServlet

Repository: incubator-freemarker
Updated Branches:
  refs/heads/2.3 0286f2367 -> 5758b0a51


FREEMARKER-1 adding OverrideResponseContentType init param in FreemarketServlet


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

Branch: refs/heads/2.3
Commit: e89dbfce5f2d2274653062dfd6911a62c354c39b
Parents: 0eccad5
Author: Woonsan Ko <wo...@apache.org>
Authored: Mon Oct 12 22:31:16 2015 +0900
Committer: Woonsan Ko <wo...@apache.org>
Committed: Mon Oct 12 22:31:16 2015 +0900

----------------------------------------------------------------------
 .classpath                                      |   2 +
 ivy.xml                                         |  10 +-
 .../ext/servlet/FreemarkerServlet.java          |  37 ++++--
 .../ext/servlet/FreemarkerServletTest.java      | 124 +++++++++++++++++++
 .../resources/freemarker/ext/servlet/foo.ftl    |  19 +++
 5 files changed, 181 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e89dbfce/.classpath
----------------------------------------------------------------------
diff --git a/.classpath b/.classpath
index ad83438..0716ff4 100644
--- a/.classpath
+++ b/.classpath
@@ -54,5 +54,7 @@
 	<classpathentry kind="lib" path="ide-dependencies/jcl-over-slf4j-1.6.1.jar"/>
 	<classpathentry kind="lib" path="ide-dependencies/junit-4.12.jar"/>
 	<classpathentry kind="lib" path="ide-dependencies/hamcrest-library-1.3.jar"/>
+	<classpathentry kind="lib" path="ide-dependencies/spring-core-2.5.6.SEC03.jar"/>
+	<classpathentry kind="lib" path="ide-dependencies/spring-test-2.5.6.SEC03.jar"/>
 	<classpathentry kind="output" path=".bin/"/>
 </classpath>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e89dbfce/ivy.xml
----------------------------------------------------------------------
diff --git a/ivy.xml b/ivy.xml
index f81c3c3..bf21fb6 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -4,6 +4,7 @@
 <!DOCTYPE ivy-module [
     <!ENTITY jetty.version "7.6.16.v20140903">
     <!ENTITY slf4j.version "1.6.1">
+    <!ENTITY spring.version "2.5.6.SEC03">
 ]>
 <ivy-module version="2.0">
   <info organisation="org.freemarker" module="freemarker">
@@ -141,7 +142,14 @@
       <exclude org="org.slf4j" name="jcl104-over-slf4j" />
       <exclude org="log4j" name="log4j" />
     </dependency>
-    
+
+    <dependency org="org.springframework" name="spring-core" rev="&spring.version;" conf="test->default">
+      <exclude org="commons-logging" name="commons-logging" />
+    </dependency>
+    <dependency org="org.springframework" name="spring-test" rev="&spring.version;" conf="test->default">
+      <exclude org="commons-logging" name="commons-logging" />
+    </dependency>
+
     <!-- docs -->
     
     <dependency org="org.freemarker" name="docgen" rev="2.0-branch-head" conf="manual->default" changing="true" />

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e89dbfce/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index 83eb14c..776a884 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -140,7 +140,12 @@ import freemarker.template.utility.StringUtil;
  * actual template file will be used (in the response HTTP header and for encoding the output stream). Note that this
  * setting can be overridden on a per-template basis by specifying a custom attribute named <tt>content_type</tt> in the
  * <tt>attributes</tt> parameter of the <tt>&lt;#ftl&gt;</tt> directive.</li>
- * 
+ *
+ * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> (since 2.4.0): If set to true, overrides the ContentType
+ * of the response by using either <strong>{@value #INIT_PARAM_CONTENT_TYPE}</strong> parameter setting or other
+ * information (see the description of <strong>{@value #INIT_PARAM_CONTENT_TYPE}</strong> for detail). The default
+ * value is <tt>false</tt>.</li>
+ *
  * <li><strong>{@value #INIT_PARAM_BUFFER_SIZE}</strong>: Sets the size of the output buffer in bytes, or if "KB" or
  * "MB" is written after the number (like {@code <param-value>256 KB</param-value>}) then in kilobytes or megabytes.
  * This corresponds to {@link HttpServletResponse#setBufferSize(int)}. If the {@link HttpServletResponse} state doesn't
@@ -273,6 +278,13 @@ public class FreemarkerServlet extends HttpServlet {
 
     /**
      * Init-param name - see the {@link FreemarkerServlet} class documentation about the init-params.
+     *
+     * @since 2.3.24
+     */
+    public static final String INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE = "OverrideResponseContentType";
+
+    /**
+     * Init-param name - see the {@link FreemarkerServlet} class documentation about the init-params.
      * 
      * @since 2.3.22
      */
@@ -314,7 +326,7 @@ public class FreemarkerServlet extends HttpServlet {
     private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_IGNORE = "ignore";
     private static final String DEPR_INITPARAM_DEBUG = "debug";
     
-    private static final String DEFAULT_CONTENT_TYPE = "text/html";
+    static final String DEFAULT_CONTENT_TYPE = "text/html";
 
     /**
      * When set, the items defined in it will be added after those coming from the
@@ -412,6 +424,7 @@ public class FreemarkerServlet extends HttpServlet {
     @SuppressFBWarnings(value="SE_BAD_FIELD", justification="Not investing into making this Servlet serializable")
     private ObjectWrapper wrapper;
     private String contentType;
+    private boolean overrideResponseContentType = true;
     private boolean noCharsetInContentType;
     private List/*<MetaInfTldSource>*/ metaInfTldSources;
     private List/*<String>*/ classpathTlds;
@@ -557,6 +570,8 @@ public class FreemarkerServlet extends HttpServlet {
                     debug = StringUtil.getYesNo(value);
                 } else if (name.equals(INIT_PARAM_CONTENT_TYPE)) {
                     contentType = value;
+                } else if (name.equals(INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE)) {
+                    overrideResponseContentType = StringUtil.getYesNo(value);
                 } else if (name.equals(INIT_PARAM_EXCEPTION_ON_MISSING_TEMPLATE)) {
                     exceptionOnMissingTemplate = StringUtil.getYesNo(value);
                 } else if (name.equals(INIT_PARAM_META_INF_TLD_LOCATIONS)) {;
@@ -708,15 +723,17 @@ public class FreemarkerServlet extends HttpServlet {
             throw newServletExceptionWithFreeMarkerLogging(
                     "Unexpected error when loading template " + StringUtil.jQuoteNoXSS(templatePath) + ".", e);
         }
-        
-        Object attrContentType = template.getCustomAttribute("content_type");
-        if (attrContentType != null) {
-            response.setContentType(attrContentType.toString());
-        } else {
-            if (noCharsetInContentType) {
-                response.setContentType(contentType + "; charset=" + template.getEncoding());
+
+        if (overrideResponseContentType) {
+            Object attrContentType = template.getCustomAttribute("content_type");
+            if (attrContentType != null) {
+                response.setContentType(attrContentType.toString());
             } else {
-                response.setContentType(contentType);
+                if (noCharsetInContentType) {
+                    response.setContentType(contentType + "; charset=" + template.getEncoding());
+                } else {
+                    response.setContentType(contentType);
+                }
             }
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e89dbfce/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
new file mode 100644
index 0000000..9690638
--- /dev/null
+++ b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.servlet;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockServletConfig;
+import org.springframework.mock.web.MockServletContext;
+
+import freemarker.log.Logger;
+
+public class FreemarkerServletTest {
+
+    private static final Logger LOG = Logger.getLogger("freemarker.servlet");
+
+    private static final String TEST_TEMPLATE_PATH = "classpath:freemarker/ext/servlet";
+
+    private FreemarkerServlet freemarkerServlet;
+
+    private MockServletContext servletContext;
+    private MockServletConfig servletConfig;
+
+    @Before
+    public void setUp() throws ServletException, IOException {
+        servletContext = new MockServletContext();
+        servletContext.setContextPath("/");
+
+        servletConfig = new MockServletConfig(servletContext);
+        servletConfig.addInitParameter(FreemarkerServlet.INIT_PARAM_TEMPLATE_PATH, TEST_TEMPLATE_PATH);
+
+        freemarkerServlet = new FreemarkerServlet();
+        freemarkerServlet.init(servletConfig);
+    }
+
+    @After
+    public void tearDown() {
+        freemarkerServlet.destroy();
+    }
+
+    @Test
+    public void testContentTypeInitParams_withNoResponseContentType_ByDefault() throws ServletException, IOException {
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        assertNull(response.getContentType());
+
+        freemarkerServlet.doGet(request, response);
+        LOG.debug("response content: " + response.getContentAsString());
+
+        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+        assertTrue(response.getContentType().contains(FreemarkerServlet.DEFAULT_CONTENT_TYPE));
+    }
+
+    @Test
+    public void testContentTypeInitParams_withResponseContentType_ByDefault() throws ServletException, IOException {
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        response.setContentType("application/json");
+        assertEquals("application/json", response.getContentType());
+
+        freemarkerServlet.doGet(request, response);
+        LOG.debug("response content: " + response.getContentAsString());
+
+        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+        assertTrue(response.getContentType().contains(FreemarkerServlet.DEFAULT_CONTENT_TYPE));
+    }
+
+    @Test
+    public void testContentTypeInitParams_withResponseContentType_NoOverriding() throws ServletException, IOException {
+        servletConfig.addInitParameter(FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false");
+        freemarkerServlet = new FreemarkerServlet();
+        freemarkerServlet.init(servletConfig);
+
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        response.setContentType("application/json");
+        assertEquals("application/json", response.getContentType());
+
+        freemarkerServlet.doGet(request, response);
+        LOG.debug("response content: " + response.getContentAsString());
+
+        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+        assertEquals("application/json", response.getContentType());
+
+        freemarkerServlet.destroy();
+    }
+
+    private MockHttpServletRequest createMockHttpServletRequest(final ServletContext servletContext,
+            final String pathInfo) {
+        MockHttpServletRequest servletRequest = new MockHttpServletRequest(servletContext);
+        servletRequest.setServerName("localhost");
+        servletRequest.setServerPort(8080);
+        servletRequest.setContextPath("");
+        servletRequest.setRequestURI(pathInfo);
+        servletRequest.setPathInfo(pathInfo);
+        return servletRequest;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e89dbfce/src/test/resources/freemarker/ext/servlet/foo.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/servlet/foo.ftl b/src/test/resources/freemarker/ext/servlet/foo.ftl
new file mode 100644
index 0000000..a3aac20
--- /dev/null
+++ b/src/test/resources/freemarker/ext/servlet/foo.ftl
@@ -0,0 +1,19 @@
+<#--
+  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.
+-->
+foo
\ No newline at end of file


[16/25] incubator-freemarker git commit: Added OverrideResponseLocale to version hisotry, and some JavaDoc/Manual polish related to the new init-params.

Posted by dd...@apache.org.
Added OverrideResponseLocale to version hisotry, and some JavaDoc/Manual polish related to the new init-params.


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

Branch: refs/heads/2.3
Commit: 0b6f0d2357e241ad5f01babf016cdffdbe4f21e3
Parents: 6819382
Author: ddekany <dd...@apache.org>
Authored: Sun Nov 1 11:27:07 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sun Nov 1 11:27:36 2015 +0100

----------------------------------------------------------------------
 .../ext/servlet/FreemarkerServlet.java          | 23 +++---
 src/manual/book.xml                             | 78 +++++++++++++++-----
 .../ext/servlet/FreemarkerServletTest.java      | 62 +++++++++-------
 3 files changed, 104 insertions(+), 59 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0b6f0d23/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index 7b785d2..464be23 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -143,8 +143,9 @@ import freemarker.template.utility.StringUtil;
  * <li>If the {@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE} init-param is {@value #INIT_PARAM_VALUE_NEVER} (the
  * default is {@value #INIT_PARAM_VALUE_ALWAYS}), then the value of {@link HttpServletResponse#getContentType()} is used
  * if that's non-{@code null}.
- * <li>The template's custom attribute name <tt>content_type</tt> in the <tt>attributes</tt> parameter of the
- * <tt>&lt;#ftl&gt;</tt> directive. This is a legacy feature, deprecated by the {@link OutputFormat} mechanism.
+ * <li>The template's custom attribute name <tt>content_type</tt>, usually specified via the <tt>attributes</tt>
+ * parameter of the <tt>&lt;#ftl&gt;</tt> directive. This is a legacy feature, deprecated by the {@link OutputFormat}
+ * mechanism.
  * <li>The {@linkplain Template#getOutputFormat() output format of the template}, if that has non-{@code null} MIME-type
  * ({@link OutputFormat#getMimeType()}). When a template has no output format specified, {@link UndefinedOutputFormat}
  * is used, which has {@code null} MIME-type. (The output format of a template is deduced from {@link Configuration}
@@ -157,7 +158,7 @@ import freemarker.template.utility.StringUtil;
  * </ol>
  * If none of the above gives a MIME type, then this init-param does. Defaults to <tt>"text/html"</tt>. The value may
  * include the charset (e.g. <tt>"text/html; charset=utf-8"</tt>). If the charset is not specified in this init-param,
- * then the charset (encoding) of the actual template file will appended after it, which, as per the Servlet
+ * then the charset (encoding) of the actual template file will be appended after it, which, as per the Servlet
  * specification, also sets the actual encoding used to write the response body.</li>
  *
  * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> (since 2.3.24): Specifies when we should
@@ -171,10 +172,10 @@ import freemarker.template.utility.StringUtil;
  * {@code null}. Setting this init-param allows you to specify the content type before forwarding to
  * {@link FreemarkerServlet}.</li>
  *
- * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_LOCALE}</strong> (since 2.3.24): Specifies when we should
- * override the template {@code locale} that might be already set (i.e., non-{@code null}) in the {@link HttpServletRequest}.
- * The default is {@value #INIT_PARAM_VALUE_ALWAYS}, which means that we always deduce the template {@code locale}
- * by invoking {@link #deduceLocale(String, HttpServletRequest, HttpServletResponse)}. Another possible value is
+ * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_LOCALE}</strong> (since 2.3.24): Specifies if we should override
+ * the template {@code locale} that might be already set (i.e., non-{@code null}) in the {@link HttpServletRequest}. The
+ * default is {@value #INIT_PARAM_VALUE_ALWAYS}, which means that we always deduce the template {@code locale} by
+ * invoking {@link #deduceLocale(String, HttpServletRequest, HttpServletResponse)}. Another possible value is
  * {@value #INIT_PARAM_VALUE_NEVER}, which means that we don't deduce the template {@code locale}, unless
  * {@link HttpServletRequest#getLocale()} is {@code null}.
  *
@@ -761,7 +762,6 @@ public class FreemarkerServlet extends HttpServlet {
         }
 
         Locale locale = request.getLocale();
-
         if (locale == null || overrideResponseLocale != OverrideResponseLocale.NEVER) {
             locale = deduceLocale(templatePath, request, response);
         }
@@ -906,9 +906,10 @@ public class FreemarkerServlet extends HttpServlet {
     }
     
     /**
-     * Returns the locale used for the {@link Configuration#getTemplate(String, Locale)} call. The base implementation
-     * simply returns the locale setting of the configuration. Override this method to provide different behaviour, i.e.
-     * to use the locale indicated in the request.
+     * Returns the locale used for the {@link Configuration#getTemplate(String, Locale)} call (as far as the
+     * {@value #INIT_PARAM_OVERRIDE_RESPONSE_LOCALE} Servlet init-param allows that). The base implementation in
+     * {@link FreemarkerServlet} simply returns the {@code locale} setting of the configuration. Override this method to
+     * provide different behavior, for example, to use the locale indicated in the HTTP request.
      * 
      * @param templatePath
      *            The template path (template name) as it will be passed to {@link Configuration#getTemplate(String)}.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0b6f0d23/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index cf47f7a..cab5f2c 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -26038,16 +26038,35 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
-              <para>New <literal>FreemarkerServlet</literal> init-param:
-              <literal>OverrideResponseContentType</literal>. Specifies when
-              should we override the <literal>contentType</literal> that's
-              already set (i.e., non-<literal>null</literal>) in the
-              <literal>HttpServletResponse</literal>. Earlier, we have always
-              set it, and that's still the default behavior. But now that this
-              init-param exists, you can change that behavior, so that the
-              <literal>contentType</literal> you have specified before
-              forwarding to <literal>FreemarkerServlet</literal>
-              matters.</para>
+              <para>New <literal>FreemarkerServlet</literal> init-params (see
+              <link
+              xlink:href="http://freemarker.org/docs/api/freemarker/ext/servlet/FreemarkerServlet.html">the
+              <literal>FreemarkerSerlvet</literal> API documentation</link>
+              for details):</para>
+
+              <itemizedlist>
+                <listitem>
+                  <para><literal>OverrideResponseContentType</literal>.
+                  Specifies when should we override the
+                  <literal>contentType</literal> that's already set (i.e.,
+                  non-<literal>null</literal>) in the
+                  <literal>HttpServletResponse</literal>. Earlier, we have
+                  always set it, and that's still the default behavior. But
+                  now that this init-param exists, you can change that
+                  behavior, so that the <literal>contentType</literal> you
+                  have specified before forwarding to
+                  <literal>FreemarkerServlet</literal> matters.</para>
+                </listitem>
+
+                <listitem>
+                  <para><literal>OverrideResponseLocale</literal>. Specifies
+                  if should we override the <literal>contentType</literal>
+                  that's already set (i.e., non-<literal>null</literal>) in
+                  the <literal>HttpServletResponse</literal>. Earlier, we have
+                  always set it, but now this behavior can be changed so that
+                  we only set it if it wasn't already set.</para>
+                </listitem>
+              </itemizedlist>
             </listitem>
 
             <listitem>
@@ -26375,16 +26394,35 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
-              <para>New <literal>FreemarkerServlet</literal> init-param:
-              <literal>OverrideResponseContentType</literal>. Specifies when
-              should we override the <literal>contentType</literal> that's
-              already set (i.e., non-<literal>null</literal>) in the
-              <literal>HttpServletResponse</literal>. Earlier, we have always
-              set it, and that's still the default behavior. But now that this
-              init-param exists, you can change that behavior, so that the
-              <literal>contentType</literal> you have specified before
-              forwarding to <literal>FreemarkerServlet</literal>
-              matters.</para>
+              <para>New <literal>FreemarkerServlet</literal> init-params (see
+              <link
+              xlink:href="http://freemarker.org/docs/api/freemarker/ext/servlet/FreemarkerServlet.html">the
+              <literal>FreemarkerSerlvet</literal> API documentation</link>
+              for details):</para>
+
+              <itemizedlist>
+                <listitem>
+                  <para><literal>OverrideResponseContentType</literal>.
+                  Specifies when should we override the
+                  <literal>contentType</literal> that's already set (i.e.,
+                  non-<literal>null</literal>) in the
+                  <literal>HttpServletResponse</literal>. Earlier, we have
+                  always set it, and that's still the default behavior. But
+                  now that this init-param exists, you can change that
+                  behavior, so that the <literal>contentType</literal> you
+                  have specified before forwarding to
+                  <literal>FreemarkerServlet</literal> matters.</para>
+                </listitem>
+
+                <listitem>
+                  <para><literal>OverrideResponseLocale</literal>. Specifies
+                  if should we override the <literal>contentType</literal>
+                  that's already set (i.e., non-<literal>null</literal>) in
+                  the <literal>HttpServletResponse</literal>. Earlier, we have
+                  always set it, but now this behavior can be changed so that
+                  we only set it if it wasn't already set.</para>
+                </listitem>
+              </itemizedlist>
             </listitem>
 
             <listitem>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0b6f0d23/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
index f18bbcb..fc81de9 100644
--- a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
+++ b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
@@ -135,34 +135,40 @@ public class FreemarkerServletTest {
 
     @Test
     public void testResponseLocaleInitParams() throws Exception {
-        // By default, the Configurable.locale is set to Locale.getDefault().
-        final Locale defaultLocale = Locale.getDefault();
-
-        assertTemplateLocaleEquals(
-                defaultLocale, // <- expected template locale
-                null, // <- request locale
-                null, // <- init-param
-                "foo.ftl");
-        assertTemplateLocaleEquals(
-                defaultLocale, // <- expected template locale
-                Locale.FRENCH, // <- request locale
-                null, // <- init-param
-                "foo.ftl");
-        assertTemplateLocaleEquals(
-                defaultLocale, // <- expected template locale
-                Locale.FRENCH, // <- request locale
-                INIT_PARAM_VALUE_ALWAYS, // <- init-param
-                "foo.ftl");
-        assertTemplateLocaleEquals(
-                defaultLocale, // <- expected template locale
-                null, // <- request locale
-                INIT_PARAM_VALUE_NEVER, // <- init-param
-                "foo.ftl");
-        assertTemplateLocaleEquals(
-                Locale.FRENCH, // <- expected template locale
-                Locale.FRENCH, // <- request locale
-                INIT_PARAM_VALUE_NEVER, // <- init-param
-                "foo.ftl");
+        Locale prevDefaultLocale = Locale.getDefault();
+        Locale.setDefault(Locale.US);
+        try {
+            // By default, the Configurable.locale is set to Locale.getDefault().
+            final Locale defaultLocale = Locale.getDefault();
+    
+            assertTemplateLocaleEquals(
+                    defaultLocale, // <- expected template locale
+                    null, // <- request locale
+                    null, // <- init-param
+                    "foo.ftl");
+            assertTemplateLocaleEquals(
+                    defaultLocale, // <- expected template locale
+                    Locale.FRENCH, // <- request locale
+                    null, // <- init-param
+                    "foo.ftl");
+            assertTemplateLocaleEquals(
+                    defaultLocale, // <- expected template locale
+                    Locale.FRENCH, // <- request locale
+                    INIT_PARAM_VALUE_ALWAYS, // <- init-param
+                    "foo.ftl");
+            assertTemplateLocaleEquals(
+                    defaultLocale, // <- expected template locale
+                    null, // <- request locale
+                    INIT_PARAM_VALUE_NEVER, // <- init-param
+                    "foo.ftl");
+            assertTemplateLocaleEquals(
+                    Locale.FRENCH, // <- expected template locale
+                    Locale.FRENCH, // <- request locale
+                    INIT_PARAM_VALUE_NEVER, // <- init-param
+                    "foo.ftl");
+        } finally {
+            Locale.setDefault(prevDefaultLocale);
+        }
     }
 
     private void assertResponseContentTypeEquals(


[08/25] incubator-freemarker git commit: Continued FREEMARKER-1 "Option to not to overwrite response ContentType in FreemarkerServlet": 3 possible values for OverrideResponseContentType init-param. Early version of the ResponseCharacterEncoding init-para

Posted by dd...@apache.org.
Continued FREEMARKER-1 "Option to not to overwrite response ContentType in FreemarkerServlet": 3 possible values for OverrideResponseContentType init-param. Early version of the ResponseCharacterEncoding init-param. More concise tests.


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

Branch: refs/heads/2.3
Commit: fca65a24f481d657bf8676485dbdf842c2bb562e
Parents: 2531f51
Author: ddekany <dd...@apache.org>
Authored: Thu Oct 22 01:55:18 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Thu Oct 22 02:33:22 2015 +0200

----------------------------------------------------------------------
 .../ext/servlet/FreemarkerServlet.java          | 187 +++++++++---
 src/manual/book.xml                             |  19 +-
 .../ext/servlet/FreemarkerServletTest.java      | 296 +++++++------------
 3 files changed, 270 insertions(+), 232 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fca65a24/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index b4aa296..c344bb4 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -38,8 +38,6 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import com.sun.org.apache.xml.internal.serialize.OutputFormat;
-
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import freemarker.cache.ClassTemplateLoader;
 import freemarker.cache.FileTemplateLoader;
@@ -47,6 +45,7 @@ import freemarker.cache.MultiTemplateLoader;
 import freemarker.cache.TemplateLoader;
 import freemarker.cache.WebappTemplateLoader;
 import freemarker.core.Configurable;
+import freemarker.core.OutputFormat;
 import freemarker.core.UndefinedOutputFormat;
 import freemarker.ext.jsp.TaglibFactory;
 import freemarker.ext.jsp.TaglibFactory.ClasspathMetaInfTldSource;
@@ -141,27 +140,34 @@ import freemarker.template.utility.StringUtil;
  * when nothing else specifies the MIME type. The things that may specify the MIME type (and hence this init-param is
  * ignored), starting with the highest precedence, are:
  * <ol>
+ * <li>If the {@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE} init-param is {@value #INIT_PARAM_VALUE_NEVER} (it
+ * itn't be default), then the value of {@link HttpServletResponse#getContentType()} is if that's non-{@code null}.
  * <li>The template's custom attribute name <tt>content_type</tt> in the <tt>attributes</tt> parameter of the
  * <tt>&lt;#ftl&gt;</tt> directive. This is a legacy feature, deprecated by the {@link OutputFormat} mechanism.
  * <li>The {@linkplain Template#getOutputFormat() output format of the template}, if that has non-{@code null} MIME-type
- * ({@link OutputFormat#getMediaType()}). When a template has no output format specified, {@link UndefinedOutputFormat}
+ * ({@link OutputFormat#getMimeType()}). When a template has no output format specified, {@link UndefinedOutputFormat}
  * is used, which has {@code null} MIME-type. (The output format of a template is deduced from {@link Configuration}
  * settings, or can be specified directly in the template, like {@code <#ftl outputFormat="HTML">}. See the FreeMarker
- * Manual for more about the output format mechanism. Note that setting an output format may turns on auto-escaping,
- * so it's not just about MIME types.)
- * <li>If the {@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE} init-param is {@code false} (the default is
- * {@code true}), then the value of {@link HttpServletResponse#getContentType()} is used if that's non-{@code null}.
+ * Manual for more about the output format mechanism. Note that setting an output format may turns on auto-escaping, so
+ * it's not just about MIME types.)
+ * <li>If the {@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE} init-param is not {@value #INIT_PARAM_VALUE_ALWAYS}
+ * (the default is {@value #INIT_PARAM_VALUE_ALWAYS}), then the value of {@link HttpServletResponse#getContentType()} is
+ * used if that's non-{@code null}.
  * </ol>
- * If none of the above gives a MIME type, then this init-param does. Defaults to <tt>"text/html"</tt>. The
- * value may include the charset (e.g. <tt>"text/html; charset=utf-8"</tt>). If the charset is not specified in this
- * init-param, then the charset (encoding) of the actual template file will appended after it, which, as per the
- * Servlet specification, also sets the actual encoding used to write the response body.</li>
+ * If none of the above gives a MIME type, then this init-param does. Defaults to <tt>"text/html"</tt>. The value may
+ * include the charset (e.g. <tt>"text/html; charset=utf-8"</tt>). If the charset is not specified in this init-param,
+ * then the charset (encoding) of the actual template file will appended after it, which, as per the Servlet
+ * specification, also sets the actual encoding used to write the response body.</li>
  *
- * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> (since 2.3.24): Specifies if we should
- * always set the {@code contentType} in the {@link HttpServletResponse} to the value of the
- * {@value #INIT_PARAM_CONTENT_TYPE} init-param (or to its default, {@code text/html}), or only if it wasn't already set
- * (i.e., {@link HttpServletResponse#getContentType()} returns {@code null}). The default is {@code true}. Setting this
- * to {@code false} allows you to specify the content type before forwarding to {@link FreemarkerServlet}.</li>
+ * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> (since 2.3.24): Specifies when should we
+ * override the {@code contentType} that's already set (i.e., non-{@code null}) in the {@link HttpServletResponse}. The
+ * default is {@value #INIT_PARAM_VALUE_ALWAYS}, which means that we always set the content type. Another possible value
+ * is {@value #INIT_PARAM_VALUE_NEVER}, which means that we don't set the content type in the response, unless
+ * {@link HttpServletResponse#getContentType()} is {@code null}. The third possible value is
+ * {@value #INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE}, which means that we only set the content type if either the
+ * template has an associated {@link OutputFormat} with non-{@code null} {@link OutputFormat#getMimeType()}, or it has a
+ * custom attribute with name <tt>content_type</tt>, or {@link HttpServletResponse#getContentType()} is {@code null}.
+ * Setting this init-param allows you to specify the content type before forwarding to {@link FreemarkerServlet}.</li>
  *
  * <li><strong>{@value #INIT_PARAM_BUFFER_SIZE}</strong>: Sets the size of the output buffer in bytes, or if "KB" or
  * "MB" is written after the number (like {@code <param-value>256 KB</param-value>}) then in kilobytes or megabytes.
@@ -300,6 +306,13 @@ public class FreemarkerServlet extends HttpServlet {
 
     /**
      * Init-param name - see the {@link FreemarkerServlet} class documentation about the init-params.
+     *
+     * @since 2.3.24
+     */
+    public static final String INIT_PARAM_RESPONSE_CHARACTER_ENCODING = "ResponseCharacterEncoding";
+    
+    /**
+     * Init-param name - see the {@link FreemarkerServlet} class documentation about the init-params.
      * 
      * @since 2.3.22
      */
@@ -342,6 +355,13 @@ public class FreemarkerServlet extends HttpServlet {
     private static final String DEPR_INITPARAM_DEBUG = "debug";
     
     static final String DEFAULT_CONTENT_TYPE = "text/html";
+    
+    public static final String INIT_PARAM_VALUE_NEVER = "never";
+    public static final String INIT_PARAM_VALUE_ALWAYS = "always";
+    public static final String INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE = "whenTemplateHasMimeType";
+    public static final String INIT_PARAM_VALUE_FROM_TEMPLATE = "fromTemplate";
+    public static final String INIT_PARAM_VALUE_LEGACY = "legacy";
+    public static final String INIT_PARAM_VALUE_DO_NOT_SET = "doNotSet";
 
     /**
      * When set, the items defined in it will be added after those coming from the
@@ -439,8 +459,9 @@ public class FreemarkerServlet extends HttpServlet {
     @SuppressFBWarnings(value="SE_BAD_FIELD", justification="Not investing into making this Servlet serializable")
     private ObjectWrapper wrapper;
     private String contentType;
-    private boolean overrideResponseContentType = true;
-    private boolean noCharsetInContentType;
+    private OverrideResponseContentType overrideResponseContentType = OverrideResponseContentType.ALWAYS;
+    private ResponseCharacterEncoding responseCharacterEncoding = ResponseCharacterEncoding.LEGACY;
+    private boolean contentTypeContainsCharset;
     private List/*<MetaInfTldSource>*/ metaInfTldSources;
     private List/*<String>*/ classpathTlds;
 
@@ -586,7 +607,9 @@ public class FreemarkerServlet extends HttpServlet {
                 } else if (name.equals(INIT_PARAM_CONTENT_TYPE)) {
                     contentType = value;
                 } else if (name.equals(INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE)) {
-                    overrideResponseContentType = StringUtil.getYesNo(value);
+                    overrideResponseContentType = initParamValueToEnum(value, OverrideResponseContentType.values());
+                } else if (name.equals(INIT_PARAM_RESPONSE_CHARACTER_ENCODING)) {
+                    responseCharacterEncoding = initParamValueToEnum(value, ResponseCharacterEncoding.values());
                 } else if (name.equals(INIT_PARAM_EXCEPTION_ON_MISSING_TEMPLATE)) {
                     exceptionOnMissingTemplate = StringUtil.getYesNo(value);
                 } else if (name.equals(INIT_PARAM_META_INF_TLD_LOCATIONS)) {;
@@ -608,20 +631,24 @@ public class FreemarkerServlet extends HttpServlet {
             }
         } // while initpnames
         
-        noCharsetInContentType = true;
-        int i = contentType.toLowerCase().indexOf("charset=");
-        if (i != -1) {
+        contentTypeContainsCharset = contentTypeContainsCharset(contentType);
+    }
+    
+    private boolean contentTypeContainsCharset(String contentType) {
+        int charsetIdx = contentType.toLowerCase().indexOf("charset=");
+        if (charsetIdx != -1) {
             char c = 0;
-            i--;
-            while (i >= 0) {
-                c = contentType.charAt(i);
+            charsetIdx--;
+            while (charsetIdx >= 0) {
+                c = contentType.charAt(charsetIdx);
                 if (!Character.isWhitespace(c)) break;
-                i--;
+                charsetIdx--;
             }
-            if (i == -1 || c == ';') {
-                noCharsetInContentType = false;
+            if (charsetIdx == -1 || c == ';') {
+                return true;
             }
         }
+        return false;
     }
 
     private List/*<MetaInfTldSource>*/ parseAsMetaInfTldLocations(String value) throws ParseException {
@@ -739,18 +766,26 @@ public class FreemarkerServlet extends HttpServlet {
                     "Unexpected error when loading template " + StringUtil.jQuoteNoXSS(templatePath) + ".", e);
         }
 
-        String templateSpecificContentType = getTemplateSpecificContentType(template);
-        if (templateSpecificContentType != null) {
-            response.setContentType(templateSpecificContentType);
-        } else {
-            if (overrideResponseContentType || response.getContentType() == null) {
-                if (noCharsetInContentType) {
-                    response.setContentType(contentType + "; charset=" + template.getEncoding());
+        if (response.getContentType() == null || overrideResponseContentType != OverrideResponseContentType.NEVER) {
+            String templateSpecificContentType = getTemplateSpecificContentType(template);
+            if (templateSpecificContentType != null) {
+                response.setContentType(templateSpecificContentType);
+            } else if (response.getContentType() == null
+                    || overrideResponseContentType == OverrideResponseContentType.ALWAYS) {
+                if (!contentTypeContainsCharset && responseCharacterEncoding == ResponseCharacterEncoding.LEGACY) {
+                    response.setContentType(contentType + "; charset=" + getTemplateSpecificOutputEncoding(template));
                 } else {
                     response.setContentType(contentType);
                 }
             }
         }
+        
+        if (responseCharacterEncoding != ResponseCharacterEncoding.LEGACY
+                && responseCharacterEncoding != ResponseCharacterEncoding.DO_NOT_SET) {
+            // Using the Servlet 2.4 way of setting character encoding.
+            response.setCharacterEncoding(getTemplateSpecificOutputEncoding(template));
+            // TODO handle "always ${charset}"
+        }
 
         setBrowserCachingPolicy(response);
 
@@ -781,15 +816,26 @@ public class FreemarkerServlet extends HttpServlet {
         }
     }
 
+    private String getTemplateSpecificOutputEncoding(Template template) {
+        String outputEncoding = responseCharacterEncoding == ResponseCharacterEncoding.LEGACY ? null
+                : template.getOutputEncoding();
+        return outputEncoding != null ? outputEncoding : template.getEncoding();
+    }
+
     private String getTemplateSpecificContentType(final Template template) {
         Object contentTypeAttr = template.getCustomAttribute("content_type");
         if (contentTypeAttr != null) {
-            // Convert with toString() for backward compatibility
+            // Converted with toString() for backward compatibility.
+            // Don't add charset for backward compatibility.
             return contentTypeAttr.toString();
         }
         
         String outputFormatMimeType = template.getOutputFormat().getMimeType();
         if (outputFormatMimeType != null) {
+            if (responseCharacterEncoding == ResponseCharacterEncoding.LEGACY) {
+                // In legacy mode we won't call serlvetResponse.getCharacterEncoding(...), so:
+                outputFormatMimeType += "; charset=" + getTemplateSpecificOutputEncoding(template);
+            }
             return outputFormatMimeType; 
         }
             
@@ -842,7 +888,7 @@ public class FreemarkerServlet extends HttpServlet {
      * to use the locale indicated in the request.
      * 
      * @param templatePath
-     *            The template path (templat name) as it will be passed to {@link Configuration#getTemplate(String)}.
+     *            The template path (template name) as it will be passed to {@link Configuration#getTemplate(String)}.
      *            (Not to be confused with the servlet init-param of identical name; they aren't related.)
      * 
      * @throws ServletException
@@ -1398,4 +1444,71 @@ public class FreemarkerServlet extends HttpServlet {
         
     }
     
+    private <T extends InitParamValueEnum> T initParamValueToEnum(String initParamValue, T[] values) {
+        for (T value : values) {
+            if (initParamValue.equals(value.getInitParamValue())) {
+                return value;
+            }
+        }
+        
+        StringBuilder sb = new StringBuilder();
+        sb.append(StringUtil.jQuote(initParamValue));
+        sb.append(" is not a one of the enumeration values: ");
+        boolean first = true;
+        for (T value : values) {
+            if (!first) {
+                sb.append(", ");
+            } else {
+                first = false;
+            }
+            sb.append(StringUtil.jQuote(value.getInitParamValue()));
+        }
+        throw new IllegalArgumentException(sb.toString());
+    }
+
+    /**
+     * Superclass of all (future) init-param value enums.
+     * 
+     * @see #initParamValueToEnum
+     */
+    private interface InitParamValueEnum {
+        String getInitParamValue();
+    }
+    
+    private enum OverrideResponseContentType implements InitParamValueEnum {
+        ALWAYS(INIT_PARAM_VALUE_ALWAYS),
+        NEVER(INIT_PARAM_VALUE_NEVER),
+        WHEN_TEMPLATE_HAS_MIME_TYPE(INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE);
+
+        private final String initParamValue;
+        
+        OverrideResponseContentType(String initParamValue) {
+            this.initParamValue = initParamValue;
+        }
+
+        @Override
+        public String getInitParamValue() {
+            return initParamValue;
+        }
+    }
+    
+    private enum ResponseCharacterEncoding implements InitParamValueEnum {
+        LEGACY(INIT_PARAM_VALUE_LEGACY),
+        FROM_TEMPLATE(INIT_PARAM_VALUE_FROM_TEMPLATE),
+        DO_NOT_SET(INIT_PARAM_VALUE_DO_NOT_SET);
+        // TODO: "always ${charset}"
+
+        private final String initParamValue;
+        
+        ResponseCharacterEncoding(String initParamValue) {
+            this.initParamValue = initParamValue;
+        }
+
+        @Override
+        public String getInitParamValue() {
+            return initParamValue;
+        }
+        
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fca65a24/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index 58bfc7a..8030ffb 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -26039,16 +26039,15 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
 
             <listitem>
               <para>New <literal>FreemarkerServlet</literal> init-param:
-              <literal>OverrideResponseContentType</literal>. This specifies
-              if we should always set the <literal>contentType</literal> in
-              the <literal>HttpServletResponse</literal> (to the value of the
-              to the value of the <literal>ContentType</literal> init-param),
-              or only if it wasn't already set. The default is
-              <literal>true</literal>, which gives the backward compatible
-              behavior. Now that this init-param exists, you can disable this
-              behavior, so the <literal>contentType</literal> you have
-              specified before forwarding to
-              <literal>FreemarkerServlet</literal> wins.</para>
+              <literal>OverrideResponseContentType</literal>. Specifies when
+              should we override the <literal>contentType</literal> that's
+              already set (i.e., non-<literal>null</literal>) in the
+              <literal>HttpServletResponse</literal>. Earlier, we have always
+              set it, and that's still the default behavior. But now that this
+              init-param exists, you can change that behavior, so that the
+              <literal>contentType</literal> you have specified before
+              forwarding to <literal>FreemarkerServlet</literal>
+              matters.</para>
             </listitem>
 
             <listitem>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fca65a24/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
index 5bb8dc9..5f7e699 100644
--- a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
+++ b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
@@ -18,6 +18,7 @@
  */
 package freemarker.ext.servlet;
 
+import static freemarker.ext.servlet.FreemarkerServlet.*;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
@@ -32,213 +33,138 @@ import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.mock.web.MockServletConfig;
 import org.springframework.mock.web.MockServletContext;
-import org.springframework.util.Assert;
 
-import freemarker.log.Logger;
+import freemarker.template.Configuration;
 
 public class FreemarkerServletTest {
 
-    private static final Logger LOG = Logger.getLogger("freemarker.servlet");
-
     private static final String TEST_TEMPLATE_PATH = "classpath:freemarker/ext/servlet";
 
     private MockServletContext servletContext;
 
     @Before
-    public void setUp() throws ServletException, IOException {
+    public void setUp() throws Exception {
         servletContext = new MockServletContext();
         servletContext.setContextPath("/");
     }
 
     @Test
-    public void testContentTypeInitParams_withNoResponseContentType_DefaultOverriding() throws ServletException, IOException {
-        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        assertNull(response.getContentType());
-
-        createFreemarkerServlet().doGet(request, response);
-        LOG.debug("response content: " + response.getContentAsString());
-
-        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
-        assertTrue(response.getContentType().contains(FreemarkerServlet.DEFAULT_CONTENT_TYPE));
-    }
-
-    @Test
-    public void testContentTypeInitParams_withNoResponseContentType_DefaultOverriding2() throws ServletException, IOException {
-        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        assertNull(response.getContentType());
-
-        createFreemarkerServlet(
-                FreemarkerServlet.INIT_PARAM_CONTENT_TYPE, "text/css")
-                .doGet(request, response);
-        LOG.debug("response content: " + response.getContentAsString());
-
-        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
-        assertTrue(response.getContentType().contains("text/css"));
-    }
-
-    @Test
-    public void testContentTypeInitParams_withResponseContentType_DefaultOverriding() throws ServletException, IOException {
-        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        response.setContentType("application/json");
-        assertEquals("application/json", response.getContentType());
-
-        createFreemarkerServlet().doGet(request, response);
-        LOG.debug("response content: " + response.getContentAsString());
-
-        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
-        assertTrue(response.getContentType().contains(FreemarkerServlet.DEFAULT_CONTENT_TYPE));
-    }
-
-    @Test
-    public void testContentTypeInitParams_withResponseContentType_DefaultOverriding2() throws ServletException, IOException {
-        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        response.setContentType("application/json");
-        assertEquals("application/json", response.getContentType());
-
-        createFreemarkerServlet(
-                FreemarkerServlet.INIT_PARAM_CONTENT_TYPE, "text/css")
-                .doGet(request, response);
-        LOG.debug("response content: " + response.getContentAsString());
-
-        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
-        assertTrue(response.getContentType().contains("text/css"));
-    }
-
-    @Test
-    public void testContentTypeInitParams_withResponseContentType_NoOverriding() throws ServletException, IOException {
-        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        response.setContentType("application/json");
-        assertEquals("application/json", response.getContentType());
-
-        createFreemarkerServlet(FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false")
-                .doGet(request, response);
-        LOG.debug("response content: " + response.getContentAsString());
-
-        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
-        assertEquals("application/json", response.getContentType());
-    }
-
-    @Test
-    public void testContentTypeInitParams_withResponseContentType_NoOverriding2() throws ServletException, IOException {
-        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        response.setContentType("application/json");
-        assertEquals("application/json", response.getContentType());
-
-        createFreemarkerServlet(
-                FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false",
-                FreemarkerServlet.INIT_PARAM_CONTENT_TYPE, "text/css")
-                .doGet(request, response);
-        LOG.debug("response content: " + response.getContentAsString());
-
-        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
-        assertEquals("application/json", response.getContentType());
-    }
-
-    @Test
-    public void testContentTypeInitParams_withNoResponseContentType_NoOverriding() throws ServletException, IOException {
-        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        assertNull(response.getContentType());
-
-        createFreemarkerServlet(FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false")
-                .doGet(request, response);
-        LOG.debug("response content: " + response.getContentAsString());
-
-        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
-        assertTrue(response.getContentType().contains(FreemarkerServlet.DEFAULT_CONTENT_TYPE));
-    }
-
-    @Test
-    public void testContentTypeInitParams_withNoResponseContentType_NoOverriding2() throws ServletException, IOException {
-        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        assertNull(response.getContentType());
-
-        createFreemarkerServlet(
-                FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false",
-                FreemarkerServlet.INIT_PARAM_CONTENT_TYPE, "text/css")
-                .doGet(request, response);
-        LOG.debug("response content: " + response.getContentAsString());
-
-        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
-        assertTrue(response.getContentType().contains("text/css"));
-    }
-
-    @Test
-    public void testContentTypeInitParams_ftlAttrAlwaysWins_DefaultOverriding() throws ServletException, IOException {
-        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/contentTypeAttr.ftl");
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        response.setContentType("application/json");
-        assertEquals("application/json", response.getContentType());
-
-        createFreemarkerServlet().doGet(request, response);
-        LOG.debug("response content: " + response.getContentAsString());
-
-        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
-        assertEquals("text/plain", response.getContentType());
-    }
-    
-    @Test
-    public void testContentTypeInitParams_ftlAttrAlwaysWins_NoOverriding() throws ServletException, IOException {
-        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/contentTypeAttr.ftl");
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        response.setContentType("application/json");
-        assertEquals("application/json", response.getContentType());
-
-        createFreemarkerServlet(FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false")
-                .doGet(request, response);
-        LOG.debug("response content: " + response.getContentAsString());
-
-        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
-        assertEquals("text/plain", response.getContentType());
-    }
-
-    @Test
-    public void testContentTypeInitParams_outputFormatAlwaysWins_DefaultOverriding() throws ServletException, IOException {
-        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/outputFormatHeader.ftl");
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        response.setContentType("application/json");
-        assertEquals("application/json", response.getContentType());
-
-        createFreemarkerServlet().doGet(request, response);
-        LOG.debug("response content: " + response.getContentAsString());
-
-        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
-        assertEquals("text/plain", response.getContentType());
+    public void testContentTypeInitParams() throws Exception {
+        // Default is INIT_PARAM_VALUE_ALWAYS, hence null is the same:
+        for (String overrideCT : new String[] { null, INIT_PARAM_VALUE_ALWAYS }) {
+            assertResponseContentTypeEquals(
+                    DEFAULT_CONTENT_TYPE + "; charset=UTF-8", // <- expected
+                    null, overrideCT, // <- init-params
+                    "foo.ftl", null); // <- request
+            assertResponseContentTypeEquals(
+                    "text/css; charset=UTF-8", // <- expected
+                    "text/css", overrideCT, // <- init-params
+                    "foo.ftl", null); // <- request
+            assertResponseContentTypeEquals(
+                    DEFAULT_CONTENT_TYPE + "; charset=UTF-8", // <- expected
+                    null, overrideCT, // <- init-params
+                    "foo.ftl", "application/json"); // <- request
+            assertResponseContentTypeEquals(
+                    "text/css; charset=UTF-8", // <- expected
+                    "text/css", overrideCT, // <- init-params
+                    "foo.ftl", "application/json"); // <- request
+            assertResponseContentTypeEquals(
+                    "text/plain", // <- expected
+                    null, overrideCT, // <- init-params
+                    "contentTypeAttr.ftl", "application/json"); // <- request
+            assertResponseContentTypeEquals(
+                    "text/plain; charset=UTF-8", // <- expected
+                    null, overrideCT, // <- init-params
+                    "outputFormatHeader.ftl", "application/json"); // <- request
+        }
+        
+        assertResponseContentTypeEquals(
+                DEFAULT_CONTENT_TYPE + "; charset=UTF-8", // <- expected
+                null, INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE, // <- init-params
+                "foo.ftl", null); // <- request
+        assertResponseContentTypeEquals(
+                "text/css; charset=UTF-8", // <- expected
+                "text/css", INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE, // <- init-params
+                "foo.ftl", null); // <- request        
+        assertResponseContentTypeEquals(
+                "application/json", // <- expected
+                null, INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE, // <- init-params
+                "foo.ftl", "application/json"); // <- request
+        assertResponseContentTypeEquals(
+                "application/json", // <- expected
+                "text/css", INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE, // <- init-params
+                "foo.ftl", "application/json"); // <- request
+        assertResponseContentTypeEquals(
+                "text/plain", // <- expected
+                null, INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE, // <- init-params
+                "contentTypeAttr.ftl", "application/json"); // <- request
+        assertResponseContentTypeEquals(
+                "text/plain; charset=UTF-8", // <- expected
+                null, INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE, // <- init-params
+                "outputFormatHeader.ftl", "application/json"); // <- request
+        
+        assertResponseContentTypeEquals(
+                DEFAULT_CONTENT_TYPE + "; charset=UTF-8", // <- expected
+                null, INIT_PARAM_VALUE_NEVER, // <- init-params
+                "foo.ftl", null); // <- request
+        assertResponseContentTypeEquals(
+                "text/css; charset=UTF-8", // <- expected
+                "text/css", INIT_PARAM_VALUE_NEVER, // <- init-params
+                "foo.ftl", null); // <- request        
+        assertResponseContentTypeEquals(
+                "application/json", // <- expected
+                null, INIT_PARAM_VALUE_NEVER, // <- init-params
+                "foo.ftl", "application/json"); // <- request
+        assertResponseContentTypeEquals(
+                "application/json", // <- expected
+                "text/css", INIT_PARAM_VALUE_NEVER, // <- init-params
+                "foo.ftl", "application/json"); // <- request
+        assertResponseContentTypeEquals(
+                "application/json", // <- expected
+                null, INIT_PARAM_VALUE_NEVER, // <- init-params
+                "contentTypeAttr.ftl", "application/json"); // <- request
+        assertResponseContentTypeEquals(
+                "application/json", // <- expected
+                null, INIT_PARAM_VALUE_NEVER, // <- init-params
+                "outputFormatHeader.ftl", "application/json"); // <- request
     }
     
-    @Test
-    public void testContentTypeInitParams_outputFormatAlwaysWins_NoOverriding() throws ServletException, IOException {
-        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/outputFormatHeader.ftl");
+    private void assertResponseContentTypeEquals(
+            String exptectContentType,
+            String ctInitParam, String overrideCTInitParam,
+            String templateName, String responseCT)
+                    throws ServletException, IOException {
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, templateName);
+        
         MockHttpServletResponse response = new MockHttpServletResponse();
-        response.setContentType("application/json");
-        assertEquals("application/json", response.getContentType());
-
-        createFreemarkerServlet(FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false")
-                .doGet(request, response);
-        LOG.debug("response content: " + response.getContentAsString());
-
-        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
-        assertEquals("text/plain", response.getContentType());
-    }
+        if (responseCT != null) {
+            response.setContentType(responseCT);
+            assertEquals(responseCT, response.getContentType());
+        } else {
+            assertNull(response.getContentType());
+        }
     
-    private FreemarkerServlet createFreemarkerServlet(String... initParams) throws ServletException {
         MockServletConfig servletConfig = new MockServletConfig(servletContext);
-        servletConfig.addInitParameter(FreemarkerServlet.INIT_PARAM_TEMPLATE_PATH, TEST_TEMPLATE_PATH);
-        Assert.isTrue(initParams.length % 2 == 0);
-        for (int i = 0; i < initParams.length; i += 2) {
-            servletConfig.addInitParameter(initParams[i], initParams[i + 1]); 
+        servletConfig.addInitParameter(INIT_PARAM_TEMPLATE_PATH, TEST_TEMPLATE_PATH);
+        servletConfig.addInitParameter(Configuration.DEFAULT_ENCODING_KEY, "UTF-8");
+        if (ctInitParam != null) {
+            servletConfig.addInitParameter(INIT_PARAM_CONTENT_TYPE, ctInitParam);            
+        }
+        if (overrideCTInitParam != null) {
+            servletConfig.addInitParameter(INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, overrideCTInitParam);
         }
         
         FreemarkerServlet freemarkerServlet = new FreemarkerServlet();
-        freemarkerServlet.init(servletConfig);
-        return freemarkerServlet;
+        try {
+            freemarkerServlet.init(servletConfig);
+            
+            freemarkerServlet.doGet(request, response);
+        
+            assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+            assertEquals(exptectContentType, response.getContentType());
+        } finally {
+            freemarkerServlet.destroy();
+        }
     }
 
     private MockHttpServletRequest createMockHttpServletRequest(final ServletContext servletContext,


[13/25] incubator-freemarker git commit: Fixes and improvements in the object builder syntax used for configuring FreeMarker from java.util.Properties (or other string-only sources). This is not to be confused with the template language syntax, which has

Posted by dd...@apache.org.
Fixes and improvements in the object builder syntax used for configuring FreeMarker from java.util.Properties (or other string-only sources). This is not to be confused with the template language syntax, which has nothing to do with the object builder syntax:

(a) Number literals can have Java type specified postfixes (f, d, l), plus bd for BigDecimal and bi for BigInteger.

(b) Number literals with decimal point inside List literals and Map literals create Double values instead of BigDecimal-s. (For method and constructor call parameters and bean properties we still use BigDecimal, which is then converted to the target type anyway.)


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

Branch: refs/heads/2.3
Commit: a761e994c11d5f84718ac5256134b9199176e95d
Parents: 4ba6736
Author: ddekany <dd...@apache.org>
Authored: Sun Oct 25 19:44:27 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sun Oct 25 19:44:27 2015 +0100

----------------------------------------------------------------------
 src/main/java/freemarker/core/Configurable.java |  9 +-
 .../core/_ObjectBuilderSettingEvaluator.java    | 96 ++++++++++++++------
 src/manual/book.xml                             | 28 ++++++
 .../core/ObjectBuilderSettingsTest.java         | 66 +++++++++++++-
 4 files changed, 169 insertions(+), 30 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/a761e994/src/main/java/freemarker/core/Configurable.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index e7b9a99..c42fdcb 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -1869,8 +1869,13 @@ public class Configurable {
      *   <li>
      *      <p>Currently, the values of arguments and properties can only be one of these:
      *      <ul>
-     *        <li>A numerical literal, like {@code 123} or {@code -1.5}. Like in FTL, there are no numerical types,
-     *            the value will be automatically converted to the type of the target.</li>
+     *        <li>A numerical literal, like {@code 123} or {@code -1.5}. The value will be automatically converted to
+     *        the type of the target (just like in FTL). However, a target type is only available if the number will
+     *        be a parameter to a method or constructor, not when it's a value (or key) in a {@code List} or
+     *        {@code Map} literal. Thus in the last case the type of number will be like in Java language, like
+     *        {@code 1} is an {@code int}, and {@code 1.0} is a {@code double}, and {@code 1.0f} is a {@code float},
+     *        etc. In all cases, the standard Java type postfixes can be used ("f", "d", "l"), plus "bd" for
+     *        {@code BigDecimal} and "bi" for {@code BigInteger}.</li>
      *        <li>A boolean literal: {@code true} or {@code false}
      *        <li>The null literal: {@code null}
      *        <li>A string literal with FTL syntax, except that  it can't contain <tt>${...}</tt>-s and

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/a761e994/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java b/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
index 30abfdb..efcd3cc 100644
--- a/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
+++ b/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
@@ -125,7 +125,7 @@ public class _ObjectBuilderSettingEvaluator {
         
         skipWS();
         try {
-            value = ensureEvaled(fetchValue(false, true, true));
+            value = ensureEvaled(fetchValue(false, true, false, true));
         } catch (LegacyExceptionWrapperSettingEvaluationExpression e) {
             e.rethrowLegacy();
             value = null; // newer reached
@@ -209,7 +209,7 @@ public class _ObjectBuilderSettingEvaluator {
             do {
                 skipWS();
                 
-                Object paramNameOrValue = fetchValue(false, false, false);
+                Object paramNameOrValue = fetchValue(false, false, true, false);
                 if (paramNameOrValue != VOID) {
                     skipWS();
                     if (paramNameOrValue instanceof Name) {
@@ -219,7 +219,7 @@ public class _ObjectBuilderSettingEvaluator {
                         fetchRequiredChar("=");
                         skipWS();
                         
-                        Object paramValue = fetchValue(false, false, true);
+                        Object paramValue = fetchValue(false, false, true, true);
                         exp.namedParamValues.add(ensureEvaled(paramValue));
                     } else {
                         if (!exp.namedParamNames.isEmpty()) {
@@ -239,10 +239,10 @@ public class _ObjectBuilderSettingEvaluator {
         }
     }
 
-    private Object fetchValue(boolean optional, boolean topLevel, boolean resolveVariables)
+    private Object fetchValue(boolean optional, boolean topLevel, boolean resultCoerced, boolean resolveVariables)
             throws _ObjectBuilderSettingEvaluationException {
         if (pos < src.length()) {
-            Object val = fetchNumberLike(true);
+            Object val = fetchNumberLike(true, resultCoerced);
             if (val != VOID) {
                 return val;
             }
@@ -361,7 +361,8 @@ public class _ObjectBuilderSettingEvaluator {
         return className;
     }
 
-    private Object fetchNumberLike(boolean optional) throws _ObjectBuilderSettingEvaluationException {
+    private Object fetchNumberLike(boolean optional, boolean resultCoerced)
+            throws _ObjectBuilderSettingEvaluationException {
         int startPos = pos;
         boolean isVersion = false;
         boolean hasDot = false;
@@ -391,38 +392,81 @@ public class _ObjectBuilderSettingEvaluator {
             }
         }
         
-        String tk = src.substring(startPos, pos);
+        String numStr = src.substring(startPos, pos);
         if (isVersion) {
             try {
-                return new Version(tk);
+                return new Version(numStr);
             } catch (IllegalArgumentException e) {
-                throw new _ObjectBuilderSettingEvaluationException("Malformed version number: " + tk, e);
+                throw new _ObjectBuilderSettingEvaluationException("Malformed version number: " + numStr, e);
             }
         } else {
+            // For example, in 1.0f, numStr is "1.0", and typePostfix is "f".
+            String typePostfix = null;
+            seekTypePostfixEnd: while (true) {
+                if (pos == src.length()) {
+                    break seekTypePostfixEnd;
+                }
+                char c = src.charAt(pos);
+                if (Character.isLetter(c)) {
+                    if (typePostfix == null) {
+                        typePostfix = String.valueOf(c);
+                    } else {
+                        typePostfix += c; 
+                    }
+                } else {
+                    break seekTypePostfixEnd;
+                }
+                pos++;
+            }
+            
             try {
-                if (tk.endsWith(".")) {
+                if (numStr.endsWith(".")) {
                     throw new NumberFormatException("A number can't end with a dot");
                 }
-                if (tk.startsWith(".") || tk.startsWith("-.")  || tk.startsWith("+.")) {
+                if (numStr.startsWith(".") || numStr.startsWith("-.")  || numStr.startsWith("+.")) {
                     throw new NumberFormatException("A number can't start with a dot");
                 }
-                
-                if (tk.indexOf('.') == -1) {
-                    BigInteger biNum = new BigInteger(tk);
-                    final int bitLength = biNum.bitLength();  // Doesn't include sign bit
-                    if (bitLength <= 31) {
-                        return Integer.valueOf(biNum.intValue());
-                    } else if (bitLength <= 63) {
-                        return Long.valueOf(biNum.longValue());
+
+                if (typePostfix == null) {
+                    // Auto-detect type
+                    if (numStr.indexOf('.') == -1) {
+                        BigInteger biNum = new BigInteger(numStr);
+                        final int bitLength = biNum.bitLength();  // Doesn't include sign bit
+                        if (bitLength <= 31) {
+                            return Integer.valueOf(biNum.intValue());
+                        } else if (bitLength <= 63) {
+                            return Long.valueOf(biNum.longValue());
+                        } else {
+                            return biNum;
+                        }
+                    } else {
+                        if (resultCoerced) {
+                            // The FTL way (BigDecimal is loseless, and it will be coerced to the target type later):
+                            return new BigDecimal(numStr);
+                        } else {
+                            // The Java way (lossy but familiar):
+                            return Double.valueOf(numStr);
+                        }
+                    }
+                } else { // Has explicitly specified type
+                    if (typePostfix.equalsIgnoreCase("l")) {
+                        return Long.valueOf(numStr);
+                    } else if (typePostfix.equalsIgnoreCase("bi")) {
+                        return new BigInteger(numStr);
+                    } else if (typePostfix.equalsIgnoreCase("bd")) {
+                        return new BigDecimal(numStr);
+                    } else if (typePostfix.equalsIgnoreCase("d")) {
+                        return Double.valueOf(numStr);
+                    } else if (typePostfix.equalsIgnoreCase("f")) {
+                        return Float.valueOf(numStr);
                     } else {
-                        return biNum;
+                        throw new _ObjectBuilderSettingEvaluationException(
+                                "Unrecognized number type postfix: " + typePostfix);
                     }
-                } else {
-                    return new BigDecimal(tk);
                 }
                 
             } catch (NumberFormatException e) {
-                throw new _ObjectBuilderSettingEvaluationException("Malformed number: " + tk, e);
+                throw new _ObjectBuilderSettingEvaluationException("Malformed number: " + numStr, e);
             }
         }
     }
@@ -516,7 +560,7 @@ public class _ObjectBuilderSettingEvaluator {
                 skipWS();
             }
             
-            listExp.addItem(fetchValue(false, false, true));
+            listExp.addItem(fetchValue(false, false, false, true));
             
             skipWS();
         }
@@ -544,11 +588,11 @@ public class _ObjectBuilderSettingEvaluator {
                 skipWS();
             }
             
-            Object key = fetchValue(false, false, true);
+            Object key = fetchValue(false, false, false, true);
             skipWS();
             fetchRequiredChar(":");
             skipWS();
-            Object value = fetchValue(false, false, true);
+            Object value = fetchValue(false, false, false, true);
             mapExp.addItem(new KeyValuePair(key, value));
             
             skipWS();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/a761e994/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index 82f89ac..e461e94 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -26128,6 +26128,14 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
                   importance as number types are converted to the constructor
                   parameter type anyway.</para>
                 </listitem>
+
+                <listitem>
+                  <para>Number literals can have Java type specified postfixes
+                  (<literal>f</literal>, <literal>d</literal>,
+                  <literal>l</literal>), plus <literal>bd</literal> for
+                  <literal>BigDecimal</literal> and <literal>bi</literal> for
+                  <literal>BigInteger</literal>.</para>
+                </listitem>
               </itemizedlist>
             </listitem>
 
@@ -26229,6 +26237,26 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
               <literal>String</literal>-s and
               <literal>Interpolation</literal>-s.</para>
             </listitem>
+
+            <listitem>
+              <para>Fixes and improvements in the <quote>object
+              builder</quote> syntax used for configuring FreeMarker from
+              <literal>java.util.Properties</literal> (or other string-only
+              sources). This is not to be confused with the template language
+              syntax, which has nothing to do with the <quote>object
+              builder</quote> syntax we are writing about here. The
+              improvements are:</para>
+
+              <itemizedlist>
+                <listitem>
+                  <para>Number literals can have Java type specified postfixes
+                  (<literal>f</literal>, <literal>d</literal>,
+                  <literal>l</literal>), plus <literal>bd</literal> for
+                  <literal>BigDecimal</literal> and <literal>bi</literal> for
+                  <literal>BigInteger</literal>.</para>
+                </listitem>
+              </itemizedlist>
+            </listitem>
           </itemizedlist>
         </section>
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/a761e994/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java b/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java
index 0a1d53c..b708314 100644
--- a/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java
+++ b/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java
@@ -632,7 +632,7 @@ public class ObjectBuilderSettingsTest {
         assertEquals(Boolean.TRUE, _ObjectBuilderSettingEvaluator.eval(
                 "  true  ",
                 Object.class, true, _SettingEvaluationEnvironment.getCurrent()));
-        assertEquals(new BigDecimal("1.23"), _ObjectBuilderSettingEvaluator.eval(
+        assertEquals(Double.valueOf("1.23"), _ObjectBuilderSettingEvaluator.eval(
                 "1.23 ",
                 Number.class, true, _SettingEvaluationEnvironment.getCurrent()));
         assertEquals(new Version(1, 2, 3), _ObjectBuilderSettingEvaluator.eval(
@@ -642,7 +642,7 @@ public class ObjectBuilderSettingsTest {
 
     @Test
     public void testNumberLiteralJavaTypes() throws Exception {
-        assertEquals(new BigDecimal("1.0"), _ObjectBuilderSettingEvaluator.eval(
+        assertEquals(Double.valueOf("1.0"), _ObjectBuilderSettingEvaluator.eval(
                 "1.0",
                 Number.class, true, _SettingEvaluationEnvironment.getCurrent()));
 
@@ -858,6 +858,68 @@ public class ObjectBuilderSettingsTest {
             assertThat(e.getMessage(), containsString("null as key"));
         }
     }
+
+    @Test
+    public void testMethodParameterNumberTypes() throws Exception {
+        {
+            TestBean8 result = (TestBean8) _ObjectBuilderSettingEvaluator.eval(
+                    "freemarker.core.ObjectBuilderSettingsTest$TestBean8(anyObject=1)",
+                    TestBean8.class, false, new _SettingEvaluationEnvironment());
+            assertEquals(result.getAnyObject(), 1);
+        }
+        {
+            TestBean8 result = (TestBean8) _ObjectBuilderSettingEvaluator.eval(
+                    "freemarker.core.ObjectBuilderSettingsTest$TestBean8(anyObject=2147483649)",
+                    TestBean8.class, false, new _SettingEvaluationEnvironment());
+            assertEquals(result.getAnyObject(), 2147483649L);
+        }
+        {
+            TestBean8 result = (TestBean8) _ObjectBuilderSettingEvaluator.eval(
+                    "freemarker.core.ObjectBuilderSettingsTest$TestBean8(anyObject=1.0)",
+                    TestBean8.class, false, new _SettingEvaluationEnvironment());
+            // Like in FTL, non-integer numbers are BigDecimal-s, that are later coerced to the actual parameter type.
+            // However, here the type is Object, so it remains BigDecimal.
+            assertEquals(new BigDecimal("1.0"), result.getAnyObject());
+        }
+    }
+    
+    @Test
+    public void testNonMethodParameterNumberTypes() throws Exception {
+        assertEqualsEvaled(Integer.valueOf(1), "1");
+        assertEqualsEvaled(Double.valueOf(1), "1.0");
+        assertEqualsEvaled(Long.valueOf(2147483649l), "2147483649");
+
+        assertEqualsEvaled(Double.valueOf(1), "1d");
+        assertEqualsEvaled(Double.valueOf(1), "1D");
+        assertEqualsEvaled(Float.valueOf(1), "1f");
+        assertEqualsEvaled(Float.valueOf(1), "1F");
+        assertEqualsEvaled(Long.valueOf(1), "1l");
+        assertEqualsEvaled(Long.valueOf(1), "1L");
+        assertEqualsEvaled(BigDecimal.valueOf(1), "1bd");
+        assertEqualsEvaled(BigDecimal.valueOf(1), "1Bd");
+        assertEqualsEvaled(BigDecimal.valueOf(1), "1BD");
+        assertEqualsEvaled(BigInteger.valueOf(1), "1bi");
+        assertEqualsEvaled(BigInteger.valueOf(1), "1bI");
+        
+        assertEqualsEvaled(Float.valueOf(1.5f), "1.5f");
+        assertEqualsEvaled(Double.valueOf(1.5), "1.5d");
+        assertEqualsEvaled(BigDecimal.valueOf(1.5), "1.5bd");
+        
+        assertEqualsEvaled(
+                ImmutableList.of(-1, -0.5, new BigDecimal("-0.1")),
+                "[ -1, -0.5, -0.1bd ]");
+        assertEqualsEvaled(
+                ImmutableMap.of(-1, -11, -0.5, -0.55, new BigDecimal("-0.1"), new BigDecimal("-0.11")),
+                "{ -1: -11, -0.5: -0.55, -0.1bd: -0.11bd }");
+    }
+    
+    private void assertEqualsEvaled(Object expectedValue, String s)
+            throws _ObjectBuilderSettingEvaluationException, ClassNotFoundException, InstantiationException,
+            IllegalAccessException {
+        Object actualValue = _ObjectBuilderSettingEvaluator.eval(
+                s, Object.class, true, _SettingEvaluationEnvironment.getCurrent());
+        assertEquals(expectedValue, actualValue);
+    }
     
     @Test
     public void visibilityTest() throws Exception {


[02/25] incubator-freemarker git commit: Merge remote-tracking branch 'FREEMARKER-1-option-overrideResponseContentType' into 2.3-gae

Posted by dd...@apache.org.
Merge remote-tracking branch 'FREEMARKER-1-option-overrideResponseContentType' into 2.3-gae

FREEMARKER-1: Adding OverrideResponseContentType init param in FreemarkerServlet
GitHub pull request #5


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

Branch: refs/heads/2.3
Commit: 3ab293ad12e1aaf2e2bededcd3540884d35f9fae
Parents: 0256df5 e89dbfc
Author: ddekany <dd...@apache.org>
Authored: Sat Oct 17 17:44:28 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sat Oct 17 17:48:10 2015 +0200

----------------------------------------------------------------------
 .classpath                                      |   2 +
 ivy.xml                                         |  10 +-
 .../ext/servlet/FreemarkerServlet.java          |  37 ++++--
 .../ext/servlet/FreemarkerServletTest.java      | 124 +++++++++++++++++++
 .../resources/freemarker/ext/servlet/foo.ftl    |  19 +++
 5 files changed, 181 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3ab293ad/.classpath
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3ab293ad/ivy.xml
----------------------------------------------------------------------


[04/25] incubator-freemarker git commit: Continued FREEMARKER-1 "Option to not to overwrite response ContentType in FreemarkerServlet": More tests

Posted by dd...@apache.org.
Continued FREEMARKER-1 "Option to not to overwrite response ContentType in FreemarkerServlet": More tests


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

Branch: refs/heads/2.3
Commit: 64ec04bf8d53a260905a174876bba434d2a13eaf
Parents: fe08488
Author: ddekany <dd...@apache.org>
Authored: Sat Oct 17 19:28:01 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sat Oct 17 19:28:01 2015 +0200

----------------------------------------------------------------------
 .../ext/servlet/FreemarkerServletTest.java      | 112 ++++++++++++++-----
 1 file changed, 83 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/64ec04bf/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
index bf2ef8a..20ac78d 100644
--- a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
+++ b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
@@ -26,13 +26,13 @@ import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletResponse;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.mock.web.MockServletConfig;
 import org.springframework.mock.web.MockServletContext;
+import org.springframework.util.Assert;
 
 import freemarker.log.Logger;
 
@@ -42,49 +42,50 @@ public class FreemarkerServletTest {
 
     private static final String TEST_TEMPLATE_PATH = "classpath:freemarker/ext/servlet";
 
-    private FreemarkerServlet freemarkerServlet;
-
     private MockServletContext servletContext;
-    private MockServletConfig servletConfig;
 
     @Before
     public void setUp() throws ServletException, IOException {
         servletContext = new MockServletContext();
         servletContext.setContextPath("/");
+    }
 
-        servletConfig = new MockServletConfig(servletContext);
-        servletConfig.addInitParameter(FreemarkerServlet.INIT_PARAM_TEMPLATE_PATH, TEST_TEMPLATE_PATH);
+    @Test
+    public void testContentTypeInitParams_withNoResponseContentType_DefaultOverriding() throws ServletException, IOException {
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        assertNull(response.getContentType());
 
-        freemarkerServlet = new FreemarkerServlet();
-        freemarkerServlet.init(servletConfig);
-    }
+        createFreemarkerServlet().doGet(request, response);
+        LOG.debug("response content: " + response.getContentAsString());
 
-    @After
-    public void tearDown() {
-        freemarkerServlet.destroy();
+        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+        assertTrue(response.getContentType().contains(FreemarkerServlet.DEFAULT_CONTENT_TYPE));
     }
 
     @Test
-    public void testContentTypeInitParams_withNoResponseContentType_ByDefault() throws ServletException, IOException {
+    public void testContentTypeInitParams_withNoResponseContentType_DefaultOverriding2() throws ServletException, IOException {
         MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
         MockHttpServletResponse response = new MockHttpServletResponse();
         assertNull(response.getContentType());
 
-        freemarkerServlet.doGet(request, response);
+        createFreemarkerServlet(
+                FreemarkerServlet.INIT_PARAM_CONTENT_TYPE, "text/css")
+                .doGet(request, response);
         LOG.debug("response content: " + response.getContentAsString());
 
         assertEquals(HttpServletResponse.SC_OK, response.getStatus());
-        assertTrue(response.getContentType().contains(FreemarkerServlet.DEFAULT_CONTENT_TYPE));
+        assertTrue(response.getContentType().contains("text/css"));
     }
 
     @Test
-    public void testContentTypeInitParams_withResponseContentType_ByDefault() throws ServletException, IOException {
+    public void testContentTypeInitParams_withResponseContentType_DefaultOverriding() throws ServletException, IOException {
         MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
         MockHttpServletResponse response = new MockHttpServletResponse();
         response.setContentType("application/json");
         assertEquals("application/json", response.getContentType());
 
-        freemarkerServlet.doGet(request, response);
+        createFreemarkerServlet().doGet(request, response);
         LOG.debug("response content: " + response.getContentAsString());
 
         assertEquals(HttpServletResponse.SC_OK, response.getStatus());
@@ -92,42 +93,94 @@ public class FreemarkerServletTest {
     }
 
     @Test
-    public void testContentTypeInitParams_withResponseContentType_NoOverriding() throws ServletException, IOException {
-        servletConfig.addInitParameter(FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false");
-        freemarkerServlet = new FreemarkerServlet();
-        freemarkerServlet.init(servletConfig);
+    public void testContentTypeInitParams_withResponseContentType_DefaultOverriding2() throws ServletException, IOException {
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        response.setContentType("application/json");
+        assertEquals("application/json", response.getContentType());
 
+        createFreemarkerServlet(
+                FreemarkerServlet.INIT_PARAM_CONTENT_TYPE, "text/css")
+                .doGet(request, response);
+        LOG.debug("response content: " + response.getContentAsString());
+
+        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+        assertTrue(response.getContentType().contains("text/css"));
+    }
+
+    @Test
+    public void testContentTypeInitParams_withResponseContentType_NoOverriding() throws ServletException, IOException {
         MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
         MockHttpServletResponse response = new MockHttpServletResponse();
         response.setContentType("application/json");
         assertEquals("application/json", response.getContentType());
 
-        freemarkerServlet.doGet(request, response);
+        createFreemarkerServlet(FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false")
+                .doGet(request, response);
         LOG.debug("response content: " + response.getContentAsString());
 
         assertEquals(HttpServletResponse.SC_OK, response.getStatus());
         assertEquals("application/json", response.getContentType());
+    }
+
+    @Test
+    public void testContentTypeInitParams_withResponseContentType_NoOverriding2() throws ServletException, IOException {
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        response.setContentType("application/json");
+        assertEquals("application/json", response.getContentType());
 
-        freemarkerServlet.destroy();
+        createFreemarkerServlet(
+                FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false",
+                FreemarkerServlet.INIT_PARAM_CONTENT_TYPE, "text/css")
+                .doGet(request, response);
+        LOG.debug("response content: " + response.getContentAsString());
+
+        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+        assertEquals("application/json", response.getContentType());
     }
 
     @Test
     public void testContentTypeInitParams_withNoResponseContentType_NoOverriding() throws ServletException, IOException {
-        servletConfig.addInitParameter(FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false");
-        freemarkerServlet = new FreemarkerServlet();
-        freemarkerServlet.init(servletConfig);
-
         MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
         MockHttpServletResponse response = new MockHttpServletResponse();
         assertNull(response.getContentType());
 
-        freemarkerServlet.doGet(request, response);
+        createFreemarkerServlet(FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false")
+                .doGet(request, response);
         LOG.debug("response content: " + response.getContentAsString());
 
         assertEquals(HttpServletResponse.SC_OK, response.getStatus());
         assertTrue(response.getContentType().contains(FreemarkerServlet.DEFAULT_CONTENT_TYPE));
+    }
+
+    @Test
+    public void testContentTypeInitParams_withNoResponseContentType_NoOverriding2() throws ServletException, IOException {
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        assertNull(response.getContentType());
 
-        freemarkerServlet.destroy();
+        createFreemarkerServlet(
+                FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false",
+                FreemarkerServlet.INIT_PARAM_CONTENT_TYPE, "text/css")
+                .doGet(request, response);
+        LOG.debug("response content: " + response.getContentAsString());
+
+        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+        assertTrue(response.getContentType().contains("text/css"));
+    }
+    
+    private FreemarkerServlet createFreemarkerServlet(String... initParams) throws ServletException {
+        MockServletConfig servletConfig = new MockServletConfig(servletContext);
+        servletConfig.addInitParameter(FreemarkerServlet.INIT_PARAM_TEMPLATE_PATH, TEST_TEMPLATE_PATH);
+        Assert.isTrue(initParams.length % 2 == 0);
+        for (int i = 0; i < initParams.length; i += 2) {
+            servletConfig.addInitParameter(initParams[i], initParams[i + 1]); 
+        }
+        
+        FreemarkerServlet freemarkerServlet = new FreemarkerServlet();
+        freemarkerServlet.init(servletConfig);
+        return freemarkerServlet;
     }
 
     private MockHttpServletRequest createMockHttpServletRequest(final ServletContext servletContext,
@@ -140,4 +193,5 @@ public class FreemarkerServletTest {
         servletRequest.setPathInfo(pathInfo);
         return servletRequest;
     }
+    
 }
\ No newline at end of file


[12/25] incubator-freemarker git commit: Made the default of the "OverrideResponseContentType" FreemarkerServlet init-param overrideable via subclassing.

Posted by dd...@apache.org.
Made the default of the "OverrideResponseContentType" FreemarkerServlet init-param overrideable via subclassing.


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

Branch: refs/heads/2.3
Commit: 4ba67367821a553693287ac39d4d6527f295f3de
Parents: 064d12e
Author: ddekany <dd...@apache.org>
Authored: Sun Oct 25 16:48:19 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sun Oct 25 16:48:19 2015 +0100

----------------------------------------------------------------------
 .../freemarker/ext/servlet/FreemarkerServlet.java    | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/4ba67367/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index c344bb4..f7731ee 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -400,7 +400,6 @@ public class FreemarkerServlet extends HttpServlet {
      * @since 2.3.22
      */
     public static final String META_INF_TLD_LOCATION_CLEAR = "clear";
-    
 
     public static final String KEY_REQUEST = "Request";
     public static final String KEY_INCLUDE = "include_page";
@@ -459,7 +458,8 @@ public class FreemarkerServlet extends HttpServlet {
     @SuppressFBWarnings(value="SE_BAD_FIELD", justification="Not investing into making this Servlet serializable")
     private ObjectWrapper wrapper;
     private String contentType;
-    private OverrideResponseContentType overrideResponseContentType = OverrideResponseContentType.ALWAYS;
+    private OverrideResponseContentType overrideResponseContentType = initParamValueToEnum(
+            getDefaultOverrideResponseContentType(), OverrideResponseContentType.values());
     private ResponseCharacterEncoding responseCharacterEncoding = ResponseCharacterEncoding.LEGACY;
     private boolean contentTypeContainsCharset;
     private List/*<MetaInfTldSource>*/ metaInfTldSources;
@@ -1364,6 +1364,17 @@ public class FreemarkerServlet extends HttpServlet {
     }
 
     /**
+     * Returns the default value of the {@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE} Servlet init-param.
+     * The method inherited from {@link FreemarkerServlet} returns {@value #INIT_PARAM_VALUE_ALWAYS}; subclasses my
+     * override this.
+     * 
+     * @since 2.3.24
+     */
+    protected String getDefaultOverrideResponseContentType() {
+        return INIT_PARAM_VALUE_ALWAYS;
+    }
+
+    /**
      * If the parameter "nocache" was set to true, generate a set of headers
      * that will advise the HTTP client not to cache the returned page.
      */


[10/25] incubator-freemarker git commit: FREEMARKER-2 adding 'OverrideResponseLocale' init param option in FreemarkerServlet

Posted by dd...@apache.org.
FREEMARKER-2 adding 'OverrideResponseLocale' init param option in
FreemarkerServlet

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

Branch: refs/heads/2.3
Commit: 1e4c64f1c0230ec9f6ddc70fed6e142d3b8199ca
Parents: eef30af
Author: Woonsan Ko <wo...@apache.org>
Authored: Sun Oct 25 23:41:04 2015 +0900
Committer: Woonsan Ko <wo...@apache.org>
Committed: Sun Oct 25 23:41:04 2015 +0900

----------------------------------------------------------------------
 .../ext/servlet/FreemarkerServlet.java          | 69 ++++++++++----
 .../ext/servlet/FreemarkerServletTest.java      | 95 ++++++++++++++++++--
 2 files changed, 144 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/1e4c64f1/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index c344bb4..560c459 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -140,8 +140,9 @@ import freemarker.template.utility.StringUtil;
  * when nothing else specifies the MIME type. The things that may specify the MIME type (and hence this init-param is
  * ignored), starting with the highest precedence, are:
  * <ol>
- * <li>If the {@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE} init-param is {@value #INIT_PARAM_VALUE_NEVER} (it
- * itn't be default), then the value of {@link HttpServletResponse#getContentType()} is if that's non-{@code null}.
+ * <li>If the {@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE} init-param is {@value #INIT_PARAM_VALUE_NEVER} (the
+ * default is {@value #INIT_PARAM_VALUE_ALWAYS}), then the value of {@link HttpServletResponse#getContentType()} is used
+ * if that's non-{@code null}.
  * <li>The template's custom attribute name <tt>content_type</tt> in the <tt>attributes</tt> parameter of the
  * <tt>&lt;#ftl&gt;</tt> directive. This is a legacy feature, deprecated by the {@link OutputFormat} mechanism.
  * <li>The {@linkplain Template#getOutputFormat() output format of the template}, if that has non-{@code null} MIME-type
@@ -159,15 +160,23 @@ import freemarker.template.utility.StringUtil;
  * then the charset (encoding) of the actual template file will appended after it, which, as per the Servlet
  * specification, also sets the actual encoding used to write the response body.</li>
  *
- * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> (since 2.3.24): Specifies when should we
- * override the {@code contentType} that's already set (i.e., non-{@code null}) in the {@link HttpServletResponse}. The
- * default is {@value #INIT_PARAM_VALUE_ALWAYS}, which means that we always set the content type. Another possible value
- * is {@value #INIT_PARAM_VALUE_NEVER}, which means that we don't set the content type in the response, unless
- * {@link HttpServletResponse#getContentType()} is {@code null}. The third possible value is
- * {@value #INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE}, which means that we only set the content type if either the
- * template has an associated {@link OutputFormat} with non-{@code null} {@link OutputFormat#getMimeType()}, or it has a
- * custom attribute with name <tt>content_type</tt>, or {@link HttpServletResponse#getContentType()} is {@code null}.
- * Setting this init-param allows you to specify the content type before forwarding to {@link FreemarkerServlet}.</li>
+ * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> (since 2.3.24): Specifies when we should
+ * override the {@code contentType} that might be already set (i.e., non-{@code null}) in the
+ * {@link HttpServletResponse}. The default is {@value #INIT_PARAM_VALUE_ALWAYS}, which means that we always set the
+ * content type. Another possible value is {@value #INIT_PARAM_VALUE_NEVER}, which means that we don't set the content
+ * type in the response, unless {@link HttpServletResponse#getContentType()} is {@code null}. The third possible value
+ * is {@value #INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE}, which means that we only set the content type if either
+ * the template has an associated {@link OutputFormat} with non-{@code null} {@link OutputFormat#getMimeType()}, or it
+ * has a custom attribute with name <tt>content_type</tt>, or {@link HttpServletResponse#getContentType()} is
+ * {@code null}. Setting this init-param allows you to specify the content type before forwarding to
+ * {@link FreemarkerServlet}.</li>
+ *
+ * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_LOCALE}</strong> (since 2.3.24): Specifies when we should
+ * override the template {@code locale} that might be already set (i.e., non-{@code null}) in the {@link HttpServletRequest}.
+ * The default is {@value #INIT_PARAM_VALUE_ALWAYS}, which means that we always deduce the template {@code locale}
+ * by invoking {@link #deduceLocale(String, HttpServletRequest, HttpServletResponse)}. Another possible value is
+ * {@value #INIT_PARAM_VALUE_NEVER}, which means that we don't deduce the template {@code locale}, unless
+ * {@link HttpServletRequest#getLocale()} is {@code null}.
  *
  * <li><strong>{@value #INIT_PARAM_BUFFER_SIZE}</strong>: Sets the size of the output buffer in bytes, or if "KB" or
  * "MB" is written after the number (like {@code <param-value>256 KB</param-value>}) then in kilobytes or megabytes.
@@ -310,7 +319,14 @@ public class FreemarkerServlet extends HttpServlet {
      * @since 2.3.24
      */
     public static final String INIT_PARAM_RESPONSE_CHARACTER_ENCODING = "ResponseCharacterEncoding";
-    
+
+    /**
+     * Init-param name - see the {@link FreemarkerServlet} class documentation about the init-params.
+     *
+     * @since 2.3.24
+     */
+    public static final String INIT_PARAM_OVERRIDE_RESPONSE_LOCALE = "OverrideResponseLocale";
+
     /**
      * Init-param name - see the {@link FreemarkerServlet} class documentation about the init-params.
      * 
@@ -462,6 +478,7 @@ public class FreemarkerServlet extends HttpServlet {
     private OverrideResponseContentType overrideResponseContentType = OverrideResponseContentType.ALWAYS;
     private ResponseCharacterEncoding responseCharacterEncoding = ResponseCharacterEncoding.LEGACY;
     private boolean contentTypeContainsCharset;
+    private OverrideResponseLocale overrideResponseLocale = OverrideResponseLocale.ALWAYS;
     private List/*<MetaInfTldSource>*/ metaInfTldSources;
     private List/*<String>*/ classpathTlds;
 
@@ -610,6 +627,8 @@ public class FreemarkerServlet extends HttpServlet {
                     overrideResponseContentType = initParamValueToEnum(value, OverrideResponseContentType.values());
                 } else if (name.equals(INIT_PARAM_RESPONSE_CHARACTER_ENCODING)) {
                     responseCharacterEncoding = initParamValueToEnum(value, ResponseCharacterEncoding.values());
+                } else if (name.equals(INIT_PARAM_OVERRIDE_RESPONSE_LOCALE)) {
+                    overrideResponseLocale = initParamValueToEnum(value, OverrideResponseLocale.values());
                 } else if (name.equals(INIT_PARAM_EXCEPTION_ON_MISSING_TEMPLATE)) {
                     exceptionOnMissingTemplate = StringUtil.getYesNo(value);
                 } else if (name.equals(INIT_PARAM_META_INF_TLD_LOCATIONS)) {;
@@ -741,8 +760,12 @@ public class FreemarkerServlet extends HttpServlet {
             LOG.debug("Requested template " + StringUtil.jQuoteNoXSS(templatePath) + ".");
         }
 
-        final Locale locale = deduceLocale(templatePath, request, response);
-        
+        Locale locale = request.getLocale();
+
+        if (locale == null || overrideResponseLocale != OverrideResponseLocale.NEVER) {
+            locale = deduceLocale(templatePath, request, response);
+        }
+
         final Template template;
         try {
             template = config.getTemplate(templatePath, locale);
@@ -1510,5 +1533,21 @@ public class FreemarkerServlet extends HttpServlet {
         }
         
     }
-    
+
+    private enum OverrideResponseLocale implements InitParamValueEnum {
+        ALWAYS(INIT_PARAM_VALUE_ALWAYS),
+        NEVER(INIT_PARAM_VALUE_NEVER);
+
+        private final String initParamValue;
+
+        OverrideResponseLocale(String initParamValue) {
+            this.initParamValue = initParamValue;
+        }
+
+        @Override
+        public String getInitParamValue() {
+            return initParamValue;
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/1e4c64f1/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
index 5f7e699..f18bbcb 100644
--- a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
+++ b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
@@ -22,9 +22,11 @@ import static freemarker.ext.servlet.FreemarkerServlet.*;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
+import java.util.Locale;
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.junit.Before;
@@ -35,6 +37,8 @@ import org.springframework.mock.web.MockServletConfig;
 import org.springframework.mock.web.MockServletContext;
 
 import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateModel;
 
 public class FreemarkerServletTest {
 
@@ -128,13 +132,45 @@ public class FreemarkerServletTest {
                 null, INIT_PARAM_VALUE_NEVER, // <- init-params
                 "outputFormatHeader.ftl", "application/json"); // <- request
     }
-    
+
+    @Test
+    public void testResponseLocaleInitParams() throws Exception {
+        // By default, the Configurable.locale is set to Locale.getDefault().
+        final Locale defaultLocale = Locale.getDefault();
+
+        assertTemplateLocaleEquals(
+                defaultLocale, // <- expected template locale
+                null, // <- request locale
+                null, // <- init-param
+                "foo.ftl");
+        assertTemplateLocaleEquals(
+                defaultLocale, // <- expected template locale
+                Locale.FRENCH, // <- request locale
+                null, // <- init-param
+                "foo.ftl");
+        assertTemplateLocaleEquals(
+                defaultLocale, // <- expected template locale
+                Locale.FRENCH, // <- request locale
+                INIT_PARAM_VALUE_ALWAYS, // <- init-param
+                "foo.ftl");
+        assertTemplateLocaleEquals(
+                defaultLocale, // <- expected template locale
+                null, // <- request locale
+                INIT_PARAM_VALUE_NEVER, // <- init-param
+                "foo.ftl");
+        assertTemplateLocaleEquals(
+                Locale.FRENCH, // <- expected template locale
+                Locale.FRENCH, // <- request locale
+                INIT_PARAM_VALUE_NEVER, // <- init-param
+                "foo.ftl");
+    }
+
     private void assertResponseContentTypeEquals(
             String exptectContentType,
             String ctInitParam, String overrideCTInitParam,
             String templateName, String responseCT)
                     throws ServletException, IOException {
-        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, templateName);
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, templateName, null);
         
         MockHttpServletResponse response = new MockHttpServletResponse();
         if (responseCT != null) {
@@ -167,15 +203,64 @@ public class FreemarkerServletTest {
         }
     }
 
+    private void assertTemplateLocaleEquals(
+            Locale exptectLocale,
+            Locale requestLocale,
+            String overrideResponseLocaleInitParam,
+            String templateName)
+                    throws ServletException, IOException {
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, templateName, requestLocale);
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        MockServletConfig servletConfig = new MockServletConfig(servletContext);
+        servletConfig.addInitParameter(INIT_PARAM_TEMPLATE_PATH, TEST_TEMPLATE_PATH);
+
+        if (overrideResponseLocaleInitParam != null) {
+            servletConfig.addInitParameter(INIT_PARAM_OVERRIDE_RESPONSE_LOCALE, overrideResponseLocaleInitParam);
+        }
+
+        final Template [] processedTemplateHolder = new Template[1];
+
+        FreemarkerServlet freemarkerServlet = new FreemarkerServlet() {
+
+            @Override
+            protected void postTemplateProcess(
+                    HttpServletRequest request,
+                    HttpServletResponse response,
+                    Template template,
+                    TemplateModel data)
+                            throws ServletException, IOException {
+                processedTemplateHolder[0] = template;
+            }
+        };
+
+        try {
+            freemarkerServlet.init(servletConfig);
+            freemarkerServlet.doGet(request, response);
+
+            assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+            assertEquals(exptectLocale, processedTemplateHolder[0].getLocale());
+        } finally {
+            freemarkerServlet.destroy();
+        }
+    }
+
     private MockHttpServletRequest createMockHttpServletRequest(final ServletContext servletContext,
-            final String pathInfo) {
-        MockHttpServletRequest servletRequest = new MockHttpServletRequest(servletContext);
+            final String pathInfo, final Locale requestLocale) {
+        MockHttpServletRequest servletRequest = new MockHttpServletRequest(servletContext) {
+            @Override
+            public Locale getLocale() {
+                return requestLocale;
+            }
+        };
+
         servletRequest.setServerName("localhost");
         servletRequest.setServerPort(8080);
         servletRequest.setContextPath("");
         servletRequest.setRequestURI(pathInfo);
         servletRequest.setPathInfo(pathInfo);
+
         return servletRequest;
     }
-    
+
 }
\ No newline at end of file


[23/25] incubator-freemarker git commit: FreemarkerServlet documentation fixes/improvements

Posted by dd...@apache.org.
FreemarkerServlet documentation fixes/improvements


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

Branch: refs/heads/2.3
Commit: 5d1a411abba840d139293a9442694ef61a92037b
Parents: 7540a51
Author: ddekany <dd...@apache.org>
Authored: Sat Nov 28 11:08:01 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sat Nov 28 11:08:01 2015 +0100

----------------------------------------------------------------------
 .../ext/servlet/FreemarkerServlet.java          | 63 +++++++++++---------
 src/manual/book.xml                             | 19 +++---
 2 files changed, 46 insertions(+), 36 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5d1a411a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index 092dc8e..a7fc2e6 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -113,10 +113,10 @@ import freemarker.template.utility.StringUtil;
  * 
  * <ul>
  * 
- * <li><strong>{@value #INIT_PARAM_TEMPLATE_PATH}</strong>: Specifies the location of the templates. By default, this is
- * interpreted as a {@link ServletContext} reasource path, which practically means a web application directory relative
- * path, or a {@code WEB-INF/lib/*.jar/META-INF/resources}-relative path (note that this last didn't work properly
- * before FreeMarker 2.3.23).<br>
+ * <li><strong>{@value #INIT_PARAM_TEMPLATE_PATH}</strong>: Specifies the location of the template files. By default,
+ * this is interpreted as a {@link ServletContext} resource path, which practically means a web application directory
+ * relative path, or a {@code WEB-INF/lib/*.jar/META-INF/resources}-relative path (note that this last haven't always
+ * worked before FreeMarker 2.3.23).<br>
  * Alternatively, you can prepend it with <tt>file://</tt> to indicate a literal path in the file system (i.e.
  * <tt>file:///var/www/project/templates/</tt>). Note that three slashes were used to specify an absolute path.<br>
  * Also, you can prepend it with {@code classpath:}, like in <tt>classpath:com/example/templates</tt>, to indicate that
@@ -133,13 +133,14 @@ import freemarker.template.utility.StringUtil;
  * {@link WebappTemplateLoader#setURLConnectionUsesCaches(Boolean)} to tune the {@link WebappTemplateLoader}. For
  * backward compatibility (not recommended!), you can use the {@code class://} prefix, like in
  * <tt>class://com/example/templates</tt> format, which is similar to {@code classpath:}, except that it uses the
- * defining class loader of this servlet's class. This can cause template not found errors, if that class (in
+ * defining class loader of this servlet's class. This can cause template-not-found errors, if that class (in
  * {@code freemarer.jar} usually) is not local to the web application, while the templates are.<br>
  * The default value is <tt>class://</tt> (that is, the root of the class hierarchy), which is not recommended anymore,
- * and should be overwritten with the init-param.</li>
+ * and should be overwritten with the {@value #INIT_PARAM_TEMPLATE_PATH} init-param.</li>
  * 
- * <li><strong>{@value #INIT_PARAM_NO_CACHE}</strong>: If set to true, generates headers in the response that advise the
- * HTTP client not to cache the returned page. The default is <tt>false</tt>.</li>
+ * <li><strong>{@value #INIT_PARAM_NO_CACHE}</strong>: If set to {@code true}, generates headers in the response that
+ * advise the HTTP client not to cache the returned page. If {@code false}, the HTTP response is not modified for this
+ * purpose. The default is {@code false}.</li>
  * 
  * <li><strong>{@value #INIT_PARAM_CONTENT_TYPE}</strong>: The Content-type HTTP header value used in the HTTP responses
  * when nothing else specifies the MIME type. The things that may specify the MIME type (and hence this init-param is
@@ -1004,6 +1005,10 @@ public class FreemarkerServlet extends HttpServlet {
         return config.getLocale();
     }
 
+    /**
+     * Creates the data-model that will be passed to {@link Template#process(Object, java.io.Writer)}.
+     * The default implementation in {@link FreemarkerServlet} returns a {@link AllHttpScopesHashModel}.
+     */
     protected TemplateModel createModel(ObjectWrapper objectWrapper,
                                         ServletContext servletContext,
                                         final HttpServletRequest request,
@@ -1410,39 +1415,39 @@ public class FreemarkerServlet extends HttpServlet {
     }
 
     /**
-     * Called before the execution is passed to template.process().
-     * This is a generic hook you might use in subclasses to perform a specific
-     * action before the template is processed. By default does nothing.
-     * A typical action to perform here is to inject application-specific
-     * objects into the model root
-     *
-     * <p>Example: Expose the Serlvet context path as "baseDir" for all templates:
-     *
-     *<pre>
-     *    ((SimpleHash) data).put("baseDir", request.getContextPath() + "/");
-     *    return true;
-     *</pre>
+     * Called before the execution is passed to {@link Template#template.process(Object, java.io.Writer)}. This is a
+     * generic hook you might use in subclasses to perform a specific action before the template is processed.
      *
-     * @param request the actual HTTP request
-     * @param response the actual HTTP response
-     * @param template the template that will get executed
-     * @param data the data that will be passed to the template. By default this will be
-     *        an {@link AllHttpScopesHashModel} (which is a {@link freemarker.template.SimpleHash} subclass).
-     *        Thus, you can add new variables to the data-model with the
-     *        {@link freemarker.template.SimpleHash#put(String, Object)} subclass) method.
+     * @param request
+     *            The HTTP request that we will response to.
+     * @param response
+     *            The HTTP response. The HTTP headers are already initialized here, such as the {@code conteType} and
+     *            the {@code responseCharacterEncoding} are already set, but you can do the final adjustments here. The
+     *            response {@linker Writer} isn't created yet, so changing HTTP headers and buffering parameters works.
+     * @param template
+     *            The template that will get executed
+     * @param model
+     *            The data model that will be passed to the template. By default this will be an
+     *            {@link AllHttpScopesHashModel} (which is a {@link freemarker.template.SimpleHash} subclass). Thus, you
+     *            can add new variables to the data-model with the
+     *            {@link freemarker.template.SimpleHash#put(String, Object)} subclass) method. However, to adjust the
+     *            data-model, overriding
+     *            {@link #createModel(ObjectWrapper, ServletContext, HttpServletRequest, HttpServletResponse)} is
+     *            probably a more appropriate place.
+     * 
      * @return true to process the template, false to suppress template processing.
      */
     protected boolean preTemplateProcess(
         HttpServletRequest request,
         HttpServletResponse response,
         Template template,
-        TemplateModel data)
+        TemplateModel model)
         throws ServletException, IOException {
         return true;
     }
 
     /**
-     * Called after the execution returns from template.process().
+     * Called after the execution returns from {@link Template#process(Object, java.io.Writer)}.
      * This is a generic hook you might use in subclasses to perform a specific
      * action after the template is processed. It will be invoked even if the
      * template processing throws an exception. By default does nothing.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/5d1a411a/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index 1654417..05e2738 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -10415,6 +10415,11 @@ TemplateHashModel roundingModeEnums =
           <programlisting role="unspecified">&lt;servlet&gt;
   &lt;servlet-name&gt;freemarker&lt;/servlet-name&gt;
   &lt;servlet-class&gt;<emphasis>freemarker.ext.servlet.FreemarkerServlet</emphasis>&lt;/servlet-class&gt;
+  
+  &lt;!--
+    Init-param documentation:
+    <link xlink:href="http://freemarker.org/docs/api/freemarker/ext/servlet/FreemarkerServlet.html">http://freemarker.org/docs/api/freemarker/ext/servlet/FreemarkerServlet.html</link>
+  --&gt;
     
   &lt;!-- FreemarkerServlet settings: --&gt;
   &lt;init-param&gt;
@@ -10434,22 +10439,22 @@ TemplateHashModel roundingModeEnums =
     &lt;param-value&gt;UTF-8&lt;/param-value&gt;
   &lt;/init-param&gt;
     
-  &lt;!-- FreeMarker settings: --&gt;
+  &lt;!-- FreeMarker engine settings: --&gt;
   &lt;init-param&gt;
     &lt;param-name&gt;incompatible_improvements&lt;/param-name&gt;
-    &lt;param-value&gt;2.3.22&lt;/param-value&gt;
-    &lt;!-- Recommended to set to a high value. For the details, see the Java API docs of
-         freemarker.template.Configuration#Configuration(Version). --&gt;
+    &lt;param-value&gt;2.3.24&lt;/param-value&gt;
+    &lt;!-- Recommended to set to a high value. See:
+         http://freemarker.org/docs/pgui_config_incompatible_improvements.html
   &lt;/init-param&gt;
   &lt;init-param&gt;
     &lt;param-name&gt;template_exception_handler&lt;/param-name&gt;
-    &lt;!-- Use "html_debug" instead during development! --&gt;
+    &lt;!-- Use "html_debug" during development! --&gt;
     &lt;param-value&gt;rethrow&lt;/param-value&gt;
   &lt;/init-param&gt;
   &lt;init-param&gt;
     &lt;param-name&gt;template_update_delay&lt;/param-name&gt;
-    &lt;!-- ATTENTION, 0 is for development only! Use higher value otherwise. --&gt;
-    &lt;param-value&gt;0&lt;/param-value&gt;
+    &lt;!-- Use 0 during development! Consider what value you need otherwise. --&gt;
+    &lt;param-value&gt;30 s&lt;/param-value&gt;
   &lt;/init-param&gt;
   &lt;init-param&gt;
     &lt;param-name&gt;default_encoding&lt;/param-name&gt;


[09/25] incubator-freemarker git commit: Bug fixed: It wasn't well defined when a Java Iterator counts as empty. Depending on what ObjectWrapper you are using, one of these fixes apply:

Posted by dd...@apache.org.
Bug fixed: It wasn't well defined when a Java Iterator counts as empty. Depending on what ObjectWrapper you are using, one of these fixes apply:

(a) DefaultObjectWrapper (fix is always active): Operations on the Iterator that only check if it's empty without reading an element from it, such as ?has_content, won't cause the a later iteration (or further emptiness check) to fail anymore. Earlier, in certain situations, the second operation has failed saying that the iterator can be listed only once.

(b) BeansWrapper (when it's not extended by DefaultObjectWrapper), if it's incompatibleImprovements property is set to 2.3.24 (or higher): Iterator-s were always said to be non-empty when using ?has_content and such (i.e., operators that check emptiness without reading any elements). Now an Iterator counts as empty exactly if it has no elements left. (Note that this bug has never affected basic functionality, like <#list ...>.)


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

Branch: refs/heads/2.3
Commit: eef30af2fa1b61be9ad553f92362e5ff72a15779
Parents: fca65a2
Author: ddekany <dd...@apache.org>
Authored: Sun Oct 25 00:00:14 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sun Oct 25 00:02:24 2015 +0200

----------------------------------------------------------------------
 .../java/freemarker/ext/beans/BeanModel.java    |   4 +
 .../java/freemarker/ext/beans/BeansWrapper.java |  19 ++-
 .../java/freemarker/template/Configuration.java |  11 ++
 .../template/DefaultIteratorAdapter.java        |  15 +-
 .../template/DefaultObjectWrapper.java          |   5 +
 .../freemarker/template/SimpleCollection.java   |  24 +--
 src/manual/book.xml                             |  34 +++++
 .../freemarker/core/IteratorIssuesTest.java     | 149 +++++++++++++++++++
 .../template/DefaultObjectWrapperTest.java      |   2 +-
 9 files changed, 242 insertions(+), 21 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eef30af2/src/main/java/freemarker/ext/beans/BeanModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/beans/BeanModel.java b/src/main/java/freemarker/ext/beans/BeanModel.java
index 26eb1f5..794a66b 100644
--- a/src/main/java/freemarker/ext/beans/BeanModel.java
+++ b/src/main/java/freemarker/ext/beans/BeanModel.java
@@ -27,6 +27,7 @@ import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -296,6 +297,9 @@ implements
         if (object instanceof Collection) {
             return ((Collection) object).isEmpty();
         }
+        if (object instanceof Iterator && wrapper.is2324Bugfixed()) {
+            return !((Iterator) object).hasNext();
+        }
         if (object instanceof Map) {
             return ((Map) object).isEmpty();
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eef30af2/src/main/java/freemarker/ext/beans/BeansWrapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapper.java b/src/main/java/freemarker/ext/beans/BeansWrapper.java
index b21bdeb..c809fe7 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapper.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapper.java
@@ -249,6 +249,13 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
      *       like in Java), which is hence never the one with the {@code Object} parameter type. For more details
      *       about overloaded method selection changes see the version history in the FreeMarker Manual.
      *     </li>
+     *     <li>
+     *       <p>2.3.24 (or higher):
+     *       {@link Iterator}-s were always said to be non-empty when using {@code ?has_content} and such (i.e.,
+     *       operators that check emptiness without reading any elements). Now an {@link Iterator} counts as
+     *       empty exactly if it has no elements left. (Note that this bug has never affected basic functionality, like
+     *       {@code <#list ...>}.) 
+     *     </li>  
      *   </ul>
      *   
      *   <p>Note that the version will be normalized to the lowest version where the same incompatible
@@ -793,6 +800,14 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
     static boolean is2321Bugfixed(Version version) {
         return version.intValue() >= _TemplateAPI.VERSION_INT_2_3_21;
     }
+
+    boolean is2324Bugfixed() {
+        return is2324Bugfixed(getIncompatibleImprovements());
+    }
+
+    static boolean is2324Bugfixed(Version version) {
+        return version.intValue() >= _TemplateAPI.VERSION_INT_2_3_24;
+    }
     
     /** 
      * Returns the lowest version number that is equivalent with the parameter version.
@@ -803,7 +818,9 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
         if (incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_0) {
             throw new IllegalArgumentException("Version must be at least 2.3.0.");
         }
-        return is2321Bugfixed(incompatibleImprovements) ? Configuration.VERSION_2_3_21 : Configuration.VERSION_2_3_0;
+        return is2324Bugfixed(incompatibleImprovements) ? Configuration.VERSION_2_3_24
+                : is2321Bugfixed(incompatibleImprovements) ? Configuration.VERSION_2_3_21
+                : Configuration.VERSION_2_3_0;
     }
     
     /**

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eef30af2/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 fe178dc..c04f37a 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -756,6 +756,17 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      *          (not <code>${...}</code>-s in general), like in <code>&lt;#assign s="Hello ${name}!"&gt;</code>, has
      *          always used {@code incompatbileImprovement}-s 0 (2.3.0 in effect). Now it's fixed.
      *       </li>
+     *       <li><p>
+     *          {@link DefaultObjectWrapper} has some minor changes with {@code incompatibleImprovements} 2.3.24;
+     *          check them out at {@link DefaultObjectWrapper#DefaultObjectWrapper(Version)}. It's important to know
+     *          that if you set the {@code object_wrapper} setting (to an other value than {@code "default"}), rather
+     *          than leaving it on its default value, the {@code object_wrapper} won't inherit the
+     *          {@code incompatibleImprovements} of the {@link Configuration}. In that case, if you want the 2.3.24
+     *          improvements of {@link DefaultObjectWrapper}, you have to set it in the {@link DefaultObjectWrapper}
+     *          object itself too! (Note that it's OK to use a {@link DefaultObjectWrapper} with a different
+     *          {@code incompatibleImprovements} version number than that of the {@link Configuration}, if that's
+     *          really what you want.)
+     *       </li>
      *     </ul>
      *   </li>
      * </ul>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eef30af2/src/main/java/freemarker/template/DefaultIteratorAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/DefaultIteratorAdapter.java b/src/main/java/freemarker/template/DefaultIteratorAdapter.java
index 6ddbbcc..a99d5b7 100644
--- a/src/main/java/freemarker/template/DefaultIteratorAdapter.java
+++ b/src/main/java/freemarker/template/DefaultIteratorAdapter.java
@@ -45,7 +45,7 @@ public class DefaultIteratorAdapter extends WrappingTemplateModel implements Tem
 
     @SuppressFBWarnings(value="SE_BAD_FIELD", justification="We hope it's Seralizable")
     private final Iterator iterator;
-    private boolean iteratorOwned;
+    private boolean iteratorOwnedBySomeone;
 
     /**
      * Factory method for creating new adapter instances.
@@ -83,7 +83,9 @@ public class DefaultIteratorAdapter extends WrappingTemplateModel implements Tem
 
         public TemplateModel next() throws TemplateModelException {
             if (!iteratorOwnedByMe) {
-                takeIteratorOwnership();
+                checkNotOwner();
+                iteratorOwnedBySomeone = true;
+                iteratorOwnedByMe = true;
             }
 
             if (!iterator.hasNext()) {
@@ -97,19 +99,16 @@ public class DefaultIteratorAdapter extends WrappingTemplateModel implements Tem
         public boolean hasNext() throws TemplateModelException {
             // Calling hasNext may looks safe, but I have met sync. problems.
             if (!iteratorOwnedByMe) {
-                takeIteratorOwnership();
+                checkNotOwner();
             }
 
             return iterator.hasNext();
         }
 
-        private void takeIteratorOwnership() throws TemplateModelException {
-            if (iteratorOwned) {
+        private void checkNotOwner() throws TemplateModelException {
+            if (iteratorOwnedBySomeone) {
                 throw new TemplateModelException(
                         "This collection value wraps a java.util.Iterator, thus it can be listed only once.");
-            } else {
-                iteratorOwned = true;
-                iteratorOwnedByMe = true;
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eef30af2/src/main/java/freemarker/template/DefaultObjectWrapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/DefaultObjectWrapper.java b/src/main/java/freemarker/template/DefaultObjectWrapper.java
index 4bcf9bf..bda6f32 100644
--- a/src/main/java/freemarker/template/DefaultObjectWrapper.java
+++ b/src/main/java/freemarker/template/DefaultObjectWrapper.java
@@ -89,6 +89,11 @@ public class DefaultObjectWrapper extends freemarker.ext.beans.BeansWrapper {
      *              <li>2.3.22 (or higher): The default value of
      *                  {@link #setUseAdaptersForContainers(boolean) useAdaptersForContainers} changes to
      *                  {@code true}.</li>
+     *              <li>2.3.24 (or higher): When wrapping an {@link Iterator}, operations on it that only check if the
+     *                  collection is empty without reading an element from it, such as {@code ?has_content},
+     *                  won't cause the a later iteration (or further emptiness check) to fail anymore. Earlier, in
+     *                  certain situations, the second operation has failed saying that the iterator "can be listed only
+     *                  once".  
      *            </ul>
      * 
      * @since 2.3.21

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eef30af2/src/main/java/freemarker/template/SimpleCollection.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/SimpleCollection.java b/src/main/java/freemarker/template/SimpleCollection.java
index baa5677..77f9755 100644
--- a/src/main/java/freemarker/template/SimpleCollection.java
+++ b/src/main/java/freemarker/template/SimpleCollection.java
@@ -112,7 +112,11 @@ implements TemplateCollectionModel, Serializable {
 
         public TemplateModel next() throws TemplateModelException {
             if (!iteratorOwnedByMe) { 
-                takeIteratorOwnership();
+                synchronized (SimpleCollection.this) {
+                    checkIteratorOwned();
+                    iteratorOwned = true;
+                    iteratorOwnedByMe = true;
+                }
             }
             
             if (!iterator.hasNext()) {
@@ -126,23 +130,21 @@ implements TemplateCollectionModel, Serializable {
         public boolean hasNext() throws TemplateModelException {
             // Calling hasNext may looks safe, but I have met sync. problems.
             if (!iteratorOwnedByMe) {
-                takeIteratorOwnership();
+                synchronized (SimpleCollection.this) {
+                    checkIteratorOwned();
+                }
             }
             
             return iterator.hasNext();
         }
         
-        private void takeIteratorOwnership() throws TemplateModelException {
-            synchronized (SimpleCollection.this) {
-                if (iteratorOwned) {
-                    throw new TemplateModelException(
-                            "This collection value wraps a java.util.Iterator, thus it can be listed only once.");
-                } else {
-                    iteratorOwned = true;
-                    iteratorOwnedByMe = true;
-                }
+        private void checkIteratorOwned() throws TemplateModelException {
+            if (iteratorOwned) {
+                throw new TemplateModelException(
+                        "This collection value wraps a java.util.Iterator, thus it can be listed only once.");
             }
         }
+        
     }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eef30af2/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index 8030ffb..82f89ac 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -26160,6 +26160,40 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para>Bug fixed: It wasn't well defined when a Java
+              <literal>Iterator</literal> counts as empty. Depending on what
+              <literal>ObjectWrapper</literal> you are using, one of these
+              fixes apply:</para>
+
+              <itemizedlist>
+                <listitem>
+                  <para><literal>DefaultObjectWrapper</literal> (fix is always
+                  active): Operations on the <literal>Iterator</literal> that
+                  only check if it's empty without reading an element from it,
+                  such as <literal>?has_content</literal>, won't cause the a
+                  later iteration (or further emptiness check) to fail
+                  anymore. Earlier, in certain situations, the second
+                  operation has failed saying that the iterator <quote>can be
+                  listed only once</quote>.</para>
+                </listitem>
+
+                <listitem>
+                  <para><literal>BeansWrapper</literal> (when it's not
+                  extended by <literal>DefaultObjectWrapper</literal>), if
+                  it's <literal>incompatibleImprovements</literal> property is
+                  set to 2.3.24 (or higher): <literal>Iterator</literal>-s
+                  were always said to be non-empty when using
+                  <literal>?has_content</literal> and such (i.e., operators
+                  that check emptiness without reading any elements). Now an
+                  <literal>Iterator</literal> counts as empty exactly if it
+                  has no elements left. (Note that this bug has never affected
+                  basic functionality, like <literal>&lt;#list
+                  ...&gt;</literal>.)</para>
+                </listitem>
+              </itemizedlist>
+            </listitem>
+
+            <listitem>
               <para>Bug fixed: The (rarely used) cause exception of
               <literal>ParseException</literal>-s wasn't set</para>
             </listitem>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eef30af2/src/test/java/freemarker/core/IteratorIssuesTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/IteratorIssuesTest.java b/src/test/java/freemarker/core/IteratorIssuesTest.java
new file mode 100644
index 0000000..78de020
--- /dev/null
+++ b/src/test/java/freemarker/core/IteratorIssuesTest.java
@@ -0,0 +1,149 @@
+package freemarker.core;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+import org.junit.Test;
+
+import freemarker.ext.beans.BeansWrapper;
+import freemarker.ext.beans.BeansWrapperBuilder;
+import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.DefaultObjectWrapperBuilder;
+import freemarker.test.TemplateTest;
+
+public class IteratorIssuesTest extends TemplateTest {
+    
+    private static final String FTL_HAS_CONTENT_AND_LIST
+            = "<#if it?hasContent><#list it as i>${i}</#list><#else>empty</#if>";
+    private static final String OUT_HAS_CONTENT_AND_LIST_ABC = "abc";
+    private static final String OUT_HAS_CONTENT_AND_LIST_EMPTY = "empty";
+    
+    private static final String FTL_LIST_AND_HAS_CONTENT
+            = "<#list it as i>${i}${it?hasContent?then('+', '-')}</#list>";
+    private static final String OUT_LIST_AND_HAS_CONTENT_BW_WRONG = "a+b+c+";
+    private static final String OUT_LIST_AND_HAS_CONTENT_BW_GOOD = "a+b+c-";
+
+    @Test
+    public void testHasContentAndListDOW230() throws Exception {
+        addToDataModel("it", getDOW230().wrap(getAbcIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_ABC);
+        
+        addToDataModel("it", getDOW230().wrap(getEmptyIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_EMPTY);
+    }
+
+    @Test
+    public void testHasContentAndListDOW2323() throws Exception {
+        addToDataModel("it", getDOW2323().wrap(getAbcIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_ABC);
+        
+        addToDataModel("it", getDOW2323().wrap(getEmptyIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_EMPTY);
+    }
+
+    @Test
+    public void testHasContentAndListDOW2324() throws Exception {
+        addToDataModel("it", getDOW2324().wrap(getAbcIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_ABC);
+        
+        addToDataModel("it", getDOW2324().wrap(getEmptyIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_EMPTY);
+    }
+
+    @Test
+    public void testHasContentAndListBW230() throws Exception {
+        addToDataModel("it", getBW230().wrap(getAbcIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_ABC);
+        
+        addToDataModel("it", getBW230().wrap(getEmptyIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, "");
+    }
+    
+    @Test
+    public void testHasContentAndListBW2323() throws Exception {
+        addToDataModel("it", getBW2323().wrap(getAbcIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_ABC);
+        
+        addToDataModel("it", getBW230().wrap(getEmptyIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, "");
+    }
+    
+    @Test
+    public void testHasContentAndListBW2324() throws Exception {
+        addToDataModel("it", getBW2324().wrap(getAbcIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_ABC);
+        
+        addToDataModel("it", getBW2324().wrap(getEmptyIt()));
+        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_EMPTY);
+    }
+    
+    @Test
+    public void testListAndHasContentDOW230() throws Exception {
+        addToDataModel("it", getDOW230().wrap(getAbcIt()));
+        assertErrorContains(FTL_LIST_AND_HAS_CONTENT, "can be listed only once");
+    }
+
+    @Test
+    public void testListAndHasContentDOW2323() throws Exception {
+        addToDataModel("it", getDOW2323().wrap(getAbcIt()));
+        assertErrorContains(FTL_LIST_AND_HAS_CONTENT, "can be listed only once");
+    }
+
+    @Test
+    public void testListAndHasContentDOW2324() throws Exception {
+        addToDataModel("it", getDOW2324().wrap(getAbcIt()));
+        assertErrorContains(FTL_LIST_AND_HAS_CONTENT, "can be listed only once");
+    }
+
+    @Test
+    public void testListAndHasContentBW230() throws Exception {
+        addToDataModel("it", getBW230().wrap(getAbcIt()));
+        assertOutput(FTL_LIST_AND_HAS_CONTENT, OUT_LIST_AND_HAS_CONTENT_BW_WRONG);
+    }
+
+    @Test
+    public void testListAndHasContentBW2323() throws Exception {
+        addToDataModel("it", getBW2323().wrap(getAbcIt()));
+        assertOutput(FTL_LIST_AND_HAS_CONTENT, OUT_LIST_AND_HAS_CONTENT_BW_WRONG);
+    }
+
+    @Test
+    public void testListAndHasContentBW2324() throws Exception {
+        addToDataModel("it", getBW2324().wrap(getAbcIt()));
+        assertOutput(FTL_LIST_AND_HAS_CONTENT, OUT_LIST_AND_HAS_CONTENT_BW_GOOD);
+    }
+    
+    private Iterator getAbcIt() {
+        return Arrays.asList(new String[] { "a", "b", "c" }).iterator();
+    }
+
+    private Iterator getEmptyIt() {
+        return Arrays.asList(new String[] {  }).iterator();
+    }
+    
+    private DefaultObjectWrapper getDOW230() {
+        return new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_0).build();
+    }
+    
+    private DefaultObjectWrapper getDOW2323() {
+        return new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_23).build();
+    }
+    
+    private DefaultObjectWrapper getDOW2324() {
+        return new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_24).build();
+    }
+
+    private BeansWrapper getBW230() {
+        return new BeansWrapperBuilder(Configuration.VERSION_2_3_0).build();
+    }
+
+    private BeansWrapper getBW2323() {
+        return new BeansWrapperBuilder(Configuration.VERSION_2_3_23).build();
+    }
+
+    private BeansWrapper getBW2324() {
+        return new BeansWrapperBuilder(Configuration.VERSION_2_3_24).build();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/eef30af2/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
index b398965..f94f2d9 100644
--- a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
+++ b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
@@ -82,7 +82,7 @@ public class DefaultObjectWrapperTest {
         expected.add(Configuration.VERSION_2_3_21);
         expected.add(Configuration.VERSION_2_3_22);
         expected.add(Configuration.VERSION_2_3_22); // no non-BC change in 2.3.23
-        expected.add(Configuration.VERSION_2_3_22); // no non-BC change in 2.3.24
+        expected.add(Configuration.VERSION_2_3_24);
 
         List<Version> actual = new ArrayList<Version>();
         for (int i = _TemplateAPI.VERSION_INT_2_3_0; i <= Configuration.getVersion().intValue(); i++) {


[20/25] incubator-freemarker git commit: (Minor ResponseCharacterEncoding documentation adjustments.)

Posted by dd...@apache.org.
(Minor ResponseCharacterEncoding documentation adjustments.)


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

Branch: refs/heads/2.3
Commit: 52a618eb5e0ee95f524dd8d68e97a08a1037e1f2
Parents: 43fd973
Author: ddekany <dd...@apache.org>
Authored: Mon Nov 23 01:37:15 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Mon Nov 23 01:37:15 2015 +0100

----------------------------------------------------------------------
 .../freemarker/ext/servlet/FreemarkerServlet.java   | 10 ++++++----
 src/manual/book.xml                                 | 16 +++++++++-------
 2 files changed, 15 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a618eb/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index ae48af6..d745010 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -161,8 +161,8 @@ import freemarker.template.utility.StringUtil;
  * the {@value #INIT_PARAM_RESPONSE_CHARACTER_ENCODING} init-param is set to {@value #INIT_PARAM_VALUE_LEGACY} (which is
  * the default of it), the content type may include the charset (as in <tt>"text/html; charset=utf-8"</tt>), in which
  * case that specifies the actual charset of the output. If the the {@value #INIT_PARAM_RESPONSE_CHARACTER_ENCODING}
- * init-param is not set to {@value #INIT_PARAM_VALUE_LEGACY}, then specifying the charset in the content type is not
- * allowed, and will cause servlet initialization error.
+ * init-param is not set to {@value #INIT_PARAM_VALUE_LEGACY}, then specifying the charset in the
+ * {@value #INIT_PARAM_CONTENT_TYPE} init-param is not allowed, and will cause servlet initialization error.
  * </li>
  *
  * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> (since 2.3.24): Specifies when we should
@@ -195,7 +195,7 @@ import freemarker.template.utility.StringUtil;
  * or writes it (though very few applications utilize that setting anyway). Also, it sets the charset of the servlet
  * response by adding it to the response content type via calling {@link HttpServletResponse#setContentType(String)} (as
  * that was the only way before Servlet 2.4), not via the more modern
- * {@link HttpServletResponse#setCharacterEncoding(String)} method. Note that the charset of a template almost always
+ * {@link HttpServletResponse#setCharacterEncoding(String)} method. Note that the charset of a template usually
  * comes from {@link Configuration#getDefaultEncoding()} (i.e., from the {@code default_encoding} FreeMarker setting),
  * occasionally from {@link Configuration#getEncoding(Locale)} (when FreeMarker was configured to use different charsets
  * depending on the locale) or even more rarely from {@link Configuration#getTemplateConfigurers()} (when FreeMarker was
@@ -205,7 +205,9 @@ import freemarker.template.utility.StringUtil;
  * template usually just inherits that from the {@link Configuration}), and if that's not set, then reads the source
  * charset of the template, just like {@value #INIT_PARAM_VALUE_LEGACY}. Then it passes the charset acquired this way to
  * {@link HttpServletResponse#setCharacterEncoding(String)} and {@link Environment#setOutputEncoding(String)}. (It
- * doesn't call the legacy {@link HttpServletResponse#setContentType(String)} API to set the charset.)
+ * doesn't call the legacy {@link HttpServletResponse#setContentType(String)} API to set the charset.) (Note that if
+ * the template has a {@code content_type} template attribute (which is deprecated) that specifies a charset, it will be
+ * considered as the output charset of that template.)
  * <li>{@value #INIT_PARAM_VALUE_DO_NOT_SET}: {@link FreemarkerServlet} will not set the {@link HttpServletResponse}
  * "character encoding". It will still call {@link Environment#setOutputEncoding(String)}, so that the running template
  * will be aware of the charset used for the output.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a618eb/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index bf04cd9..c6f8033 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -26069,13 +26069,15 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
 
                 <listitem>
                   <para><literal>ResponseCharacterEncoding</literal>:
-                  Deprecates the old (and quirky) way of specifying the output
-                  charset, which was specifying it in the
-                  <literal>ContentType</literal>, or falling back to using the
-                  template file charset. The possible value are
-                  <literal>legacy</literal> (the default for backward
-                  compatibility), <literal>fromTemplate</literal> (which is
-                  <literal>legacy</literal> without quirks),
+                  Deprecates the old (and quirky) logic of specifying the
+                  output charset, which was putting it into the
+                  <literal>ContentType</literal> init-param after the MIME
+                  type, otherwise falling back to using the template file
+                  charset. The possible values are <literal>legacy</literal>
+                  (the default for backward compatibility),
+                  <literal>fromTemplate</literal> (which is
+                  <literal>legacy</literal> without quirks, and is aware of
+                  the <literal>outputEncoding</literal> setting),
                   <literal>doNotSet</literal> (keeps what the caller has
                   already set in the <literal>ServletRespone</literal>) and
                   <literal>force


[24/25] incubator-freemarker git commit: Reverted earlier change; FreemarkerServlet ResponseCharacterEncoding init-param uses "force" prefix again. Updated FreemarkerServlet configuration example to use FreeMarker's output_encoding setting, also added th

Posted by dd...@apache.org.
Reverted earlier change; FreemarkerServlet ResponseCharacterEncoding init-param uses "force" prefix again. Updated FreemarkerServlet configuration example to use FreeMarker's output_encoding setting, also added the ftlh and fltx extensions to it. Some other minor JavaDoc adjustments.


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

Branch: refs/heads/2.3
Commit: 116b817543c369dbc91aae09ea84d20040ef26f5
Parents: 5d1a411
Author: ddekany <dd...@apache.org>
Authored: Sat Nov 28 22:47:47 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sat Nov 28 22:47:47 2015 +0100

----------------------------------------------------------------------
 .../ext/servlet/FreemarkerServlet.java          | 77 ++++++--------------
 src/manual/book.xml                             | 35 ++++++---
 .../ext/servlet/FreemarkerServletTest.java      | 12 +--
 3 files changed, 55 insertions(+), 69 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/116b8175/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index a7fc2e6..6ad1040 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -20,9 +20,8 @@
 package freemarker.ext.servlet;
 
 import java.io.IOException;
+import java.io.Writer;
 import java.nio.charset.Charset;
-import java.nio.charset.IllegalCharsetNameException;
-import java.nio.charset.UnsupportedCharsetException;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -47,7 +46,6 @@ import freemarker.cache.FileTemplateLoader;
 import freemarker.cache.MultiTemplateLoader;
 import freemarker.cache.TemplateLoader;
 import freemarker.cache.WebappTemplateLoader;
-import freemarker.core.BugException;
 import freemarker.core.Configurable;
 import freemarker.core.Environment;
 import freemarker.core.OutputFormat;
@@ -214,10 +212,10 @@ import freemarker.template.utility.StringUtil;
  * <li>{@value #INIT_PARAM_VALUE_DO_NOT_SET}: {@link FreemarkerServlet} will not set the {@link HttpServletResponse}
  * "character encoding". It will still call {@link Environment#setOutputEncoding(String)}, so that the running template
  * will be aware of the charset used for the output.
- * <li>A charset name, for example {@code UTF-8}: The output charset will the specified charset, regardless of
- * everything. The charset specified this way is passed to {@link HttpServletResponse#setCharacterEncoding(String)} and
- * {@link Environment#setOutputEncoding(String)}. If the charset name is not recognized by Java, the servlet
- * initialization will fail.
+ * <li>{@value #INIT_PARAM_VALUE_FORCE_PREFIX} + charset name, for example {@code force UTF-8}: The output charset will
+ * be the one specified after "force" + space, regardless of everything. The charset specified this way is passed to
+ * {@link HttpServletResponse#setCharacterEncoding(String)} and {@link Environment#setOutputEncoding(String)}. If the
+ * charset name is not recognized by Java, the servlet initialization will fail.
  * </ul>
  *
  * <li><strong>{@value #INIT_PARAM_BUFFER_SIZE}</strong>: Sets the size of the output buffer in bytes, or if "KB" or
@@ -266,7 +264,8 @@ import freemarker.template.utility.StringUtil;
  * as {@code object_wrapper}.
  * 
  * <li><strong>Any other init-params</strong> will be interpreted as {@link Configuration}-level FreeMarker setting. See
- * the possible names and values at {@link Configuration#setSetting(String, String)}.</li>
+ * the possible names and values at {@link Configuration#setSetting(String, String)}. Note that these init-param names
+ * are starting with lower-case letter (upper-case init-params are used for FreemarkerSerlvet settings).</li>
  * 
  * </ul>
  * 
@@ -420,6 +419,7 @@ public class FreemarkerServlet extends HttpServlet {
     public static final String INIT_PARAM_VALUE_FROM_TEMPLATE = "fromTemplate";
     public static final String INIT_PARAM_VALUE_LEGACY = "legacy";
     public static final String INIT_PARAM_VALUE_DO_NOT_SET = "doNotSet";
+    public static final String INIT_PARAM_VALUE_FORCE_PREFIX = "force ";
 
     /**
      * When set, the items defined in it will be added after those coming from the
@@ -670,20 +670,9 @@ public class FreemarkerServlet extends HttpServlet {
                     overrideResponseContentType = initParamValueToEnum(value, OverrideResponseContentType.values());
                 } else if (name.equals(INIT_PARAM_RESPONSE_CHARACTER_ENCODING)) {
                     responseCharacterEncoding = initParamValueToEnum(value, ResponseCharacterEncoding.values());
-                    if (responseCharacterEncoding == ResponseCharacterEncoding.SPECIFIC_CHARSET) {
-                        try {
-                            forcedResponseCharacterEncoding = Charset.forName(value);
-                        } catch (IllegalArgumentException e) {
-                            if (!(e instanceof UnsupportedCharsetException
-                                    || e instanceof IllegalCharsetNameException)) {
-                                throw e;
-                            }
-                            StringBuilder sb = new StringBuilder();
-                            sb.append(StringUtil.jQuote(value));
-                            sb.append(" isn't a valid charset name, nor it is any of the predefined values: ");
-                            appendEnumInitParamValues(sb, ResponseCharacterEncoding.values());
-                            throw new IllegalArgumentException(sb.toString());
-                        }
+                    if (responseCharacterEncoding == ResponseCharacterEncoding.FORCE_CHARSET) {
+                        String charsetName = value.substring(INIT_PARAM_VALUE_FORCE_PREFIX.length()).trim();
+                        forcedResponseCharacterEncoding = Charset.forName(charsetName);
                     }
                 } else if (name.equals(INIT_PARAM_OVERRIDE_RESPONSE_LOCALE)) {
                     overrideResponseLocale = initParamValueToEnum(value, OverrideResponseLocale.values());
@@ -859,7 +848,7 @@ public class FreemarkerServlet extends HttpServlet {
         if (responseCharacterEncoding != ResponseCharacterEncoding.LEGACY
                 && responseCharacterEncoding != ResponseCharacterEncoding.DO_NOT_SET) {
             // Using the Servlet 2.4 way of setting character encoding.
-            if (responseCharacterEncoding != ResponseCharacterEncoding.SPECIFIC_CHARSET) {
+            if (responseCharacterEncoding != ResponseCharacterEncoding.FORCE_CHARSET) {
                 if (!tempSpecContentTypeContainsCharset) {
                     response.setCharacterEncoding(getTemplateSpecificOutputEncoding(template));
                 }
@@ -1005,10 +994,6 @@ public class FreemarkerServlet extends HttpServlet {
         return config.getLocale();
     }
 
-    /**
-     * Creates the data-model that will be passed to {@link Template#process(Object, java.io.Writer)}.
-     * The default implementation in {@link FreemarkerServlet} returns a {@link AllHttpScopesHashModel}.
-     */
     protected TemplateModel createModel(ObjectWrapper objectWrapper,
                                         ServletContext servletContext,
                                         final HttpServletRequest request,
@@ -1415,7 +1400,7 @@ public class FreemarkerServlet extends HttpServlet {
     }
 
     /**
-     * Called before the execution is passed to {@link Template#template.process(Object, java.io.Writer)}. This is a
+     * Called before the execution is passed to {@link Template#process(Object, java.io.Writer)}. This is a
      * generic hook you might use in subclasses to perform a specific action before the template is processed.
      *
      * @param request
@@ -1423,7 +1408,7 @@ public class FreemarkerServlet extends HttpServlet {
      * @param response
      *            The HTTP response. The HTTP headers are already initialized here, such as the {@code conteType} and
      *            the {@code responseCharacterEncoding} are already set, but you can do the final adjustments here. The
-     *            response {@linker Writer} isn't created yet, so changing HTTP headers and buffering parameters works.
+     *            response {@link Writer} isn't created yet, so changing HTTP headers and buffering parameters works.
      * @param template
      *            The template that will get executed
      * @param model
@@ -1606,42 +1591,28 @@ public class FreemarkerServlet extends HttpServlet {
     }
     
     private <T extends InitParamValueEnum> T initParamValueToEnum(String initParamValue, T[] enumValues) {
-        T wildcardEnumValue = null;
         for (T enumValue : enumValues) {
             String enumInitParamValue = enumValue.getInitParamValue();
-            if (enumInitParamValue == null) {
-                if (wildcardEnumValue != null) {
-                    throw new BugException();
-                }
-                wildcardEnumValue = enumValue;
-            } else if (initParamValue.equals(enumInitParamValue)) {
+            if (initParamValue.equals(enumInitParamValue)
+                    || enumInitParamValue.endsWith("}") && initParamValue.startsWith(
+                            enumInitParamValue.substring(0, enumInitParamValue.indexOf("${")))) {
                 return enumValue;
             }
         }
-        if (wildcardEnumValue != null) {
-            return wildcardEnumValue;
-        }
         
         StringBuilder sb = new StringBuilder();
         sb.append(StringUtil.jQuote(initParamValue));
         sb.append(" is not a one of the enumeration values: ");
-        appendEnumInitParamValues(sb, enumValues);
-        throw new IllegalArgumentException(sb.toString());
-    }
-
-    protected <T extends InitParamValueEnum> void appendEnumInitParamValues(StringBuilder sb, T[] enumValues) {
         boolean first = true;
         for (T value : enumValues) {
-            String initParamValue = value.getInitParamValue();
-            if (initParamValue != null) {  // Not a wildcard enum
-                if (!first) {
-                    sb.append(", ");
-                } else {
-                    first = false;
-                }
-                sb.append(StringUtil.jQuote(initParamValue));
+            if (!first) {
+                sb.append(", ");
+            } else {
+                first = false;
             }
+            sb.append(StringUtil.jQuote(value.getInitParamValue()));
         }
+        throw new IllegalArgumentException(sb.toString());
     }
 
     /**
@@ -1674,7 +1645,7 @@ public class FreemarkerServlet extends HttpServlet {
         LEGACY(INIT_PARAM_VALUE_LEGACY),
         FROM_TEMPLATE(INIT_PARAM_VALUE_FROM_TEMPLATE),
         DO_NOT_SET(INIT_PARAM_VALUE_DO_NOT_SET),
-        SPECIFIC_CHARSET(null);
+        FORCE_CHARSET(INIT_PARAM_VALUE_FORCE_PREFIX + "${charsetName}");
 
         private final String initParamValue;
         

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/116b8175/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index 05e2738..a35f8d2 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -10431,20 +10431,24 @@ TemplateHashModel roundingModeEnums =
     &lt;param-value&gt;true&lt;/param-value&gt;
   &lt;/init-param&gt;
   &lt;init-param&gt;
-    &lt;param-name&gt;ContentType&lt;/param-name&gt;
-    &lt;param-value&gt;text/html&lt;/param-value&gt;
+    &lt;param-name&gt;ResponseCharacterEncoding&lt;/param-name&gt;
+    &lt;!-- Use the output_encoding setting of FreeMarker: --&gt;
+    &lt;param-value&gt;fromTemplate&lt;/param-value&gt;
   &lt;/init-param&gt;
   &lt;init-param&gt;
-    &lt;param-name&gt;ResponseCharacterEncoding&lt;/param-name&gt;
-    &lt;param-value&gt;UTF-8&lt;/param-value&gt;
+    &lt;param-name&gt;ExceptionOnMissingTemplate&lt;/param-name&gt;
+    &lt;!-- true =&gt; HTTP 500 on missing template, instead of HTTP 404. --&gt;
+    &lt;param-value&gt;true&lt;/param-value&gt;
   &lt;/init-param&gt;
-    
+
   &lt;!-- FreeMarker engine settings: --&gt;
   &lt;init-param&gt;
     &lt;param-name&gt;incompatible_improvements&lt;/param-name&gt;
     &lt;param-value&gt;2.3.24&lt;/param-value&gt;
-    &lt;!-- Recommended to set to a high value. See:
-         http://freemarker.org/docs/pgui_config_incompatible_improvements.html
+    &lt;!--
+      Recommended to set to a high value.
+      See: http://freemarker.org/docs/pgui_config_incompatible_improvements.html
+    --&gt;
   &lt;/init-param&gt;
   &lt;init-param&gt;
     &lt;param-name&gt;template_exception_handler&lt;/param-name&gt;
@@ -10458,7 +10462,13 @@ TemplateHashModel roundingModeEnums =
   &lt;/init-param&gt;
   &lt;init-param&gt;
     &lt;param-name&gt;default_encoding&lt;/param-name&gt;
-    &lt;!-- The encoding of the template files. --&gt;
+    &lt;!-- The encoding of the template files: --&gt;
+    &lt;param-value&gt;UTF-8&lt;/param-value&gt;
+  &lt;/init-param&gt;
+  &lt;init-param&gt;
+    &lt;param-name&gt;output_encoding&lt;/param-name&gt;
+    &lt;!-- The encoding of the template output; Note that you must set
+         "ResponseCharacterEncodring" to "fromTemplate" for this to work! --&gt;
     &lt;param-value&gt;UTF-8&lt;/param-value&gt;
   &lt;/init-param&gt;
   &lt;init-param&gt;
@@ -10477,6 +10487,9 @@ TemplateHashModel roundingModeEnums =
 &lt;servlet-mapping&gt;
   &lt;servlet-name&gt;freemarker&lt;/servlet-name&gt;
   &lt;url-pattern&gt;<emphasis>*.ftl</emphasis>&lt;/url-pattern&gt;
+  &lt;!-- HTML and XML auto-escaped if incompatible_improvements &gt;= 2.3.24: --&gt;
+  &lt;url-pattern&gt;<emphasis>*.ftlh</emphasis>&lt;/url-pattern&gt;
+  &lt;url-pattern&gt;<emphasis>*.ftl</emphasis>x&lt;/url-pattern&gt;
 &lt;/servlet-mapping&gt;
 
 <replaceable>...</replaceable>
@@ -10490,6 +10503,8 @@ TemplateHashModel roundingModeEnums =
   &lt;web-resource-collection&gt;
     &lt;web-resource-name&gt;FreeMarker MVC Views&lt;/web-resource-name&gt;
     &lt;url-pattern&gt;*.ftl&lt;/url-pattern&gt;
+    &lt;url-pattern&gt;*.ftlh&lt;/url-pattern&gt;
+    &lt;url-pattern&gt;*.ftlx&lt;/url-pattern&gt;
   &lt;/web-resource-collection&gt;
   &lt;auth-constraint&gt;
     &lt;!-- Nobody is allowed to visit these directly. --&gt;
@@ -26089,8 +26104,8 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
                   the <literal>outputEncoding</literal> setting),
                   <literal>doNotSet</literal> (keeps what the caller has
                   already set in the <literal>ServletRespone</literal>) and
-                  any valid charset name (forces a specific output
-                  charset).</para>
+                  <literal>force</literal> followed by a charset name (forces
+                  a specific output charset).</para>
                 </listitem>
               </itemizedlist>
             </listitem>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/116b8175/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
index 6e5ce09..f008280 100644
--- a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
+++ b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
@@ -310,23 +310,23 @@ public class FreemarkerServletTest {
         assertOutputEncodingEquals(
                 "UTF-16LE", // <- expected response.characterEncoding
                 "UTF-16LE", // <- expected env.outputEncoding
-                "UTF-16LE", // <- init-param
+                FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
                 FOO_FTL);
         assertOutputEncodingEquals(
                 "UTF-16LE", // <- expected response.characterEncoding
                 "UTF-16LE", // <- expected env.outputEncoding
-                "UTF-16LE", // <- init-param
+                FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
                 FOO_SRC_UTF8_FTL);
         assertOutputEncodingEquals(
                 "UTF-16LE", // <- expected response.characterEncoding
                 "UTF-16LE", // <- expected env.outputEncoding
-                "UTF-16LE", // <- init-param
+                FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
                 FOO_OUT_UTF8_FTL);
         try {
             assertOutputEncodingEquals(
                     null, // <- expected response.characterEncoding
                     null, // <- expected env.outputEncoding
-                    "noSuchCharset", // <- init-param
+                    FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "noSuchCharset", // <- init-param
                     FOO_FTL);
             fail();
         } catch (ServletException e) {
@@ -337,7 +337,7 @@ public class FreemarkerServletTest {
             assertOutputEncodingEquals(
                     "UTF-16LE", // <- expected response.characterEncoding
                     "UTF-16LE", // <- expected env.outputEncoding
-                    "UTF-16LE", // <- init-param
+                    FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
                     "text/html; charset=ISO-8859-2", // ContentType init-param
                     FOO_FTL);
             fail();
@@ -348,7 +348,7 @@ public class FreemarkerServletTest {
         assertOutputEncodingEquals(
                 "UTF-16LE", // <- expected response.characterEncoding
                 "UTF-16LE", // <- expected env.outputEncoding
-                "UTF-16LE", // <- init-param
+                FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
                 CONTENT_TYPE_ATTR_WITH_CHARSET_FTL);
     }
 


[03/25] incubator-freemarker git commit: Continued FREEMARKER-1 "Option to not to overwrite response ContentType in FreemarkerServlet": Always set response Content-type header when it's null.

Posted by dd...@apache.org.
Continued FREEMARKER-1 "Option to not to overwrite response ContentType in FreemarkerServlet": Always set response Content-type header when it's null.


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

Branch: refs/heads/2.3
Commit: fe08488da50e12dfcb8a5c600417a391df218d26
Parents: 3ab293a
Author: ddekany <dd...@apache.org>
Authored: Sat Oct 17 19:07:08 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sat Oct 17 19:07:08 2015 +0200

----------------------------------------------------------------------
 .../ext/servlet/FreemarkerServlet.java          | 59 ++++++++++----------
 .../ext/servlet/FreemarkerServletTest.java      | 19 +++++++
 2 files changed, 49 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fe08488d/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index 776a884..929bde4 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -88,9 +88,9 @@ import freemarker.template.utility.StringUtil;
  * <code>&lt;#assign tiles=JspTaglibs["/WEB-INF/struts-tiles.tld"]&gt;</code>.
  * 
  * <li>A custom directive named {@code include_page} allows you to include the output of another servlet resource from
- * your servlet container, just as if you used {@code ServletRequest.getRequestDispatcher(path).include()}:
- * {@code <@include_page path="/myWebapp/somePage.jsp"/>}. You can also pass parameters to the newly included page by
- * passing a hash named {@code params}:
+ * your servlet container, just as if you used {@code ServletRequest.getRequestDispatcher(path).include()}: {@code 
+ * <@include_page path="/myWebapp/somePage.jsp"/>}. You can also pass parameters to the newly included page by passing a
+ * hash named {@code params}:
  * <code>&lt;@include_page path="/myWebapp/somePage.jsp" params= lang: "en", q="5"}/&gt;</code>. By default, the request
  * parameters of the original request (the one being processed by FreemarkerServlet) are also inherited by the include.
  * You can explicitly control this inheritance using the {@code inherit_params} parameter:
@@ -116,15 +116,15 @@ import freemarker.template.utility.StringUtil;
  * you want to load templates from the specified package accessible through the Thread Context Class Loader of the
  * thread that initializes this servlet.<br>
  * If {@code incompatible_improvements} is set to 2.3.22 (or higher), you can specify multiple comma separated locations
- * inside square brackets, like: {@code [ WEB-INF/templates, classpath:com/example/myapp/templates ]}.
- * This internally creates a {@link MultiTemplateLoader}. Note again that if {@code incompatible_improvements} isn't
- * set to at least 2.3.22, the initial {@code [} has no special meaning, and so this feature is unavailable.<br>
+ * inside square brackets, like: {@code [ WEB-INF/templates, classpath:com/example/myapp/templates ]}. This internally
+ * creates a {@link MultiTemplateLoader}. Note again that if {@code incompatible_improvements} isn't set to at least
+ * 2.3.22, the initial {@code [} has no special meaning, and so this feature is unavailable.<br>
  * Any of the above can have a {@code ?setting(name=value, ...)} postfix to set the JavaBeans properties of the
  * {@link TemplateLoader} created. For example,
- * {@code /templates?settings(attemptFileAccess=false, URLConnectionUsesCaches=true)}
- * calls {@link WebappTemplateLoader#setAttemptFileAccess(boolean)}
- * and {@link WebappTemplateLoader#setURLConnectionUsesCaches(Boolean)} to tune the {@link WebappTemplateLoader}. 
- * For backward compatibility (not recommended!), you can use the {@code class://} prefix, like in
+ * {@code /templates?settings(attemptFileAccess=false, URLConnectionUsesCaches=true)} calls
+ * {@link WebappTemplateLoader#setAttemptFileAccess(boolean)} and
+ * {@link WebappTemplateLoader#setURLConnectionUsesCaches(Boolean)} to tune the {@link WebappTemplateLoader}. For
+ * backward compatibility (not recommended!), you can use the {@code class://} prefix, like in
  * <tt>class://com/example/templates</tt> format, which is similar to {@code classpath:}, except that it uses the
  * defining class loader of this servlet's class. This can cause template not found errors, if that class (in
  * {@code freemarer.jar} usually) is not local to the web application, while the templates are.<br>
@@ -134,17 +134,20 @@ import freemarker.template.utility.StringUtil;
  * <li><strong>{@value #INIT_PARAM_NO_CACHE}</strong>: If set to true, generates headers in the response that advise the
  * HTTP client not to cache the returned page. The default is <tt>false</tt>.</li>
  * 
- * <li><strong>{@value #INIT_PARAM_CONTENT_TYPE}</strong>: If specified, response uses the specified Content-type HTTP
- * header. The value may include the charset (e.g. <tt>"text/html; charset=ISO-8859-1"</tt>). If not specified,
- * <tt>"text/html"</tt> is used. If the charset is not specified in this init-param, then the charset (encoding) of the
- * actual template file will be used (in the response HTTP header and for encoding the output stream). Note that this
- * setting can be overridden on a per-template basis by specifying a custom attribute named <tt>content_type</tt> in the
- * <tt>attributes</tt> parameter of the <tt>&lt;#ftl&gt;</tt> directive.</li>
+ * <li><strong>{@value #INIT_PARAM_CONTENT_TYPE}</strong>: The Content-type HTTP header value used in the HTTP responses
+ * (unless <strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> is set to {@code false} and the response
+ * Content-type is already set by the time {@link FreemarkerServlet} is invoked). Defaults to <tt>"text/html"</tt>. The
+ * value may include the charset (e.g. <tt>"text/html; charset=ISO-8859-1"</tt>). If the charset is not specified in
+ * this init-param, then the charset (encoding) of the actual template file will be used (both in the response HTTP
+ * header and for encoding the output stream). Note that this setting can be overridden on a per-template basis by
+ * specifying a custom attribute named <tt>content_type</tt> in the <tt>attributes</tt> parameter of the
+ * <tt>&lt;#ftl&gt;</tt> directive.</li>
  *
- * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> (since 2.4.0): If set to true, overrides the ContentType
- * of the response by using either <strong>{@value #INIT_PARAM_CONTENT_TYPE}</strong> parameter setting or other
- * information (see the description of <strong>{@value #INIT_PARAM_CONTENT_TYPE}</strong> for detail). The default
- * value is <tt>false</tt>.</li>
+ * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> (since 2.3.24): If set to {@code true}
+ * (which is the default), the Content-type HTTP header of the response is always set to the value of the
+ * <strong>{@value #INIT_PARAM_CONTENT_TYPE}</strong> setting. If set to {@code false}, {@link FreemarkerServlet} will
+ * only set the Content-type HTTP response header if it isn't already set when {@link FreemarkerServlet} is invoked.
+ * Thus, {@code false} allows you to specify the Content-type before forwarding to {@link FreemarkerServlet}.</li>
  *
  * <li><strong>{@value #INIT_PARAM_BUFFER_SIZE}</strong>: Sets the size of the output buffer in bytes, or if "KB" or
  * "MB" is written after the number (like {@code <param-value>256 KB</param-value>}) then in kilobytes or megabytes.
@@ -162,9 +165,9 @@ import freemarker.template.utility.StringUtil;
  * 
  * <li><strong>{@value #INIT_PARAM_META_INF_TLD_LOCATIONS}</strong> (since 2.3.22): Comma separated list of items, each
  * is either {@value #META_INF_TLD_LOCATION_WEB_INF_PER_LIB_JARS}, or {@value #META_INF_TLD_LOCATION_CLASSPATH}
- * optionally followed by colon and a regular expression, or {@value #META_INF_TLD_LOCATION_CLEAR}. For example
- * {@code <param-value>classpath:.*myoverride.*\.jar$, webInfPerLibJars, classpath:.*taglib.*\.jar$</param-value>}, or
- * {@code <param-value>classpath</param-value>}. (Whitespace around the commas and list items will be ignored.) See
+ * optionally followed by colon and a regular expression, or {@value #META_INF_TLD_LOCATION_CLEAR}. For example {@code 
+ * <param-value>classpath:.*myoverride.*\.jar$, webInfPerLibJars, classpath:.*taglib.*\.jar$</param-value>}, or {@code 
+ * <param-value>classpath</param-value>}. (Whitespace around the commas and list items will be ignored.) See
  * {@link TaglibFactory#setMetaInfTldSources(List)} for more information. Defaults to a list that contains
  * {@value #META_INF_TLD_LOCATION_WEB_INF_PER_LIB_JARS} only (can be overridden with
  * {@link #createDefaultMetaInfTldSources()}). Note that this can be also specified with the
@@ -230,12 +233,10 @@ import freemarker.template.utility.StringUtil;
  * the servlet container (with the proper cause exception). After all, if the visited URL had an associated "action" but
  * the template behind it is missing, that's an internal server error, not a wrong URL.</li>
  * 
- * <li>
- * If the template contains parsing errors, it will log it with error level, then the servlet throws
+ * <li>If the template contains parsing errors, it will log it with error level, then the servlet throws
  * {@link ServletException} to the servlet container (with the proper cause exception).</li>
  * 
- * <li>
- * If the template throws exception during its execution, and the value of the {@code template_exception_handler}
+ * <li>If the template throws exception during its execution, and the value of the {@code template_exception_handler}
  * init-param is {@code rethrow} (recommended), it will log it with error level and then the servlet throws
  * {@link ServletException} to the servlet container (with the proper cause exception). But beware, the default value of
  * the {@code template_exception_handler} init-param is {@code html_debug}, which is for development only! Set it to
@@ -596,7 +597,7 @@ public class FreemarkerServlet extends HttpServlet {
         noCharsetInContentType = true;
         int i = contentType.toLowerCase().indexOf("charset=");
         if (i != -1) {
-            char c = ' ';
+            char c = 0;
             i--;
             while (i >= 0) {
                 c = contentType.charAt(i);
@@ -724,7 +725,7 @@ public class FreemarkerServlet extends HttpServlet {
                     "Unexpected error when loading template " + StringUtil.jQuoteNoXSS(templatePath) + ".", e);
         }
 
-        if (overrideResponseContentType) {
+        if (overrideResponseContentType || response.getContentType() == null) {
             Object attrContentType = template.getCustomAttribute("content_type");
             if (attrContentType != null) {
                 response.setContentType(attrContentType.toString());

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fe08488d/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
index 9690638..bf2ef8a 100644
--- a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
+++ b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
@@ -111,6 +111,25 @@ public class FreemarkerServletTest {
         freemarkerServlet.destroy();
     }
 
+    @Test
+    public void testContentTypeInitParams_withNoResponseContentType_NoOverriding() throws ServletException, IOException {
+        servletConfig.addInitParameter(FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false");
+        freemarkerServlet = new FreemarkerServlet();
+        freemarkerServlet.init(servletConfig);
+
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/foo.ftl");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        assertNull(response.getContentType());
+
+        freemarkerServlet.doGet(request, response);
+        LOG.debug("response content: " + response.getContentAsString());
+
+        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+        assertTrue(response.getContentType().contains(FreemarkerServlet.DEFAULT_CONTENT_TYPE));
+
+        freemarkerServlet.destroy();
+    }
+
     private MockHttpServletRequest createMockHttpServletRequest(final ServletContext servletContext,
             final String pathInfo) {
         MockHttpServletRequest servletRequest = new MockHttpServletRequest(servletContext);


[17/25] incubator-freemarker git commit: (JavaDoc typo)

Posted by dd...@apache.org.
(JavaDoc typo)


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

Branch: refs/heads/2.3
Commit: e96460cb9e17d224f326398235cd6761842dceb5
Parents: 0b6f0d2
Author: ddekany <dd...@apache.org>
Authored: Sun Nov 15 21:51:51 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sun Nov 15 21:51:51 2015 +0100

----------------------------------------------------------------------
 src/main/java/freemarker/core/Environment.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e96460cb/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 358c371..8b9a0cc 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -931,7 +931,7 @@ public final class Environment extends Configurable {
 
     /**
      * Returns the name of the charset that should be used for URL encoding. This will be <code>null</code> if the
-     * information is not available. The function caches the return value, so it's quick to call it repeately.
+     * information is not available. The function caches the return value, so it's quick to call it repeatedly.
      */
     String getEffectiveURLEscapingCharset() {
         if (!cachedURLEscapingCharsetSet) {


[18/25] incubator-freemarker git commit: Added new init-param, ResponseCharacterEncoding: Deprecates the old (and quirky) way of specifying the output charset, which was specifying it in the ContentType, or falling back to using the template file charset

Posted by dd...@apache.org.
Added new init-param, ResponseCharacterEncoding: Deprecates the old (and quirky) way of specifying the output charset, which was specifying it in the ContentType, or falling back to using the template file charset. The possible value are legacy (the default for backward compatibility), fromTemplate (which is legacy without quirks), doNotSet (keeps what the caller has already set in the ServletRespone) and force charsetName (forces a specific charset).

Along the way, also made FreemarkerServletTest-s easier to write.


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

Branch: refs/heads/2.3
Commit: f64aa1f0d6f071e858b2ff41a325dca722735579
Parents: e96460c
Author: ddekany <dd...@apache.org>
Authored: Sun Nov 15 21:53:19 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sun Nov 15 21:53:19 2015 +0100

----------------------------------------------------------------------
 .../ext/servlet/FreemarkerServlet.java          | 114 +++++-
 src/manual/book.xml                             |  20 +-
 .../ext/servlet/FreemarkerServletTest.java      | 396 +++++++++++++++----
 .../freemarker/ext/servlet/contentTypeAttr.ftl  |  20 -
 .../resources/freemarker/ext/servlet/foo.ftl    |  19 -
 .../ext/servlet/outputFormatHeader.ftl          |  20 -
 6 files changed, 434 insertions(+), 155 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f64aa1f0/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index 464be23..695ac8d 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -20,6 +20,7 @@
 package freemarker.ext.servlet;
 
 import java.io.IOException;
+import java.nio.charset.Charset;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -45,6 +46,7 @@ import freemarker.cache.MultiTemplateLoader;
 import freemarker.cache.TemplateLoader;
 import freemarker.cache.WebappTemplateLoader;
 import freemarker.core.Configurable;
+import freemarker.core.Environment;
 import freemarker.core.OutputFormat;
 import freemarker.core.UndefinedOutputFormat;
 import freemarker.ext.jsp.TaglibFactory;
@@ -143,9 +145,8 @@ import freemarker.template.utility.StringUtil;
  * <li>If the {@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE} init-param is {@value #INIT_PARAM_VALUE_NEVER} (the
  * default is {@value #INIT_PARAM_VALUE_ALWAYS}), then the value of {@link HttpServletResponse#getContentType()} is used
  * if that's non-{@code null}.
- * <li>The template's custom attribute name <tt>content_type</tt>, usually specified via the <tt>attributes</tt>
- * parameter of the <tt>&lt;#ftl&gt;</tt> directive. This is a legacy feature, deprecated by the {@link OutputFormat}
- * mechanism.
+ * <li>The template's <tt>content_type</tt> custom attribute, usually specified via the <tt>attributes</tt> parameter of
+ * the <tt>&lt;#ftl&gt;</tt> directive. This is a legacy feature, deprecated by the {@link OutputFormat} mechanism.
  * <li>The {@linkplain Template#getOutputFormat() output format of the template}, if that has non-{@code null} MIME-type
  * ({@link OutputFormat#getMimeType()}). When a template has no output format specified, {@link UndefinedOutputFormat}
  * is used, which has {@code null} MIME-type. (The output format of a template is deduced from {@link Configuration}
@@ -156,10 +157,13 @@ import freemarker.template.utility.StringUtil;
  * (the default is {@value #INIT_PARAM_VALUE_ALWAYS}), then the value of {@link HttpServletResponse#getContentType()} is
  * used if that's non-{@code null}.
  * </ol>
- * If none of the above gives a MIME type, then this init-param does. Defaults to <tt>"text/html"</tt>. The value may
- * include the charset (e.g. <tt>"text/html; charset=utf-8"</tt>). If the charset is not specified in this init-param,
- * then the charset (encoding) of the actual template file will be appended after it, which, as per the Servlet
- * specification, also sets the actual encoding used to write the response body.</li>
+ * If none of the above gives a MIME type, then this init-param does. Defaults to <tt>"text/html"</tt>. If and only if
+ * the {@value #INIT_PARAM_RESPONSE_CHARACTER_ENCODING} init-param is set to {@value #INIT_PARAM_VALUE_LEGACY} (which is
+ * the default of it), the content type may include the charset (as in <tt>"text/html; charset=utf-8"</tt>), in which
+ * case that specifies the actual charset of the output. If the the {@value #INIT_PARAM_RESPONSE_CHARACTER_ENCODING}
+ * init-param is not set to {@value #INIT_PARAM_VALUE_LEGACY}, then specifying the charset in the content type is not
+ * allowed, and will cause servlet initialization error.
+ * </li>
  *
  * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> (since 2.3.24): Specifies when we should
  * override the {@code contentType} that might be already set (i.e., non-{@code null}) in the
@@ -178,6 +182,38 @@ import freemarker.template.utility.StringUtil;
  * invoking {@link #deduceLocale(String, HttpServletRequest, HttpServletResponse)}. Another possible value is
  * {@value #INIT_PARAM_VALUE_NEVER}, which means that we don't deduce the template {@code locale}, unless
  * {@link HttpServletRequest#getLocale()} is {@code null}.
+ * 
+ * <li><strong>{@value #INIT_PARAM_RESPONSE_CHARACTER_ENCODING}</strong> (since 2.3.24): Specifies how the
+ * {@link HttpServletResponse} "character encoding" (as in {@link HttpServletResponse#setCharacterEncoding(String)})
+ * will deduced. The possible modes are:
+ * <ul>
+ * <li>{@value #INIT_PARAM_VALUE_LEGACY}: This is the default for backward compatibility; in new applications, use
+ * {@value #INIT_PARAM_VALUE_FROM_TEMPLATE} (or some of the other options) instead. {@value #INIT_PARAM_VALUE_LEGACY}
+ * will use the charset of the template file to set the charset of the servlet response. Except, if the
+ * {@value #INIT_PARAM_CONTENT_TYPE} init-param contains a charset, it will use that instead. A quirk of this legacy
+ * mode is that it's not aware of the {@link Configurable#getOutputEncoding()} FreeMarker setting, and thus never reads
+ * or writes it (though very few applications utilize that setting anyway). Also, it sets the charset of the servlet
+ * response by adding it to the response content type via calling {@link HttpServletResponse#setContentType(String)} (as
+ * that was the only way before Servlet 2.4), not via the more modern
+ * {@link HttpServletResponse#setCharacterEncoding(String)} method. Note that the charset of a template almost always
+ * comes from {@link Configuration#getDefaultEncoding()} (i.e., from the {@code default_encoding} FreeMarker setting),
+ * occasionally from {@link Configuration#getEncoding(Locale)} (when FreeMarker was configured to use different charsets
+ * depending on the locale) or even more rarely from {@link Configuration#getTemplateConfigurers()} (when FreeMarker was
+ * configured to use a specific charset for certain templates).
+ * <li>{@value #INIT_PARAM_VALUE_FROM_TEMPLATE}: This should be used in most applications, but it's not the default for
+ * backward compatibility. It reads the {@link Configurable#getOutputEncoding()} setting of the template (note that the
+ * template usually just inherits that from the {@link Configuration}), and if that's not set, then reads the source
+ * charset of the template, just like {@value #INIT_PARAM_VALUE_LEGACY}. Then it passes the charset acquired this way to
+ * {@link HttpServletResponse#setCharacterEncoding(String)} and {@link Environment#setOutputEncoding(String)}. (It
+ * doesn't call the legacy {@link HttpServletResponse#setContentType(String)} API to set the charset.)
+ * <li>{@value #INIT_PARAM_VALUE_DO_NOT_SET}: {@link FreemarkerServlet} will not set the {@link HttpServletResponse}
+ * "character encoding". It will still call {@link Environment#setOutputEncoding(String)}, so that the running template
+ * will be aware of the charset used for the output.
+ * <li>{@value #INIT_PARAM_VALUE_FORCE_PREFIX} + charset name, for example {@code force UTF-8}: The output charset will
+ * be the one specified after "force" + space, regardless of everything. The charset specified this way is passed to
+ * {@link HttpServletResponse#setCharacterEncoding(String)} and {@link Environment#setOutputEncoding(String)}. If the
+ * charset name is not recognized by Java, the servlet initialization will fail.
+ * </ul>
  *
  * <li><strong>{@value #INIT_PARAM_BUFFER_SIZE}</strong>: Sets the size of the output buffer in bytes, or if "KB" or
  * "MB" is written after the number (like {@code <param-value>256 KB</param-value>}) then in kilobytes or megabytes.
@@ -379,6 +415,7 @@ public class FreemarkerServlet extends HttpServlet {
     public static final String INIT_PARAM_VALUE_FROM_TEMPLATE = "fromTemplate";
     public static final String INIT_PARAM_VALUE_LEGACY = "legacy";
     public static final String INIT_PARAM_VALUE_DO_NOT_SET = "doNotSet";
+    public static final String INIT_PARAM_VALUE_FORCE_PREFIX = "force ";
 
     /**
      * When set, the items defined in it will be added after those coming from the
@@ -478,6 +515,7 @@ public class FreemarkerServlet extends HttpServlet {
     private OverrideResponseContentType overrideResponseContentType = initParamValueToEnum(
             getDefaultOverrideResponseContentType(), OverrideResponseContentType.values());
     private ResponseCharacterEncoding responseCharacterEncoding = ResponseCharacterEncoding.LEGACY;
+    private Charset forcedResponseCharacterEncoding;
     private boolean contentTypeContainsCharset;
     private OverrideResponseLocale overrideResponseLocale = OverrideResponseLocale.ALWAYS;
     private List/*<MetaInfTldSource>*/ metaInfTldSources;
@@ -628,6 +666,10 @@ public class FreemarkerServlet extends HttpServlet {
                     overrideResponseContentType = initParamValueToEnum(value, OverrideResponseContentType.values());
                 } else if (name.equals(INIT_PARAM_RESPONSE_CHARACTER_ENCODING)) {
                     responseCharacterEncoding = initParamValueToEnum(value, ResponseCharacterEncoding.values());
+                    if (responseCharacterEncoding == ResponseCharacterEncoding.FORCE_CHARSET) {
+                        String charsetName = value.substring(INIT_PARAM_VALUE_FORCE_PREFIX.length()).trim();
+                        forcedResponseCharacterEncoding = Charset.forName(charsetName);
+                    }
                 } else if (name.equals(INIT_PARAM_OVERRIDE_RESPONSE_LOCALE)) {
                     overrideResponseLocale = initParamValueToEnum(value, OverrideResponseLocale.values());
                 } else if (name.equals(INIT_PARAM_EXCEPTION_ON_MISSING_TEMPLATE)) {
@@ -652,6 +694,12 @@ public class FreemarkerServlet extends HttpServlet {
         } // while initpnames
         
         contentTypeContainsCharset = contentTypeContainsCharset(contentType);
+        if (contentTypeContainsCharset && responseCharacterEncoding != ResponseCharacterEncoding.LEGACY) {
+            throw new InitParamValueException(INIT_PARAM_CONTENT_TYPE, contentType,
+                    new IllegalStateException("You can't specify the charset in the content type, because the \"" +
+                            INIT_PARAM_RESPONSE_CHARACTER_ENCODING + "\" init-param isn't set to "
+                            + "\"" + INIT_PARAM_VALUE_LEGACY + "\"."));
+        }
     }
     
     private boolean contentTypeContainsCharset(String contentType) {
@@ -806,8 +854,11 @@ public class FreemarkerServlet extends HttpServlet {
         if (responseCharacterEncoding != ResponseCharacterEncoding.LEGACY
                 && responseCharacterEncoding != ResponseCharacterEncoding.DO_NOT_SET) {
             // Using the Servlet 2.4 way of setting character encoding.
-            response.setCharacterEncoding(getTemplateSpecificOutputEncoding(template));
-            // TODO handle "always ${charset}"
+            if (responseCharacterEncoding != ResponseCharacterEncoding.FORCE_CHARSET) {
+                response.setCharacterEncoding(getTemplateSpecificOutputEncoding(template));
+            } else {
+                response.setCharacterEncoding(forcedResponseCharacterEncoding.name());
+            }
         }
 
         setBrowserCachingPolicy(response);
@@ -822,7 +873,14 @@ public class FreemarkerServlet extends HttpServlet {
             if (preTemplateProcess(request, response, template, model)) {
                 try {
                     // Process the template
-                    template.process(model, response.getWriter());
+                    Environment env = template.createProcessingEnvironment(model, response.getWriter());
+                    if (responseCharacterEncoding != ResponseCharacterEncoding.LEGACY) {
+                        String actualOutputCharset = response.getCharacterEncoding();
+                        if (actualOutputCharset != null) {
+                            env.setOutputEncoding(actualOutputCharset);
+                        }
+                    }
+                    processEnvironment(env, request, response);
                 } finally {
                     // Give subclasses a chance to hook into postprocessing
                     postTemplateProcess(request, response, template, model);
@@ -839,6 +897,23 @@ public class FreemarkerServlet extends HttpServlet {
         }
     }
 
+    /**
+     * This is the method that actually executes the template. The original implementation coming from
+     * {@link FreemarkerServlet} simply calls {@link Environment#process()}. Overriding this method allows you to
+     * prepare the {@link Environment} before the execution, or extract information from the {@link Environment} after
+     * the execution. It also allows you to capture exceptions throw by the template.
+     * 
+     * @param env
+     *            The {@link Environment} object already set up to execute the template. You only have to call
+     *            {@link Environment#process()} and the output will be produced by the template.
+     * 
+     * @since 2.3.24
+     */
+    protected void processEnvironment(Environment env, HttpServletRequest request, HttpServletResponse response)
+            throws TemplateException, IOException {
+        env.process();
+    }
+
     private String getTemplateSpecificOutputEncoding(Template template) {
         String outputEncoding = responseCharacterEncoding == ResponseCharacterEncoding.LEGACY ? null
                 : template.getOutputEncoding();
@@ -856,7 +931,7 @@ public class FreemarkerServlet extends HttpServlet {
         String outputFormatMimeType = template.getOutputFormat().getMimeType();
         if (outputFormatMimeType != null) {
             if (responseCharacterEncoding == ResponseCharacterEncoding.LEGACY) {
-                // In legacy mode we won't call serlvetResponse.getCharacterEncoding(...), so:
+                // In legacy mode we won't call serlvetResponse.setCharacterEncoding(...), so:
                 outputFormatMimeType += "; charset=" + getTemplateSpecificOutputEncoding(template);
             }
             return outputFormatMimeType; 
@@ -1479,10 +1554,13 @@ public class FreemarkerServlet extends HttpServlet {
         
     }
     
-    private <T extends InitParamValueEnum> T initParamValueToEnum(String initParamValue, T[] values) {
-        for (T value : values) {
-            if (initParamValue.equals(value.getInitParamValue())) {
-                return value;
+    private <T extends InitParamValueEnum> T initParamValueToEnum(String initParamValue, T[] enumValues) {
+        for (T enumValue : enumValues) {
+            String enumInitParamValue = enumValue.getInitParamValue();
+            if (initParamValue.equals(enumInitParamValue)
+                    || enumInitParamValue.endsWith("}") && initParamValue.startsWith(
+                            enumInitParamValue.substring(0, enumInitParamValue.indexOf("${")))) {
+                return enumValue;
             }
         }
         
@@ -1490,7 +1568,7 @@ public class FreemarkerServlet extends HttpServlet {
         sb.append(StringUtil.jQuote(initParamValue));
         sb.append(" is not a one of the enumeration values: ");
         boolean first = true;
-        for (T value : values) {
+        for (T value : enumValues) {
             if (!first) {
                 sb.append(", ");
             } else {
@@ -1530,8 +1608,8 @@ public class FreemarkerServlet extends HttpServlet {
     private enum ResponseCharacterEncoding implements InitParamValueEnum {
         LEGACY(INIT_PARAM_VALUE_LEGACY),
         FROM_TEMPLATE(INIT_PARAM_VALUE_FROM_TEMPLATE),
-        DO_NOT_SET(INIT_PARAM_VALUE_DO_NOT_SET);
-        // TODO: "always ${charset}"
+        DO_NOT_SET(INIT_PARAM_VALUE_DO_NOT_SET),
+        FORCE_CHARSET(INIT_PARAM_VALUE_FORCE_PREFIX + "${charsetName}");
 
         private final String initParamValue;
         

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f64aa1f0/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index cab5f2c..bf04cd9 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -26046,7 +26046,7 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
 
               <itemizedlist>
                 <listitem>
-                  <para><literal>OverrideResponseContentType</literal>.
+                  <para><literal>OverrideResponseContentType</literal>:
                   Specifies when should we override the
                   <literal>contentType</literal> that's already set (i.e.,
                   non-<literal>null</literal>) in the
@@ -26059,13 +26059,29 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
                 </listitem>
 
                 <listitem>
-                  <para><literal>OverrideResponseLocale</literal>. Specifies
+                  <para><literal>OverrideResponseLocale</literal>: Specifies
                   if should we override the <literal>contentType</literal>
                   that's already set (i.e., non-<literal>null</literal>) in
                   the <literal>HttpServletResponse</literal>. Earlier, we have
                   always set it, but now this behavior can be changed so that
                   we only set it if it wasn't already set.</para>
                 </listitem>
+
+                <listitem>
+                  <para><literal>ResponseCharacterEncoding</literal>:
+                  Deprecates the old (and quirky) way of specifying the output
+                  charset, which was specifying it in the
+                  <literal>ContentType</literal>, or falling back to using the
+                  template file charset. The possible value are
+                  <literal>legacy</literal> (the default for backward
+                  compatibility), <literal>fromTemplate</literal> (which is
+                  <literal>legacy</literal> without quirks),
+                  <literal>doNotSet</literal> (keeps what the caller has
+                  already set in the <literal>ServletRespone</literal>) and
+                  <literal>force
+                  <replaceable>charsetName</replaceable></literal> (forces a
+                  specific charset).</para>
+                </listitem>
               </itemizedlist>
             </listitem>
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f64aa1f0/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
index fc81de9..b95cda5 100644
--- a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
+++ b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
@@ -19,9 +19,11 @@
 package freemarker.ext.servlet;
 
 import static freemarker.ext.servlet.FreemarkerServlet.*;
+import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
+import java.nio.charset.UnsupportedCharsetException;
 import java.util.Locale;
 
 import javax.servlet.ServletContext;
@@ -36,13 +38,29 @@ import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.mock.web.MockServletConfig;
 import org.springframework.mock.web.MockServletContext;
 
+import freemarker.cache.ConditionalTemplateConfigurerFactory;
+import freemarker.cache.FileNameGlobMatcher;
+import freemarker.cache.FirstMatchTemplateConfigurerFactory;
+import freemarker.cache.StringTemplateLoader;
+import freemarker.cache.TemplateLoader;
+import freemarker.core.Environment;
+import freemarker.core.TemplateConfigurer;
 import freemarker.template.Configuration;
 import freemarker.template.Template;
-import freemarker.template.TemplateModel;
+import freemarker.template.TemplateException;
 
 public class FreemarkerServletTest {
 
-    private static final String TEST_TEMPLATE_PATH = "classpath:freemarker/ext/servlet";
+    private static final String OUTPUT_FORMAT_HEADER_FTL = "outputFormatHeader.ftl";
+    private static final String CONTENT_TYPE_ATTR_FTL = "contentTypeAttr.ftl";
+    private static final String FOO_FTL = "foo.ftl";
+    private static final String FOO_SRC_UTF8_FTL = "foo-src-utf8.ftl";
+    private static final String FOO_OUT_UTF8_FTL = "foo-out-utf8.ftl";
+
+    private static final Locale DEFAULT_LOCALE = Locale.US;
+    private static final String CFG_DEFAULT_ENCODING = "US-ASCII";
+    /** According to the Servlet Specification */
+    private static final String SERVLET_RESPONSE_DEFAULT_CHARSET = "ISO-8859-1";
 
     private MockServletContext servletContext;
 
@@ -59,115 +77,245 @@ public class FreemarkerServletTest {
             assertResponseContentTypeEquals(
                     DEFAULT_CONTENT_TYPE + "; charset=UTF-8", // <- expected
                     null, overrideCT, // <- init-params
-                    "foo.ftl", null); // <- request
+                    FOO_FTL, null); // <- request
             assertResponseContentTypeEquals(
                     "text/css; charset=UTF-8", // <- expected
                     "text/css", overrideCT, // <- init-params
-                    "foo.ftl", null); // <- request
+                    FOO_FTL, null); // <- request
             assertResponseContentTypeEquals(
                     DEFAULT_CONTENT_TYPE + "; charset=UTF-8", // <- expected
                     null, overrideCT, // <- init-params
-                    "foo.ftl", "application/json"); // <- request
+                    FOO_FTL, "application/json"); // <- request
             assertResponseContentTypeEquals(
                     "text/css; charset=UTF-8", // <- expected
                     "text/css", overrideCT, // <- init-params
-                    "foo.ftl", "application/json"); // <- request
+                    FOO_FTL, "application/json"); // <- request
             assertResponseContentTypeEquals(
                     "text/plain", // <- expected
                     null, overrideCT, // <- init-params
-                    "contentTypeAttr.ftl", "application/json"); // <- request
+                    CONTENT_TYPE_ATTR_FTL, "application/json"); // <- request
             assertResponseContentTypeEquals(
                     "text/plain; charset=UTF-8", // <- expected
                     null, overrideCT, // <- init-params
-                    "outputFormatHeader.ftl", "application/json"); // <- request
+                    OUTPUT_FORMAT_HEADER_FTL, "application/json"); // <- request
         }
-        
+
         assertResponseContentTypeEquals(
                 DEFAULT_CONTENT_TYPE + "; charset=UTF-8", // <- expected
                 null, INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE, // <- init-params
-                "foo.ftl", null); // <- request
+                FOO_FTL, null); // <- request
         assertResponseContentTypeEquals(
                 "text/css; charset=UTF-8", // <- expected
                 "text/css", INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE, // <- init-params
-                "foo.ftl", null); // <- request        
+                FOO_FTL, null); // <- request
         assertResponseContentTypeEquals(
                 "application/json", // <- expected
                 null, INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE, // <- init-params
-                "foo.ftl", "application/json"); // <- request
+                FOO_FTL, "application/json"); // <- request
         assertResponseContentTypeEquals(
                 "application/json", // <- expected
                 "text/css", INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE, // <- init-params
-                "foo.ftl", "application/json"); // <- request
+                FOO_FTL, "application/json"); // <- request
         assertResponseContentTypeEquals(
                 "text/plain", // <- expected
                 null, INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE, // <- init-params
-                "contentTypeAttr.ftl", "application/json"); // <- request
+                CONTENT_TYPE_ATTR_FTL, "application/json"); // <- request
         assertResponseContentTypeEquals(
                 "text/plain; charset=UTF-8", // <- expected
                 null, INIT_PARAM_VALUE_WHEN_TEMPLATE_HAS_MIME_TYPE, // <- init-params
-                "outputFormatHeader.ftl", "application/json"); // <- request
-        
+                OUTPUT_FORMAT_HEADER_FTL, "application/json"); // <- request
+
         assertResponseContentTypeEquals(
                 DEFAULT_CONTENT_TYPE + "; charset=UTF-8", // <- expected
                 null, INIT_PARAM_VALUE_NEVER, // <- init-params
-                "foo.ftl", null); // <- request
+                FOO_FTL, null); // <- request
         assertResponseContentTypeEquals(
                 "text/css; charset=UTF-8", // <- expected
                 "text/css", INIT_PARAM_VALUE_NEVER, // <- init-params
-                "foo.ftl", null); // <- request        
+                FOO_FTL, null); // <- request
         assertResponseContentTypeEquals(
                 "application/json", // <- expected
                 null, INIT_PARAM_VALUE_NEVER, // <- init-params
-                "foo.ftl", "application/json"); // <- request
+                FOO_FTL, "application/json"); // <- request
         assertResponseContentTypeEquals(
                 "application/json", // <- expected
                 "text/css", INIT_PARAM_VALUE_NEVER, // <- init-params
-                "foo.ftl", "application/json"); // <- request
+                FOO_FTL, "application/json"); // <- request
         assertResponseContentTypeEquals(
                 "application/json", // <- expected
                 null, INIT_PARAM_VALUE_NEVER, // <- init-params
-                "contentTypeAttr.ftl", "application/json"); // <- request
+                CONTENT_TYPE_ATTR_FTL, "application/json"); // <- request
         assertResponseContentTypeEquals(
                 "application/json", // <- expected
                 null, INIT_PARAM_VALUE_NEVER, // <- init-params
-                "outputFormatHeader.ftl", "application/json"); // <- request
+                OUTPUT_FORMAT_HEADER_FTL, "application/json"); // <- request
     }
 
     @Test
     public void testResponseLocaleInitParams() throws Exception {
-        Locale prevDefaultLocale = Locale.getDefault();
-        Locale.setDefault(Locale.US);
+        assertTemplateLocaleEquals(
+                DEFAULT_LOCALE, // <- expected template locale
+                null, // <- request locale
+                null, // <- init-param
+                FOO_FTL);
+        assertTemplateLocaleEquals(
+                DEFAULT_LOCALE, // <- expected template locale
+                Locale.FRENCH, // <- request locale
+                null, // <- init-param
+                FOO_FTL);
+        assertTemplateLocaleEquals(
+                DEFAULT_LOCALE, // <- expected template locale
+                Locale.FRENCH, // <- request locale
+                INIT_PARAM_VALUE_ALWAYS, // <- init-param
+                FOO_FTL);
+        assertTemplateLocaleEquals(
+                DEFAULT_LOCALE, // <- expected template locale
+                null, // <- request locale
+                INIT_PARAM_VALUE_NEVER, // <- init-param
+                FOO_FTL);
+        assertTemplateLocaleEquals(
+                Locale.FRENCH, // <- expected template locale
+                Locale.FRENCH, // <- request locale
+                INIT_PARAM_VALUE_NEVER, // <- init-param
+                FOO_FTL);
+    }
+
+    @Test
+    public void testResponseOutputCharsetInitParam() throws Exception {
+        // Legacy mode is not aware of the outputEncoding, thus it doesn't set it:
+        assertOutputEncodingEquals(
+                CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
+                null, // <- expected env.outputEncoding
+                null, // <- init-param
+                FOO_FTL);
+        assertOutputEncodingEquals(
+                CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
+                null, // <- expected env.outputEncoding
+                FreemarkerServlet.INIT_PARAM_VALUE_LEGACY, // <- init-param
+                FOO_FTL);
+        // Legacy mode follows the source encoding of the template:
+        assertOutputEncodingEquals(
+                "UTF-8", // <- expected response.characterEncoding
+                null, // <- expected env.outputEncoding
+                null, // <- init-param
+                FOO_SRC_UTF8_FTL);
+        // Legacy mode doesn't deal with outputEncoding, but it's inherited by the Environment from the Template:
+        assertOutputEncodingEquals(
+                CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
+                "UTF-8", // <- expected env.outputEncoding
+                null, // <- init-param
+                FOO_OUT_UTF8_FTL);
+        // Charset in content type is the strongest:
+        assertOutputEncodingEquals(
+                "ISO-8859-2", // <- expected response.characterEncoding
+                null, // <- expected env.outputEncoding
+                null, // <- init-param
+                "text/html; charset=ISO-8859-2", // ContentType init-param
+                FOO_FTL);
+        assertOutputEncodingEquals(
+                "ISO-8859-2", // <- expected response.characterEncoding
+                null, // <- expected env.outputEncoding
+                null, // <- init-param
+                "text/html; charset=ISO-8859-2", // ContentType init-param
+                FOO_SRC_UTF8_FTL);
+        
+        // Non-legacy mode always keeps env.outputEncoding in sync. with the Servlet response encoding:
+        assertOutputEncodingEquals(
+                CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
+                CFG_DEFAULT_ENCODING, // <- expected env.outputEncoding
+                FreemarkerServlet.INIT_PARAM_VALUE_FROM_TEMPLATE, // <- init-param
+                FOO_FTL);
+        // Non-legacy mode considers the template-specific outputEncoding:
+        assertOutputEncodingEquals(
+                "UTF-8", // <- expected response.characterEncoding
+                "UTF-8", // <- expected env.outputEncoding
+                FreemarkerServlet.INIT_PARAM_VALUE_FROM_TEMPLATE, // <- init-param
+                FOO_OUT_UTF8_FTL);
+        // Non-legacy mode uses the template source encoding as a fallback for outputEncoding:
+        assertOutputEncodingEquals(
+                "UTF-8", // <- expected response.characterEncoding
+                "UTF-8", // <- expected env.outputEncoding
+                FreemarkerServlet.INIT_PARAM_VALUE_FROM_TEMPLATE, // <- init-param
+                FOO_SRC_UTF8_FTL);
+        // Not allowed to specify the charset in the contentType init-param: 
         try {
-            // By default, the Configurable.locale is set to Locale.getDefault().
-            final Locale defaultLocale = Locale.getDefault();
-    
-            assertTemplateLocaleEquals(
-                    defaultLocale, // <- expected template locale
-                    null, // <- request locale
-                    null, // <- init-param
-                    "foo.ftl");
-            assertTemplateLocaleEquals(
-                    defaultLocale, // <- expected template locale
-                    Locale.FRENCH, // <- request locale
-                    null, // <- init-param
-                    "foo.ftl");
-            assertTemplateLocaleEquals(
-                    defaultLocale, // <- expected template locale
-                    Locale.FRENCH, // <- request locale
-                    INIT_PARAM_VALUE_ALWAYS, // <- init-param
-                    "foo.ftl");
-            assertTemplateLocaleEquals(
-                    defaultLocale, // <- expected template locale
-                    null, // <- request locale
-                    INIT_PARAM_VALUE_NEVER, // <- init-param
-                    "foo.ftl");
-            assertTemplateLocaleEquals(
-                    Locale.FRENCH, // <- expected template locale
-                    Locale.FRENCH, // <- request locale
-                    INIT_PARAM_VALUE_NEVER, // <- init-param
-                    "foo.ftl");
-        } finally {
-            Locale.setDefault(prevDefaultLocale);
+            assertOutputEncodingEquals(
+                    null, // <- expected response.characterEncoding
+                    null, // <- expected env.outputEncoding
+                    FreemarkerServlet.INIT_PARAM_VALUE_FROM_TEMPLATE, // <- init-param
+                    "text/html; charset=ISO-8859-2", // ContentType init-param
+                    FOO_FTL);
+            fail();
+        } catch (ServletException e) {
+            assertThat(e.getCause().getCause().getMessage(), containsString(FreemarkerServlet.INIT_PARAM_VALUE_LEGACY));
+        }
+        
+        // Do not set mode:
+        assertOutputEncodingEquals(
+                SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected response.characterEncoding
+                SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected env.outputEncoding
+                FreemarkerServlet.INIT_PARAM_VALUE_DO_NOT_SET, // <- init-param
+                FOO_FTL);
+        assertOutputEncodingEquals(
+                SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected response.characterEncoding
+                SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected env.outputEncoding
+                FreemarkerServlet.INIT_PARAM_VALUE_DO_NOT_SET, // <- init-param
+                FOO_SRC_UTF8_FTL);
+        assertOutputEncodingEquals(
+                SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected response.characterEncoding
+                SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected env.outputEncoding
+                FreemarkerServlet.INIT_PARAM_VALUE_DO_NOT_SET, // <- init-param
+                FOO_OUT_UTF8_FTL);
+        // Not allowed to specify the charset in the contentType init-param: 
+        try {
+            assertOutputEncodingEquals(
+                    null, // <- expected response.characterEncoding
+                    null, // <- expected env.outputEncoding
+                    FreemarkerServlet.INIT_PARAM_VALUE_DO_NOT_SET, // <- init-param
+                    "text/html; charset=ISO-8859-2", // ContentType init-param
+                    FOO_FTL);
+            fail();
+        } catch (ServletException e) {
+            assertThat(e.getCause().getCause().getMessage(), containsString(FreemarkerServlet.INIT_PARAM_VALUE_LEGACY));
+        }
+        
+        // Forced mode:
+        assertOutputEncodingEquals(
+                "UTF-16LE", // <- expected response.characterEncoding
+                "UTF-16LE", // <- expected env.outputEncoding
+                FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
+                FOO_FTL);
+        assertOutputEncodingEquals(
+                "UTF-16LE", // <- expected response.characterEncoding
+                "UTF-16LE", // <- expected env.outputEncoding
+                FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
+                FOO_SRC_UTF8_FTL);
+        assertOutputEncodingEquals(
+                "UTF-16LE", // <- expected response.characterEncoding
+                "UTF-16LE", // <- expected env.outputEncoding
+                FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
+                FOO_OUT_UTF8_FTL);
+        try {
+            assertOutputEncodingEquals(
+                    null, // <- expected response.characterEncoding
+                    null, // <- expected env.outputEncoding
+                    FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "noSuchCharset", // <- init-param
+                    FOO_FTL);
+            fail();
+        } catch (ServletException e) {
+            assertThat(e.getCause().getCause(), instanceOf(UnsupportedCharsetException.class));
+        }
+        // Not allowed to specify the charset in the contentType init-param: 
+        try {
+            assertOutputEncodingEquals(
+                    null, // <- expected response.characterEncoding
+                    null, // <- expected env.outputEncoding
+                    FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
+                    "text/html; charset=ISO-8859-2", // ContentType init-param
+                    FOO_FTL);
+            fail();
+        } catch (ServletException e) {
+            assertThat(e.getCause().getCause().getMessage(), containsString(FreemarkerServlet.INIT_PARAM_VALUE_LEGACY));
         }
     }
 
@@ -177,7 +325,7 @@ public class FreemarkerServletTest {
             String templateName, String responseCT)
                     throws ServletException, IOException {
         MockHttpServletRequest request = createMockHttpServletRequest(servletContext, templateName, null);
-        
+
         MockHttpServletResponse response = new MockHttpServletResponse();
         if (responseCT != null) {
             response.setContentType(responseCT);
@@ -185,23 +333,22 @@ public class FreemarkerServletTest {
         } else {
             assertNull(response.getContentType());
         }
-    
+
         MockServletConfig servletConfig = new MockServletConfig(servletContext);
-        servletConfig.addInitParameter(INIT_PARAM_TEMPLATE_PATH, TEST_TEMPLATE_PATH);
         servletConfig.addInitParameter(Configuration.DEFAULT_ENCODING_KEY, "UTF-8");
         if (ctInitParam != null) {
-            servletConfig.addInitParameter(INIT_PARAM_CONTENT_TYPE, ctInitParam);            
+            servletConfig.addInitParameter(INIT_PARAM_CONTENT_TYPE, ctInitParam);
         }
         if (overrideCTInitParam != null) {
             servletConfig.addInitParameter(INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, overrideCTInitParam);
         }
-        
-        FreemarkerServlet freemarkerServlet = new FreemarkerServlet();
+
+        TestFreemarkerServlet freemarkerServlet = new TestFreemarkerServlet();
         try {
             freemarkerServlet.init(servletConfig);
-            
+
             freemarkerServlet.doGet(request, response);
-        
+
             assertEquals(HttpServletResponse.SC_OK, response.getStatus());
             assertEquals(exptectContentType, response.getContentType());
         } finally {
@@ -219,33 +366,65 @@ public class FreemarkerServletTest {
         MockHttpServletResponse response = new MockHttpServletResponse();
 
         MockServletConfig servletConfig = new MockServletConfig(servletContext);
-        servletConfig.addInitParameter(INIT_PARAM_TEMPLATE_PATH, TEST_TEMPLATE_PATH);
 
         if (overrideResponseLocaleInitParam != null) {
             servletConfig.addInitParameter(INIT_PARAM_OVERRIDE_RESPONSE_LOCALE, overrideResponseLocaleInitParam);
         }
 
-        final Template [] processedTemplateHolder = new Template[1];
+        TestFreemarkerServlet freemarkerServlet = new TestFreemarkerServlet();
 
-        FreemarkerServlet freemarkerServlet = new FreemarkerServlet() {
+        try {
+            freemarkerServlet.init(servletConfig);
+            freemarkerServlet.doGet(request, response);
 
-            @Override
-            protected void postTemplateProcess(
-                    HttpServletRequest request,
-                    HttpServletResponse response,
-                    Template template,
-                    TemplateModel data)
-                            throws ServletException, IOException {
-                processedTemplateHolder[0] = template;
-            }
-        };
+            assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+            assertEquals(exptectLocale, freemarkerServlet.lastLocale);
+            assertEquals(freemarkerServlet.lastLocale, freemarkerServlet.lastMainTemplate.getLocale());
+        } finally {
+            freemarkerServlet.destroy();
+        }
+    }
+
+    private void assertOutputEncodingEquals(
+            String exptectRespCharacterEncoding,
+            String exptectEnvOutputEncoding,
+            String responseCharacterEncodingInitParam,
+            String templateName) throws ServletException, IOException {
+        assertOutputEncodingEquals(
+                exptectRespCharacterEncoding, exptectEnvOutputEncoding,
+                responseCharacterEncodingInitParam, null,
+                templateName);
+    }
+    
+    private void assertOutputEncodingEquals(
+            String exptectRespCharacterEncoding,
+            String exptectEnvOutputEncoding,
+            String responseCharacterEncodingInitParam,
+            String contentTypeInitParam,
+            String templateName)
+                    throws ServletException, IOException {
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, templateName, null);
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        MockServletConfig servletConfig = new MockServletConfig(servletContext);
+
+        if (responseCharacterEncodingInitParam != null) {
+            servletConfig.addInitParameter(INIT_PARAM_RESPONSE_CHARACTER_ENCODING, responseCharacterEncodingInitParam);
+        }
+        
+        if (contentTypeInitParam != null) {
+            servletConfig.addInitParameter(INIT_PARAM_CONTENT_TYPE, contentTypeInitParam);
+        }
+
+        TestFreemarkerServlet freemarkerServlet = new TestFreemarkerServlet();
 
         try {
             freemarkerServlet.init(servletConfig);
             freemarkerServlet.doGet(request, response);
 
             assertEquals(HttpServletResponse.SC_OK, response.getStatus());
-            assertEquals(exptectLocale, processedTemplateHolder[0].getLocale());
+            assertEquals(exptectEnvOutputEncoding, freemarkerServlet.lastOutputEncoding);
+            assertEquals(exptectRespCharacterEncoding, response.getCharacterEncoding());
         } finally {
             freemarkerServlet.destroy();
         }
@@ -254,6 +433,7 @@ public class FreemarkerServletTest {
     private MockHttpServletRequest createMockHttpServletRequest(final ServletContext servletContext,
             final String pathInfo, final Locale requestLocale) {
         MockHttpServletRequest servletRequest = new MockHttpServletRequest(servletContext) {
+
             @Override
             public Locale getLocale() {
                 return requestLocale;
@@ -269,4 +449,68 @@ public class FreemarkerServletTest {
         return servletRequest;
     }
 
+    static class TestFreemarkerServlet extends FreemarkerServlet {
+
+        private Template lastMainTemplate;
+        private Locale lastLocale;
+        private String lastOutputEncoding;
+
+        @Override
+        protected Configuration createConfiguration() {
+            Configuration cfg = super.createConfiguration();
+            // Needed for the TemplateConfigurer that sets outputEncoding:
+            cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_22);
+
+            // Set a test runner environment independent default locale:
+            cfg.setLocale(DEFAULT_LOCALE);
+            cfg.setDefaultEncoding(CFG_DEFAULT_ENCODING);
+
+            {
+                TemplateConfigurer outUtf8TC = new TemplateConfigurer();
+                outUtf8TC.setOutputEncoding("UTF-8");
+                
+                TemplateConfigurer srcUtf8TC = new TemplateConfigurer();
+                srcUtf8TC.setEncoding("UTF-8");
+                
+                cfg.setTemplateConfigurers(
+                        new FirstMatchTemplateConfigurerFactory(
+                                new ConditionalTemplateConfigurerFactory(
+                                        new FileNameGlobMatcher(FOO_SRC_UTF8_FTL), srcUtf8TC),
+                                new ConditionalTemplateConfigurerFactory(
+                                        new FileNameGlobMatcher(FOO_OUT_UTF8_FTL), outUtf8TC)
+                        )
+                        .allowNoMatch(true)
+                );
+            }
+
+            return cfg;
+        }
+
+        @Override
+        protected TemplateLoader createTemplateLoader(String templatePath) throws IOException {
+            // Override default template loader
+            if (templatePath.equals("class://")) {
+                StringTemplateLoader tl = new StringTemplateLoader();
+                tl.putTemplate(FOO_FTL, "foo");
+                tl.putTemplate(FOO_SRC_UTF8_FTL, "foo");
+                tl.putTemplate(FOO_OUT_UTF8_FTL, "foo");
+                tl.putTemplate(CONTENT_TYPE_ATTR_FTL, "<#ftl attributes={ 'content_type': 'text/plain' }>foo");
+                tl.putTemplate(OUTPUT_FORMAT_HEADER_FTL, "<#ftl outputFormat='plainText'>foo");
+                return tl;
+            } else {
+                return super.createTemplateLoader(templatePath);
+            }
+        }
+
+        @Override
+        protected void processEnvironment(Environment env, HttpServletRequest request, HttpServletResponse response)
+                throws TemplateException, IOException {
+            lastMainTemplate = env.getMainTemplate();
+            lastLocale = env.getLocale();
+            lastOutputEncoding = env.getOutputEncoding();
+            super.processEnvironment(env, request, response);
+        }
+
+    }
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f64aa1f0/src/test/resources/freemarker/ext/servlet/contentTypeAttr.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/servlet/contentTypeAttr.ftl b/src/test/resources/freemarker/ext/servlet/contentTypeAttr.ftl
deleted file mode 100644
index 241f0b7..0000000
--- a/src/test/resources/freemarker/ext/servlet/contentTypeAttr.ftl
+++ /dev/null
@@ -1,20 +0,0 @@
-<#ftl attributes={ 'content_type': 'text/plain' }>
-<#--
-  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.
--->
-foo
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f64aa1f0/src/test/resources/freemarker/ext/servlet/foo.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/servlet/foo.ftl b/src/test/resources/freemarker/ext/servlet/foo.ftl
deleted file mode 100644
index a3aac20..0000000
--- a/src/test/resources/freemarker/ext/servlet/foo.ftl
+++ /dev/null
@@ -1,19 +0,0 @@
-<#--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-  
-  Unless required by applicable law or agreed to in writing,
-  software distributed under the License is distributed on an
-  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-  KIND, either express or implied.  See the License for the
-  specific language governing permissions and limitations
-  under the License.
--->
-foo
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f64aa1f0/src/test/resources/freemarker/ext/servlet/outputFormatHeader.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/servlet/outputFormatHeader.ftl b/src/test/resources/freemarker/ext/servlet/outputFormatHeader.ftl
deleted file mode 100644
index 27d7b2e..0000000
--- a/src/test/resources/freemarker/ext/servlet/outputFormatHeader.ftl
+++ /dev/null
@@ -1,20 +0,0 @@
-<#ftl outputFormat="plainText">
-<#--
-  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.
--->
-foo
\ No newline at end of file


[19/25] incubator-freemarker git commit: FreemarkerServlet ResponseCharacterEncoding init-param now works properly with the legacy content_type template attribute (usually specified in the #ftl tag)

Posted by dd...@apache.org.
FreemarkerServlet ResponseCharacterEncoding init-param now works properly with the legacy content_type template attribute (usually specified in the #ftl tag)


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

Branch: refs/heads/2.3
Commit: 43fd9737a8acdc33c1d5273439e5d6dcb041accc
Parents: f64aa1f
Author: ddekany <dd...@apache.org>
Authored: Sat Nov 21 12:41:22 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sat Nov 21 12:41:22 2015 +0100

----------------------------------------------------------------------
 .../ext/servlet/FreemarkerServlet.java          | 104 +++++++++++------
 .../ext/servlet/FreemarkerServletTest.java      | 114 ++++++++++++-------
 2 files changed, 142 insertions(+), 76 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/43fd9737/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index 695ac8d..ae48af6 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -407,7 +407,7 @@ public class FreemarkerServlet extends HttpServlet {
     private static final String DEPR_INITPARAM_TEMPLATE_EXCEPTION_HANDLER_IGNORE = "ignore";
     private static final String DEPR_INITPARAM_DEBUG = "debug";
     
-    static final String DEFAULT_CONTENT_TYPE = "text/html";
+    private static final ContentType DEFAULT_CONTENT_TYPE = new ContentType("text/html");
     
     public static final String INIT_PARAM_VALUE_NEVER = "never";
     public static final String INIT_PARAM_VALUE_ALWAYS = "always";
@@ -511,12 +511,12 @@ public class FreemarkerServlet extends HttpServlet {
     private Configuration config;
     @SuppressFBWarnings(value="SE_BAD_FIELD", justification="Not investing into making this Servlet serializable")
     private ObjectWrapper wrapper;
-    private String contentType;
+    private ContentType contentType;
     private OverrideResponseContentType overrideResponseContentType = initParamValueToEnum(
             getDefaultOverrideResponseContentType(), OverrideResponseContentType.values());
     private ResponseCharacterEncoding responseCharacterEncoding = ResponseCharacterEncoding.LEGACY;
+    @SuppressFBWarnings(value="SE_BAD_FIELD", justification="Not investing into making this Servlet serializable")
     private Charset forcedResponseCharacterEncoding;
-    private boolean contentTypeContainsCharset;
     private OverrideResponseLocale overrideResponseLocale = OverrideResponseLocale.ALWAYS;
     private List/*<MetaInfTldSource>*/ metaInfTldSources;
     private List/*<String>*/ classpathTlds;
@@ -661,7 +661,7 @@ public class FreemarkerServlet extends HttpServlet {
                 } else if (name.equals(INIT_PARAM_DEBUG)) {
                     debug = StringUtil.getYesNo(value);
                 } else if (name.equals(INIT_PARAM_CONTENT_TYPE)) {
-                    contentType = value;
+                    contentType = new ContentType(value);
                 } else if (name.equals(INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE)) {
                     overrideResponseContentType = initParamValueToEnum(value, OverrideResponseContentType.values());
                 } else if (name.equals(INIT_PARAM_RESPONSE_CHARACTER_ENCODING)) {
@@ -693,32 +693,14 @@ public class FreemarkerServlet extends HttpServlet {
             }
         } // while initpnames
         
-        contentTypeContainsCharset = contentTypeContainsCharset(contentType);
-        if (contentTypeContainsCharset && responseCharacterEncoding != ResponseCharacterEncoding.LEGACY) {
-            throw new InitParamValueException(INIT_PARAM_CONTENT_TYPE, contentType,
+        if (contentType.containsCharset && responseCharacterEncoding != ResponseCharacterEncoding.LEGACY) {
+            throw new InitParamValueException(INIT_PARAM_CONTENT_TYPE, contentType.httpHeaderValue,
                     new IllegalStateException("You can't specify the charset in the content type, because the \"" +
                             INIT_PARAM_RESPONSE_CHARACTER_ENCODING + "\" init-param isn't set to "
                             + "\"" + INIT_PARAM_VALUE_LEGACY + "\"."));
-        }
+        }        
     }
     
-    private boolean contentTypeContainsCharset(String contentType) {
-        int charsetIdx = contentType.toLowerCase().indexOf("charset=");
-        if (charsetIdx != -1) {
-            char c = 0;
-            charsetIdx--;
-            while (charsetIdx >= 0) {
-                c = contentType.charAt(charsetIdx);
-                if (!Character.isWhitespace(c)) break;
-                charsetIdx--;
-            }
-            if (charsetIdx == -1 || c == ';') {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private List/*<MetaInfTldSource>*/ parseAsMetaInfTldLocations(String value) throws ParseException {
         List/*<MetaInfTldSource>*/ metaInfTldSources = null;
         
@@ -837,16 +819,24 @@ public class FreemarkerServlet extends HttpServlet {
                     "Unexpected error when loading template " + StringUtil.jQuoteNoXSS(templatePath) + ".", e);
         }
 
+        boolean tempSpecContentTypeContainsCharset = false;
         if (response.getContentType() == null || overrideResponseContentType != OverrideResponseContentType.NEVER) {
-            String templateSpecificContentType = getTemplateSpecificContentType(template);
+            ContentType templateSpecificContentType = getTemplateSpecificContentType(template);
             if (templateSpecificContentType != null) {
-                response.setContentType(templateSpecificContentType);
+                // With ResponseCharacterEncoding.LEGACY we should append the charset, but we don't do that for b. c.
+                response.setContentType(
+                        responseCharacterEncoding != ResponseCharacterEncoding.DO_NOT_SET
+                                ? templateSpecificContentType.httpHeaderValue
+                                : templateSpecificContentType.getMimeType());
+                tempSpecContentTypeContainsCharset = templateSpecificContentType.containsCharset;
             } else if (response.getContentType() == null
                     || overrideResponseContentType == OverrideResponseContentType.ALWAYS) {
-                if (!contentTypeContainsCharset && responseCharacterEncoding == ResponseCharacterEncoding.LEGACY) {
-                    response.setContentType(contentType + "; charset=" + getTemplateSpecificOutputEncoding(template));
+                if (responseCharacterEncoding == ResponseCharacterEncoding.LEGACY && !contentType.containsCharset) {
+                    // In legacy mode we don't call response.setCharacterEncoding, so the charset must be set here:
+                    response.setContentType(
+                            contentType.httpHeaderValue + "; charset=" + getTemplateSpecificOutputEncoding(template));
                 } else {
-                    response.setContentType(contentType);
+                    response.setContentType(contentType.httpHeaderValue);
                 }
             }
         }
@@ -855,7 +845,9 @@ public class FreemarkerServlet extends HttpServlet {
                 && responseCharacterEncoding != ResponseCharacterEncoding.DO_NOT_SET) {
             // Using the Servlet 2.4 way of setting character encoding.
             if (responseCharacterEncoding != ResponseCharacterEncoding.FORCE_CHARSET) {
-                response.setCharacterEncoding(getTemplateSpecificOutputEncoding(template));
+                if (!tempSpecContentTypeContainsCharset) {
+                    response.setCharacterEncoding(getTemplateSpecificOutputEncoding(template));
+                }
             } else {
                 response.setCharacterEncoding(forcedResponseCharacterEncoding.name());
             }
@@ -920,21 +912,21 @@ public class FreemarkerServlet extends HttpServlet {
         return outputEncoding != null ? outputEncoding : template.getEncoding();
     }
 
-    private String getTemplateSpecificContentType(final Template template) {
+    private ContentType getTemplateSpecificContentType(final Template template) {
         Object contentTypeAttr = template.getCustomAttribute("content_type");
         if (contentTypeAttr != null) {
             // Converted with toString() for backward compatibility.
-            // Don't add charset for backward compatibility.
-            return contentTypeAttr.toString();
+            return new ContentType(contentTypeAttr.toString());
         }
         
         String outputFormatMimeType = template.getOutputFormat().getMimeType();
         if (outputFormatMimeType != null) {
             if (responseCharacterEncoding == ResponseCharacterEncoding.LEGACY) {
                 // In legacy mode we won't call serlvetResponse.setCharacterEncoding(...), so:
-                outputFormatMimeType += "; charset=" + getTemplateSpecificOutputEncoding(template);
+                return new ContentType(outputFormatMimeType + "; charset=" + getTemplateSpecificOutputEncoding(template), true);
+            } else {
+                return new ContentType(outputFormatMimeType, false);
             }
-            return outputFormatMimeType; 
         }
             
         return null;
@@ -1554,6 +1546,46 @@ public class FreemarkerServlet extends HttpServlet {
         
     }
     
+    private static class ContentType {
+        private final String httpHeaderValue;
+        private final boolean containsCharset;
+        
+        public ContentType(String httpHeaderValue) {
+            this(httpHeaderValue, contentTypeContainsCharset(httpHeaderValue));
+        }
+
+        public ContentType(String httpHeaderValue, boolean containsCharset) {
+            this.httpHeaderValue = httpHeaderValue;
+            this.containsCharset = containsCharset;
+        }
+        
+        private static boolean contentTypeContainsCharset(String contentType) {
+            int charsetIdx = contentType.toLowerCase().indexOf("charset=");
+            if (charsetIdx != -1) {
+                char c = 0;
+                charsetIdx--;
+                while (charsetIdx >= 0) {
+                    c = contentType.charAt(charsetIdx);
+                    if (!Character.isWhitespace(c)) break;
+                    charsetIdx--;
+                }
+                if (charsetIdx == -1 || c == ';') {
+                    return true;
+                }
+            }
+            return false;
+        }
+        
+        /**
+         * Extracts the MIME type without the charset specifier or other such extras.
+         */
+        private String getMimeType() {
+            int scIdx = httpHeaderValue.indexOf(';');
+            return (scIdx == -1 ? httpHeaderValue : httpHeaderValue.substring(0, scIdx)).trim();
+        }
+        
+    }
+    
     private <T extends InitParamValueEnum> T initParamValueToEnum(String initParamValue, T[] enumValues) {
         for (T enumValue : enumValues) {
             String enumInitParamValue = enumValue.getInitParamValue();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/43fd9737/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
index b95cda5..f008280 100644
--- a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
+++ b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
@@ -53,6 +53,7 @@ public class FreemarkerServletTest {
 
     private static final String OUTPUT_FORMAT_HEADER_FTL = "outputFormatHeader.ftl";
     private static final String CONTENT_TYPE_ATTR_FTL = "contentTypeAttr.ftl";
+    private static final String CONTENT_TYPE_ATTR_WITH_CHARSET_FTL = "contentTypeAttrWithCharset.ftl";
     private static final String FOO_FTL = "foo.ftl";
     private static final String FOO_SRC_UTF8_FTL = "foo-src-utf8.ftl";
     private static final String FOO_OUT_UTF8_FTL = "foo-out-utf8.ftl";
@@ -61,6 +62,7 @@ public class FreemarkerServletTest {
     private static final String CFG_DEFAULT_ENCODING = "US-ASCII";
     /** According to the Servlet Specification */
     private static final String SERVLET_RESPONSE_DEFAULT_CHARSET = "ISO-8859-1";
+    private static final String DEFAULT_CONTENT_TYPE = "text/html";
 
     private MockServletContext servletContext;
 
@@ -182,42 +184,55 @@ public class FreemarkerServletTest {
 
     @Test
     public void testResponseOutputCharsetInitParam() throws Exception {
-        // Legacy mode is not aware of the outputEncoding, thus it doesn't set it:
-        assertOutputEncodingEquals(
-                CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
-                null, // <- expected env.outputEncoding
-                null, // <- init-param
-                FOO_FTL);
-        assertOutputEncodingEquals(
-                CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
-                null, // <- expected env.outputEncoding
-                FreemarkerServlet.INIT_PARAM_VALUE_LEGACY, // <- init-param
-                FOO_FTL);
-        // Legacy mode follows the source encoding of the template:
-        assertOutputEncodingEquals(
-                "UTF-8", // <- expected response.characterEncoding
-                null, // <- expected env.outputEncoding
-                null, // <- init-param
-                FOO_SRC_UTF8_FTL);
-        // Legacy mode doesn't deal with outputEncoding, but it's inherited by the Environment from the Template:
-        assertOutputEncodingEquals(
-                CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
-                "UTF-8", // <- expected env.outputEncoding
-                null, // <- init-param
-                FOO_OUT_UTF8_FTL);
-        // Charset in content type is the strongest:
-        assertOutputEncodingEquals(
-                "ISO-8859-2", // <- expected response.characterEncoding
-                null, // <- expected env.outputEncoding
-                null, // <- init-param
-                "text/html; charset=ISO-8859-2", // ContentType init-param
-                FOO_FTL);
-        assertOutputEncodingEquals(
-                "ISO-8859-2", // <- expected response.characterEncoding
-                null, // <- expected env.outputEncoding
-                null, // <- init-param
-                "text/html; charset=ISO-8859-2", // ContentType init-param
-                FOO_SRC_UTF8_FTL);
+        for (String initParamValue : new String[] { null, FreemarkerServlet.INIT_PARAM_VALUE_LEGACY }) {
+            // Legacy mode is not aware of the outputEncoding, thus it doesn't set it:
+            assertOutputEncodingEquals(
+                    CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
+                    null, // <- expected env.outputEncoding
+                    initParamValue, // <- init-param
+                    FOO_FTL);
+            assertOutputEncodingEquals(
+                    CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
+                    null, // <- expected env.outputEncoding
+                    initParamValue, // <- init-param
+                    FOO_FTL);
+            // Legacy mode follows the source encoding of the template:
+            assertOutputEncodingEquals(
+                    "UTF-8", // <- expected response.characterEncoding
+                    null, // <- expected env.outputEncoding
+                    initParamValue, // <- init-param
+                    FOO_SRC_UTF8_FTL);
+            // Legacy mode doesn't deal with outputEncoding, but it's inherited by the Environment from the Template:
+            assertOutputEncodingEquals(
+                    CFG_DEFAULT_ENCODING, // <- expected response.characterEncoding
+                    "UTF-8", // <- expected env.outputEncoding
+                    initParamValue, // <- init-param
+                    FOO_OUT_UTF8_FTL);
+            // Charset in content type is the strongest:
+            assertOutputEncodingEquals(
+                    "ISO-8859-2", // <- expected response.characterEncoding
+                    null, // <- expected env.outputEncoding
+                    initParamValue, // <- init-param
+                    "text/html; charset=ISO-8859-2", // ContentType init-param
+                    FOO_FTL);
+            assertOutputEncodingEquals(
+                    "ISO-8859-2", // <- expected response.characterEncoding
+                    null, // <- expected env.outputEncoding
+                    initParamValue, // <- init-param
+                    "text/html; charset=ISO-8859-2", // ContentType init-param
+                    FOO_SRC_UTF8_FTL);
+            assertOutputEncodingEquals(
+                    "UTF-8", // <- expected response.characterEncoding
+                    null, // <- expected env.outputEncoding
+                    initParamValue, // <- init-param
+                    CONTENT_TYPE_ATTR_WITH_CHARSET_FTL);
+            assertOutputEncodingEquals(
+                    "UTF-8", // <- expected response.characterEncoding
+                    null, // <- expected env.outputEncoding
+                    initParamValue, // <- init-param
+                    "text/html; charset=ISO-8859-2", // ContentType init-param
+                    CONTENT_TYPE_ATTR_WITH_CHARSET_FTL);
+        }
         
         // Non-legacy mode always keeps env.outputEncoding in sync. with the Servlet response encoding:
         assertOutputEncodingEquals(
@@ -249,6 +264,12 @@ public class FreemarkerServletTest {
         } catch (ServletException e) {
             assertThat(e.getCause().getCause().getMessage(), containsString(FreemarkerServlet.INIT_PARAM_VALUE_LEGACY));
         }
+        // But the legacy content_type template attribute can still set the output charset:
+        assertOutputEncodingEquals(
+                "UTF-8", // <- expected response.characterEncoding
+                "UTF-8", // <- expected env.outputEncoding
+                FreemarkerServlet.INIT_PARAM_VALUE_FROM_TEMPLATE, // <- init-param
+                CONTENT_TYPE_ATTR_WITH_CHARSET_FTL);
         
         // Do not set mode:
         assertOutputEncodingEquals(
@@ -269,8 +290,8 @@ public class FreemarkerServletTest {
         // Not allowed to specify the charset in the contentType init-param: 
         try {
             assertOutputEncodingEquals(
-                    null, // <- expected response.characterEncoding
-                    null, // <- expected env.outputEncoding
+                    SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected response.characterEncoding
+                    SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected env.outputEncoding
                     FreemarkerServlet.INIT_PARAM_VALUE_DO_NOT_SET, // <- init-param
                     "text/html; charset=ISO-8859-2", // ContentType init-param
                     FOO_FTL);
@@ -278,6 +299,12 @@ public class FreemarkerServletTest {
         } catch (ServletException e) {
             assertThat(e.getCause().getCause().getMessage(), containsString(FreemarkerServlet.INIT_PARAM_VALUE_LEGACY));
         }
+        // The legacy content_type template attribute can still specify an output charset, though it will be ignored:
+        assertOutputEncodingEquals(
+                SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected response.characterEncoding
+                SERVLET_RESPONSE_DEFAULT_CHARSET, // <- expected env.outputEncoding
+                FreemarkerServlet.INIT_PARAM_VALUE_DO_NOT_SET, // <- init-param
+                CONTENT_TYPE_ATTR_WITH_CHARSET_FTL);
         
         // Forced mode:
         assertOutputEncodingEquals(
@@ -308,8 +335,8 @@ public class FreemarkerServletTest {
         // Not allowed to specify the charset in the contentType init-param: 
         try {
             assertOutputEncodingEquals(
-                    null, // <- expected response.characterEncoding
-                    null, // <- expected env.outputEncoding
+                    "UTF-16LE", // <- expected response.characterEncoding
+                    "UTF-16LE", // <- expected env.outputEncoding
                     FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
                     "text/html; charset=ISO-8859-2", // ContentType init-param
                     FOO_FTL);
@@ -317,6 +344,12 @@ public class FreemarkerServletTest {
         } catch (ServletException e) {
             assertThat(e.getCause().getCause().getMessage(), containsString(FreemarkerServlet.INIT_PARAM_VALUE_LEGACY));
         }
+        // The legacy content_type template attribute can still specify an output charset, though it will be overridden:
+        assertOutputEncodingEquals(
+                "UTF-16LE", // <- expected response.characterEncoding
+                "UTF-16LE", // <- expected env.outputEncoding
+                FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
+                CONTENT_TYPE_ATTR_WITH_CHARSET_FTL);
     }
 
     private void assertResponseContentTypeEquals(
@@ -495,6 +528,7 @@ public class FreemarkerServletTest {
                 tl.putTemplate(FOO_SRC_UTF8_FTL, "foo");
                 tl.putTemplate(FOO_OUT_UTF8_FTL, "foo");
                 tl.putTemplate(CONTENT_TYPE_ATTR_FTL, "<#ftl attributes={ 'content_type': 'text/plain' }>foo");
+                tl.putTemplate(CONTENT_TYPE_ATTR_WITH_CHARSET_FTL, "<#ftl attributes={ 'content_type': 'text/plain; charset=UTF-8' }>foo");
                 tl.putTemplate(OUTPUT_FORMAT_HEADER_FTL, "<#ftl outputFormat='plainText'>foo");
                 return tl;
             } else {


[11/25] incubator-freemarker git commit: (Removed unnecessary test permutation.)

Posted by dd...@apache.org.
(Removed unnecessary test permutation.)


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

Branch: refs/heads/2.3
Commit: 064d12eb95b2086fbff903e1950d41f103face7b
Parents: eef30af
Author: ddekany <dd...@apache.org>
Authored: Sun Oct 25 16:30:28 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sun Oct 25 16:30:28 2015 +0100

----------------------------------------------------------------------
 .../java/freemarker/core/IteratorIssuesTest.java | 19 -------------------
 1 file changed, 19 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/064d12eb/src/test/java/freemarker/core/IteratorIssuesTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/IteratorIssuesTest.java b/src/test/java/freemarker/core/IteratorIssuesTest.java
index 78de020..453da4a 100644
--- a/src/test/java/freemarker/core/IteratorIssuesTest.java
+++ b/src/test/java/freemarker/core/IteratorIssuesTest.java
@@ -43,15 +43,6 @@ public class IteratorIssuesTest extends TemplateTest {
     }
 
     @Test
-    public void testHasContentAndListDOW2324() throws Exception {
-        addToDataModel("it", getDOW2324().wrap(getAbcIt()));
-        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_ABC);
-        
-        addToDataModel("it", getDOW2324().wrap(getEmptyIt()));
-        assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_EMPTY);
-    }
-
-    @Test
     public void testHasContentAndListBW230() throws Exception {
         addToDataModel("it", getBW230().wrap(getAbcIt()));
         assertOutput(FTL_HAS_CONTENT_AND_LIST, OUT_HAS_CONTENT_AND_LIST_ABC);
@@ -91,12 +82,6 @@ public class IteratorIssuesTest extends TemplateTest {
     }
 
     @Test
-    public void testListAndHasContentDOW2324() throws Exception {
-        addToDataModel("it", getDOW2324().wrap(getAbcIt()));
-        assertErrorContains(FTL_LIST_AND_HAS_CONTENT, "can be listed only once");
-    }
-
-    @Test
     public void testListAndHasContentBW230() throws Exception {
         addToDataModel("it", getBW230().wrap(getAbcIt()));
         assertOutput(FTL_LIST_AND_HAS_CONTENT, OUT_LIST_AND_HAS_CONTENT_BW_WRONG);
@@ -129,10 +114,6 @@ public class IteratorIssuesTest extends TemplateTest {
     private DefaultObjectWrapper getDOW2323() {
         return new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_23).build();
     }
-    
-    private DefaultObjectWrapper getDOW2324() {
-        return new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_24).build();
-    }
 
     private BeansWrapper getBW230() {
         return new BeansWrapperBuilder(Configuration.VERSION_2_3_0).build();


[15/25] incubator-freemarker git commit: Merge remote-tracking branch 'github/FREEMARKER-2-option-request-locale' into 2.3-gae

Posted by dd...@apache.org.
Merge remote-tracking branch 'github/FREEMARKER-2-option-request-locale' into 2.3-gae

FREEMARKER-2: Option to set request locale from the given servlet request
GitHub pull request #7


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

Branch: refs/heads/2.3
Commit: 6819382e6e32d03e80ef63b862b13300d1467dbd
Parents: ef943b2 1e4c64f
Author: ddekany <dd...@apache.org>
Authored: Mon Oct 26 23:31:56 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Mon Oct 26 23:33:27 2015 +0100

----------------------------------------------------------------------
 .../ext/servlet/FreemarkerServlet.java          | 69 ++++++++++----
 .../ext/servlet/FreemarkerServletTest.java      | 95 ++++++++++++++++++--
 2 files changed, 144 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6819382e/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --cc src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index f7731ee,560c459..7b785d2
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@@ -458,10 -475,10 +474,11 @@@ public class FreemarkerServlet extends 
      @SuppressFBWarnings(value="SE_BAD_FIELD", justification="Not investing into making this Servlet serializable")
      private ObjectWrapper wrapper;
      private String contentType;
 -    private OverrideResponseContentType overrideResponseContentType = OverrideResponseContentType.ALWAYS;
 +    private OverrideResponseContentType overrideResponseContentType = initParamValueToEnum(
 +            getDefaultOverrideResponseContentType(), OverrideResponseContentType.values());
      private ResponseCharacterEncoding responseCharacterEncoding = ResponseCharacterEncoding.LEGACY;
      private boolean contentTypeContainsCharset;
+     private OverrideResponseLocale overrideResponseLocale = OverrideResponseLocale.ALWAYS;
      private List/*<MetaInfTldSource>*/ metaInfTldSources;
      private List/*<String>*/ classpathTlds;
  


[22/25] incubator-freemarker git commit: FreemarkerServlet ResponseCharacterEncoding init-param doesn't use the "force" prefix anymore, you just specify the charset name as its value.

Posted by dd...@apache.org.
FreemarkerServlet ResponseCharacterEncoding init-param doesn't use the "force" prefix anymore, you just specify the charset name as its value.


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

Branch: refs/heads/2.3
Commit: 7540a51c13e9b666bea6e8e5065cb1ee1712aee8
Parents: 947428a
Author: ddekany <dd...@apache.org>
Authored: Thu Nov 26 22:36:42 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Thu Nov 26 23:44:19 2015 +0100

----------------------------------------------------------------------
 .../ext/servlet/FreemarkerServlet.java          | 81 +++++++++++++-------
 src/manual/book.xml                             | 32 ++++++--
 .../ext/servlet/FreemarkerServletTest.java      | 12 +--
 3 files changed, 85 insertions(+), 40 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7540a51c/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index d745010..092dc8e 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -21,6 +21,8 @@ package freemarker.ext.servlet;
 
 import java.io.IOException;
 import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -45,6 +47,7 @@ import freemarker.cache.FileTemplateLoader;
 import freemarker.cache.MultiTemplateLoader;
 import freemarker.cache.TemplateLoader;
 import freemarker.cache.WebappTemplateLoader;
+import freemarker.core.BugException;
 import freemarker.core.Configurable;
 import freemarker.core.Environment;
 import freemarker.core.OutputFormat;
@@ -162,8 +165,7 @@ import freemarker.template.utility.StringUtil;
  * the default of it), the content type may include the charset (as in <tt>"text/html; charset=utf-8"</tt>), in which
  * case that specifies the actual charset of the output. If the the {@value #INIT_PARAM_RESPONSE_CHARACTER_ENCODING}
  * init-param is not set to {@value #INIT_PARAM_VALUE_LEGACY}, then specifying the charset in the
- * {@value #INIT_PARAM_CONTENT_TYPE} init-param is not allowed, and will cause servlet initialization error.
- * </li>
+ * {@value #INIT_PARAM_CONTENT_TYPE} init-param is not allowed, and will cause servlet initialization error.</li>
  *
  * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> (since 2.3.24): Specifies when we should
  * override the {@code contentType} that might be already set (i.e., non-{@code null}) in the
@@ -185,7 +187,7 @@ import freemarker.template.utility.StringUtil;
  * 
  * <li><strong>{@value #INIT_PARAM_RESPONSE_CHARACTER_ENCODING}</strong> (since 2.3.24): Specifies how the
  * {@link HttpServletResponse} "character encoding" (as in {@link HttpServletResponse#setCharacterEncoding(String)})
- * will deduced. The possible modes are:
+ * will be deduced. The possible modes are:
  * <ul>
  * <li>{@value #INIT_PARAM_VALUE_LEGACY}: This is the default for backward compatibility; in new applications, use
  * {@value #INIT_PARAM_VALUE_FROM_TEMPLATE} (or some of the other options) instead. {@value #INIT_PARAM_VALUE_LEGACY}
@@ -195,8 +197,8 @@ import freemarker.template.utility.StringUtil;
  * or writes it (though very few applications utilize that setting anyway). Also, it sets the charset of the servlet
  * response by adding it to the response content type via calling {@link HttpServletResponse#setContentType(String)} (as
  * that was the only way before Servlet 2.4), not via the more modern
- * {@link HttpServletResponse#setCharacterEncoding(String)} method. Note that the charset of a template usually
- * comes from {@link Configuration#getDefaultEncoding()} (i.e., from the {@code default_encoding} FreeMarker setting),
+ * {@link HttpServletResponse#setCharacterEncoding(String)} method. Note that the charset of a template usually comes
+ * from {@link Configuration#getDefaultEncoding()} (i.e., from the {@code default_encoding} FreeMarker setting),
  * occasionally from {@link Configuration#getEncoding(Locale)} (when FreeMarker was configured to use different charsets
  * depending on the locale) or even more rarely from {@link Configuration#getTemplateConfigurers()} (when FreeMarker was
  * configured to use a specific charset for certain templates).
@@ -205,16 +207,16 @@ import freemarker.template.utility.StringUtil;
  * template usually just inherits that from the {@link Configuration}), and if that's not set, then reads the source
  * charset of the template, just like {@value #INIT_PARAM_VALUE_LEGACY}. Then it passes the charset acquired this way to
  * {@link HttpServletResponse#setCharacterEncoding(String)} and {@link Environment#setOutputEncoding(String)}. (It
- * doesn't call the legacy {@link HttpServletResponse#setContentType(String)} API to set the charset.) (Note that if
- * the template has a {@code content_type} template attribute (which is deprecated) that specifies a charset, it will be
- * considered as the output charset of that template.)
+ * doesn't call the legacy {@link HttpServletResponse#setContentType(String)} API to set the charset.) (Note that if the
+ * template has a {@code content_type} template attribute (which is deprecated) that specifies a charset, it will be
+ * used as the output charset of that template.)
  * <li>{@value #INIT_PARAM_VALUE_DO_NOT_SET}: {@link FreemarkerServlet} will not set the {@link HttpServletResponse}
  * "character encoding". It will still call {@link Environment#setOutputEncoding(String)}, so that the running template
  * will be aware of the charset used for the output.
- * <li>{@value #INIT_PARAM_VALUE_FORCE_PREFIX} + charset name, for example {@code force UTF-8}: The output charset will
- * be the one specified after "force" + space, regardless of everything. The charset specified this way is passed to
- * {@link HttpServletResponse#setCharacterEncoding(String)} and {@link Environment#setOutputEncoding(String)}. If the
- * charset name is not recognized by Java, the servlet initialization will fail.
+ * <li>A charset name, for example {@code UTF-8}: The output charset will the specified charset, regardless of
+ * everything. The charset specified this way is passed to {@link HttpServletResponse#setCharacterEncoding(String)} and
+ * {@link Environment#setOutputEncoding(String)}. If the charset name is not recognized by Java, the servlet
+ * initialization will fail.
  * </ul>
  *
  * <li><strong>{@value #INIT_PARAM_BUFFER_SIZE}</strong>: Sets the size of the output buffer in bytes, or if "KB" or
@@ -417,7 +419,6 @@ public class FreemarkerServlet extends HttpServlet {
     public static final String INIT_PARAM_VALUE_FROM_TEMPLATE = "fromTemplate";
     public static final String INIT_PARAM_VALUE_LEGACY = "legacy";
     public static final String INIT_PARAM_VALUE_DO_NOT_SET = "doNotSet";
-    public static final String INIT_PARAM_VALUE_FORCE_PREFIX = "force ";
 
     /**
      * When set, the items defined in it will be added after those coming from the
@@ -668,9 +669,20 @@ public class FreemarkerServlet extends HttpServlet {
                     overrideResponseContentType = initParamValueToEnum(value, OverrideResponseContentType.values());
                 } else if (name.equals(INIT_PARAM_RESPONSE_CHARACTER_ENCODING)) {
                     responseCharacterEncoding = initParamValueToEnum(value, ResponseCharacterEncoding.values());
-                    if (responseCharacterEncoding == ResponseCharacterEncoding.FORCE_CHARSET) {
-                        String charsetName = value.substring(INIT_PARAM_VALUE_FORCE_PREFIX.length()).trim();
-                        forcedResponseCharacterEncoding = Charset.forName(charsetName);
+                    if (responseCharacterEncoding == ResponseCharacterEncoding.SPECIFIC_CHARSET) {
+                        try {
+                            forcedResponseCharacterEncoding = Charset.forName(value);
+                        } catch (IllegalArgumentException e) {
+                            if (!(e instanceof UnsupportedCharsetException
+                                    || e instanceof IllegalCharsetNameException)) {
+                                throw e;
+                            }
+                            StringBuilder sb = new StringBuilder();
+                            sb.append(StringUtil.jQuote(value));
+                            sb.append(" isn't a valid charset name, nor it is any of the predefined values: ");
+                            appendEnumInitParamValues(sb, ResponseCharacterEncoding.values());
+                            throw new IllegalArgumentException(sb.toString());
+                        }
                     }
                 } else if (name.equals(INIT_PARAM_OVERRIDE_RESPONSE_LOCALE)) {
                     overrideResponseLocale = initParamValueToEnum(value, OverrideResponseLocale.values());
@@ -846,7 +858,7 @@ public class FreemarkerServlet extends HttpServlet {
         if (responseCharacterEncoding != ResponseCharacterEncoding.LEGACY
                 && responseCharacterEncoding != ResponseCharacterEncoding.DO_NOT_SET) {
             // Using the Servlet 2.4 way of setting character encoding.
-            if (responseCharacterEncoding != ResponseCharacterEncoding.FORCE_CHARSET) {
+            if (responseCharacterEncoding != ResponseCharacterEncoding.SPECIFIC_CHARSET) {
                 if (!tempSpecContentTypeContainsCharset) {
                     response.setCharacterEncoding(getTemplateSpecificOutputEncoding(template));
                 }
@@ -1589,28 +1601,42 @@ public class FreemarkerServlet extends HttpServlet {
     }
     
     private <T extends InitParamValueEnum> T initParamValueToEnum(String initParamValue, T[] enumValues) {
+        T wildcardEnumValue = null;
         for (T enumValue : enumValues) {
             String enumInitParamValue = enumValue.getInitParamValue();
-            if (initParamValue.equals(enumInitParamValue)
-                    || enumInitParamValue.endsWith("}") && initParamValue.startsWith(
-                            enumInitParamValue.substring(0, enumInitParamValue.indexOf("${")))) {
+            if (enumInitParamValue == null) {
+                if (wildcardEnumValue != null) {
+                    throw new BugException();
+                }
+                wildcardEnumValue = enumValue;
+            } else if (initParamValue.equals(enumInitParamValue)) {
                 return enumValue;
             }
         }
+        if (wildcardEnumValue != null) {
+            return wildcardEnumValue;
+        }
         
         StringBuilder sb = new StringBuilder();
         sb.append(StringUtil.jQuote(initParamValue));
         sb.append(" is not a one of the enumeration values: ");
+        appendEnumInitParamValues(sb, enumValues);
+        throw new IllegalArgumentException(sb.toString());
+    }
+
+    protected <T extends InitParamValueEnum> void appendEnumInitParamValues(StringBuilder sb, T[] enumValues) {
         boolean first = true;
         for (T value : enumValues) {
-            if (!first) {
-                sb.append(", ");
-            } else {
-                first = false;
+            String initParamValue = value.getInitParamValue();
+            if (initParamValue != null) {  // Not a wildcard enum
+                if (!first) {
+                    sb.append(", ");
+                } else {
+                    first = false;
+                }
+                sb.append(StringUtil.jQuote(initParamValue));
             }
-            sb.append(StringUtil.jQuote(value.getInitParamValue()));
         }
-        throw new IllegalArgumentException(sb.toString());
     }
 
     /**
@@ -1643,7 +1669,7 @@ public class FreemarkerServlet extends HttpServlet {
         LEGACY(INIT_PARAM_VALUE_LEGACY),
         FROM_TEMPLATE(INIT_PARAM_VALUE_FROM_TEMPLATE),
         DO_NOT_SET(INIT_PARAM_VALUE_DO_NOT_SET),
-        FORCE_CHARSET(INIT_PARAM_VALUE_FORCE_PREFIX + "${charsetName}");
+        SPECIFIC_CHARSET(null);
 
         private final String initParamValue;
         
@@ -1655,7 +1681,6 @@ public class FreemarkerServlet extends HttpServlet {
         public String getInitParamValue() {
             return initParamValue;
         }
-        
     }
 
     private enum OverrideResponseLocale implements InitParamValueEnum {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7540a51c/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index c6f8033..1654417 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -10427,7 +10427,11 @@ TemplateHashModel roundingModeEnums =
   &lt;/init-param&gt;
   &lt;init-param&gt;
     &lt;param-name&gt;ContentType&lt;/param-name&gt;
-    &lt;param-value&gt;text/html; charset=UTF-8&lt;/param-value&gt; &lt;!-- Forces UTF-8 output encoding! --&gt;
+    &lt;param-value&gt;text/html&lt;/param-value&gt;
+  &lt;/init-param&gt;
+  &lt;init-param&gt;
+    &lt;param-name&gt;ResponseCharacterEncoding&lt;/param-name&gt;
+    &lt;param-value&gt;UTF-8&lt;/param-value&gt;
   &lt;/init-param&gt;
     
   &lt;!-- FreeMarker settings: --&gt;
@@ -26080,9 +26084,8 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
                   the <literal>outputEncoding</literal> setting),
                   <literal>doNotSet</literal> (keeps what the caller has
                   already set in the <literal>ServletRespone</literal>) and
-                  <literal>force
-                  <replaceable>charsetName</replaceable></literal> (forces a
-                  specific charset).</para>
+                  any valid charset name (forces a specific output
+                  charset).</para>
                 </listitem>
               </itemizedlist>
             </listitem>
@@ -26420,7 +26423,7 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
 
               <itemizedlist>
                 <listitem>
-                  <para><literal>OverrideResponseContentType</literal>.
+                  <para><literal>OverrideResponseContentType</literal>:
                   Specifies when should we override the
                   <literal>contentType</literal> that's already set (i.e.,
                   non-<literal>null</literal>) in the
@@ -26433,13 +26436,30 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
                 </listitem>
 
                 <listitem>
-                  <para><literal>OverrideResponseLocale</literal>. Specifies
+                  <para><literal>OverrideResponseLocale</literal>: Specifies
                   if should we override the <literal>contentType</literal>
                   that's already set (i.e., non-<literal>null</literal>) in
                   the <literal>HttpServletResponse</literal>. Earlier, we have
                   always set it, but now this behavior can be changed so that
                   we only set it if it wasn't already set.</para>
                 </listitem>
+
+                <listitem>
+                  <para><literal>ResponseCharacterEncoding</literal>:
+                  Deprecates the old (and quirky) logic of specifying the
+                  output charset, which was putting it into the
+                  <literal>ContentType</literal> init-param after the MIME
+                  type, otherwise falling back to using the template file
+                  charset. The possible values are <literal>legacy</literal>
+                  (the default for backward compatibility),
+                  <literal>fromTemplate</literal> (which is
+                  <literal>legacy</literal> without quirks, and is aware of
+                  the <literal>outputEncoding</literal> setting),
+                  <literal>doNotSet</literal> (keeps what the caller has
+                  already set in the <literal>ServletRespone</literal>) and
+                  any valid charset name (forces a specific output
+                  charset).</para>
+                </listitem>
               </itemizedlist>
             </listitem>
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7540a51c/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
index f008280..6e5ce09 100644
--- a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
+++ b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
@@ -310,23 +310,23 @@ public class FreemarkerServletTest {
         assertOutputEncodingEquals(
                 "UTF-16LE", // <- expected response.characterEncoding
                 "UTF-16LE", // <- expected env.outputEncoding
-                FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
+                "UTF-16LE", // <- init-param
                 FOO_FTL);
         assertOutputEncodingEquals(
                 "UTF-16LE", // <- expected response.characterEncoding
                 "UTF-16LE", // <- expected env.outputEncoding
-                FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
+                "UTF-16LE", // <- init-param
                 FOO_SRC_UTF8_FTL);
         assertOutputEncodingEquals(
                 "UTF-16LE", // <- expected response.characterEncoding
                 "UTF-16LE", // <- expected env.outputEncoding
-                FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
+                "UTF-16LE", // <- init-param
                 FOO_OUT_UTF8_FTL);
         try {
             assertOutputEncodingEquals(
                     null, // <- expected response.characterEncoding
                     null, // <- expected env.outputEncoding
-                    FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "noSuchCharset", // <- init-param
+                    "noSuchCharset", // <- init-param
                     FOO_FTL);
             fail();
         } catch (ServletException e) {
@@ -337,7 +337,7 @@ public class FreemarkerServletTest {
             assertOutputEncodingEquals(
                     "UTF-16LE", // <- expected response.characterEncoding
                     "UTF-16LE", // <- expected env.outputEncoding
-                    FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
+                    "UTF-16LE", // <- init-param
                     "text/html; charset=ISO-8859-2", // ContentType init-param
                     FOO_FTL);
             fail();
@@ -348,7 +348,7 @@ public class FreemarkerServletTest {
         assertOutputEncodingEquals(
                 "UTF-16LE", // <- expected response.characterEncoding
                 "UTF-16LE", // <- expected env.outputEncoding
-                FreemarkerServlet.INIT_PARAM_VALUE_FORCE_PREFIX + "UTF-16LE", // <- init-param
+                "UTF-16LE", // <- init-param
                 CONTENT_TYPE_ATTR_WITH_CHARSET_FTL);
     }
 


[25/25] incubator-freemarker git commit: Merge remote-tracking branch 'origin/2.3-gae' into 2.3

Posted by dd...@apache.org.
Merge remote-tracking branch 'origin/2.3-gae' into 2.3


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

Branch: refs/heads/2.3
Commit: 5758b0a5148e4c99879d2e6f4b6fcb6f59db6e5d
Parents: 0286f23 116b817
Author: ddekany <dd...@apache.org>
Authored: Sun Nov 29 12:56:53 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sun Nov 29 12:56:53 2015 +0100

----------------------------------------------------------------------
 .classpath                                      |   2 +
 ivy.xml                                         |  10 +-
 src/main/java/freemarker/core/Configurable.java |   9 +-
 src/main/java/freemarker/core/Environment.java  |   2 +-
 .../core/_ObjectBuilderSettingEvaluator.java    |  96 +++-
 .../java/freemarker/ext/beans/BeanModel.java    |   4 +
 .../java/freemarker/ext/beans/BeansWrapper.java |  19 +-
 .../java/freemarker/ext/jsp/TaglibFactory.java  |   3 +-
 .../ext/servlet/FreemarkerServlet.java          | 498 ++++++++++++++---
 .../java/freemarker/template/Configuration.java |  11 +
 .../template/DefaultIteratorAdapter.java        |  15 +-
 .../template/DefaultObjectWrapper.java          |   5 +
 .../freemarker/template/SimpleCollection.java   |  24 +-
 src/manual/book.xml                             | 240 +++++++-
 .../freemarker/core/IteratorIssuesTest.java     | 130 +++++
 .../core/ObjectBuilderSettingsTest.java         |  66 ++-
 .../ext/servlet/FreemarkerServletTest.java      | 550 +++++++++++++++++++
 .../template/DefaultObjectWrapperTest.java      |   2 +-
 18 files changed, 1534 insertions(+), 152 deletions(-)
----------------------------------------------------------------------



[07/25] incubator-freemarker git commit: Continued FREEMARKER-1 "Option to not to overwrite response ContentType in FreemarkerServlet": The output_format setting of the Template also sets the response contentType, similarly to the content_type custom att

Posted by dd...@apache.org.
Continued FREEMARKER-1 "Option to not to overwrite response ContentType in FreemarkerServlet": The output_format setting of the Template also sets the response contentType, similarly to the content_type custom attribute.


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

Branch: refs/heads/2.3
Commit: 2531f51e7bc019480abdb35671b7c94bf75fc4b0
Parents: 3bf80f6
Author: ddekany <dd...@apache.org>
Authored: Sun Oct 18 12:27:50 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sun Oct 18 12:27:50 2015 +0200

----------------------------------------------------------------------
 .../ext/servlet/FreemarkerServlet.java          | 49 ++++++++++++++++----
 .../ext/servlet/FreemarkerServletTest.java      | 29 ++++++++++++
 .../ext/servlet/outputFormatHeader.ftl          | 20 ++++++++
 3 files changed, 88 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2531f51e/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index 291df35..b4aa296 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -38,6 +38,8 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
+import com.sun.org.apache.xml.internal.serialize.OutputFormat;
+
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import freemarker.cache.ClassTemplateLoader;
 import freemarker.cache.FileTemplateLoader;
@@ -45,6 +47,7 @@ import freemarker.cache.MultiTemplateLoader;
 import freemarker.cache.TemplateLoader;
 import freemarker.cache.WebappTemplateLoader;
 import freemarker.core.Configurable;
+import freemarker.core.UndefinedOutputFormat;
 import freemarker.ext.jsp.TaglibFactory;
 import freemarker.ext.jsp.TaglibFactory.ClasspathMetaInfTldSource;
 import freemarker.ext.jsp.TaglibFactory.ClearMetaInfTldSource;
@@ -135,13 +138,24 @@ import freemarker.template.utility.StringUtil;
  * HTTP client not to cache the returned page. The default is <tt>false</tt>.</li>
  * 
  * <li><strong>{@value #INIT_PARAM_CONTENT_TYPE}</strong>: The Content-type HTTP header value used in the HTTP responses
- * (unless {@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE} is set to {@code false} and the response
- * {@code contentType} is already set by the time {@link FreemarkerServlet} is invoked). Defaults to
- * <tt>"text/html"</tt>. The value may include the charset (e.g. <tt>"text/html; charset=utf-8"</tt>). If the charset is
- * not specified in this init-param, then the charset (encoding) of the actual template file will be used (both in the
- * response HTTP header and for encoding the output stream). Note that this setting can be overridden on a per-template
- * basis by specifying a custom attribute named <tt>content_type</tt> in the <tt>attributes</tt> parameter of the
- * <tt>&lt;#ftl&gt;</tt> directive.</li>
+ * when nothing else specifies the MIME type. The things that may specify the MIME type (and hence this init-param is
+ * ignored), starting with the highest precedence, are:
+ * <ol>
+ * <li>The template's custom attribute name <tt>content_type</tt> in the <tt>attributes</tt> parameter of the
+ * <tt>&lt;#ftl&gt;</tt> directive. This is a legacy feature, deprecated by the {@link OutputFormat} mechanism.
+ * <li>The {@linkplain Template#getOutputFormat() output format of the template}, if that has non-{@code null} MIME-type
+ * ({@link OutputFormat#getMediaType()}). When a template has no output format specified, {@link UndefinedOutputFormat}
+ * is used, which has {@code null} MIME-type. (The output format of a template is deduced from {@link Configuration}
+ * settings, or can be specified directly in the template, like {@code <#ftl outputFormat="HTML">}. See the FreeMarker
+ * Manual for more about the output format mechanism. Note that setting an output format may turns on auto-escaping,
+ * so it's not just about MIME types.)
+ * <li>If the {@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE} init-param is {@code false} (the default is
+ * {@code true}), then the value of {@link HttpServletResponse#getContentType()} is used if that's non-{@code null}.
+ * </ol>
+ * If none of the above gives a MIME type, then this init-param does. Defaults to <tt>"text/html"</tt>. The
+ * value may include the charset (e.g. <tt>"text/html; charset=utf-8"</tt>). If the charset is not specified in this
+ * init-param, then the charset (encoding) of the actual template file will appended after it, which, as per the
+ * Servlet specification, also sets the actual encoding used to write the response body.</li>
  *
  * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> (since 2.3.24): Specifies if we should
  * always set the {@code contentType} in the {@link HttpServletResponse} to the value of the
@@ -725,9 +739,9 @@ public class FreemarkerServlet extends HttpServlet {
                     "Unexpected error when loading template " + StringUtil.jQuoteNoXSS(templatePath) + ".", e);
         }
 
-        Object attrContentType = template.getCustomAttribute("content_type");
-        if (attrContentType != null) {
-            response.setContentType(attrContentType.toString());
+        String templateSpecificContentType = getTemplateSpecificContentType(template);
+        if (templateSpecificContentType != null) {
+            response.setContentType(templateSpecificContentType);
         } else {
             if (overrideResponseContentType || response.getContentType() == null) {
                 if (noCharsetInContentType) {
@@ -767,6 +781,21 @@ public class FreemarkerServlet extends HttpServlet {
         }
     }
 
+    private String getTemplateSpecificContentType(final Template template) {
+        Object contentTypeAttr = template.getCustomAttribute("content_type");
+        if (contentTypeAttr != null) {
+            // Convert with toString() for backward compatibility
+            return contentTypeAttr.toString();
+        }
+        
+        String outputFormatMimeType = template.getOutputFormat().getMimeType();
+        if (outputFormatMimeType != null) {
+            return outputFormatMimeType; 
+        }
+            
+        return null;
+    }
+
     private ServletException newServletExceptionWithFreeMarkerLogging(String message, Throwable cause) throws ServletException {
         if (cause instanceof TemplateException) {
             // For backward compatibility, we log into the same category as Environment did when

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2531f51e/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
index 9239ee0..5bb8dc9 100644
--- a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
+++ b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
@@ -198,6 +198,35 @@ public class FreemarkerServletTest {
         assertEquals(HttpServletResponse.SC_OK, response.getStatus());
         assertEquals("text/plain", response.getContentType());
     }
+
+    @Test
+    public void testContentTypeInitParams_outputFormatAlwaysWins_DefaultOverriding() throws ServletException, IOException {
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/outputFormatHeader.ftl");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        response.setContentType("application/json");
+        assertEquals("application/json", response.getContentType());
+
+        createFreemarkerServlet().doGet(request, response);
+        LOG.debug("response content: " + response.getContentAsString());
+
+        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+        assertEquals("text/plain", response.getContentType());
+    }
+    
+    @Test
+    public void testContentTypeInitParams_outputFormatAlwaysWins_NoOverriding() throws ServletException, IOException {
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/outputFormatHeader.ftl");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        response.setContentType("application/json");
+        assertEquals("application/json", response.getContentType());
+
+        createFreemarkerServlet(FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false")
+                .doGet(request, response);
+        LOG.debug("response content: " + response.getContentAsString());
+
+        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+        assertEquals("text/plain", response.getContentType());
+    }
     
     private FreemarkerServlet createFreemarkerServlet(String... initParams) throws ServletException {
         MockServletConfig servletConfig = new MockServletConfig(servletContext);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2531f51e/src/test/resources/freemarker/ext/servlet/outputFormatHeader.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/servlet/outputFormatHeader.ftl b/src/test/resources/freemarker/ext/servlet/outputFormatHeader.ftl
new file mode 100644
index 0000000..27d7b2e
--- /dev/null
+++ b/src/test/resources/freemarker/ext/servlet/outputFormatHeader.ftl
@@ -0,0 +1,20 @@
+<#ftl outputFormat="plainText">
+<#--
+  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.
+-->
+foo
\ No newline at end of file


[14/25] incubator-freemarker git commit: (Version history: Some missing entries under "Changes compared to 2.3.24 Preview 1")

Posted by dd...@apache.org.
(Version history: Some missing entries under "Changes compared to 2.3.24 Preview 1")


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

Branch: refs/heads/2.3
Commit: ef943b23f89fd197039637549388513916a8db53
Parents: a761e99
Author: ddekany <dd...@apache.org>
Authored: Sun Oct 25 19:47:57 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sun Oct 25 19:47:57 2015 +0100

----------------------------------------------------------------------
 src/manual/book.xml | 87 +++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 67 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef943b23/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index e461e94..cf47f7a 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -26237,26 +26237,6 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
               <literal>String</literal>-s and
               <literal>Interpolation</literal>-s.</para>
             </listitem>
-
-            <listitem>
-              <para>Fixes and improvements in the <quote>object
-              builder</quote> syntax used for configuring FreeMarker from
-              <literal>java.util.Properties</literal> (or other string-only
-              sources). This is not to be confused with the template language
-              syntax, which has nothing to do with the <quote>object
-              builder</quote> syntax we are writing about here. The
-              improvements are:</para>
-
-              <itemizedlist>
-                <listitem>
-                  <para>Number literals can have Java type specified postfixes
-                  (<literal>f</literal>, <literal>d</literal>,
-                  <literal>l</literal>), plus <literal>bd</literal> for
-                  <literal>BigDecimal</literal> and <literal>bi</literal> for
-                  <literal>BigInteger</literal>.</para>
-                </listitem>
-              </itemizedlist>
-            </listitem>
           </itemizedlist>
         </section>
 
@@ -26393,6 +26373,73 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
               returns <literal>true</literal> if the value is of type
               <quote>markup output</quote>.</para>
             </listitem>
+
+            <listitem>
+              <para>New <literal>FreemarkerServlet</literal> init-param:
+              <literal>OverrideResponseContentType</literal>. Specifies when
+              should we override the <literal>contentType</literal> that's
+              already set (i.e., non-<literal>null</literal>) in the
+              <literal>HttpServletResponse</literal>. Earlier, we have always
+              set it, and that's still the default behavior. But now that this
+              init-param exists, you can change that behavior, so that the
+              <literal>contentType</literal> you have specified before
+              forwarding to <literal>FreemarkerServlet</literal>
+              matters.</para>
+            </listitem>
+
+            <listitem>
+              <para>Bug fixed: It wasn't well defined when a Java
+              <literal>Iterator</literal> counts as empty. Depending on what
+              <literal>ObjectWrapper</literal> you are using, one of these
+              fixes apply:</para>
+
+              <itemizedlist>
+                <listitem>
+                  <para><literal>DefaultObjectWrapper</literal> (fix is always
+                  active): Operations on the <literal>Iterator</literal> that
+                  only check if it's empty without reading an element from it,
+                  such as <literal>?has_content</literal>, won't cause the a
+                  later iteration (or further emptiness check) to fail
+                  anymore. Earlier, in certain situations, the second
+                  operation has failed saying that the iterator <quote>can be
+                  listed only once</quote>.</para>
+                </listitem>
+
+                <listitem>
+                  <para><literal>BeansWrapper</literal> (when it's not
+                  extended by <literal>DefaultObjectWrapper</literal>), if
+                  it's <literal>incompatibleImprovements</literal> property is
+                  set to 2.3.24 (or higher): <literal>Iterator</literal>-s
+                  were always said to be non-empty when using
+                  <literal>?has_content</literal> and such (i.e., operators
+                  that check emptiness without reading any elements). Now an
+                  <literal>Iterator</literal> counts as empty exactly if it
+                  has no elements left. (Note that this bug has never affected
+                  basic functionality, like <literal>&lt;#list
+                  ...&gt;</literal>.)</para>
+                </listitem>
+              </itemizedlist>
+            </listitem>
+
+            <listitem>
+              <para>Fixes and improvements in the <quote>object
+              builder</quote> syntax used for configuring FreeMarker from
+              <literal>java.util.Properties</literal> (or other string-only
+              sources). This is not to be confused with the template language
+              syntax, which has nothing to do with the <quote>object
+              builder</quote> syntax we are writing about here. The
+              improvements are:</para>
+
+              <itemizedlist>
+                <listitem>
+                  <para>Number literals can have Java type specified postfixes
+                  (<literal>f</literal>, <literal>d</literal>,
+                  <literal>l</literal>), plus <literal>bd</literal> for
+                  <literal>BigDecimal</literal> and <literal>bi</literal> for
+                  <literal>BigInteger</literal>.</para>
+                </listitem>
+              </itemizedlist>
+            </listitem>
           </itemizedlist>
         </section>
       </section>


[06/25] incubator-freemarker git commit: Continued FREEMARKER-1 "Option to not to overwrite response ContentType in FreemarkerServlet": Improved JavaDocs. Added version history entry.

Posted by dd...@apache.org.
Continued FREEMARKER-1 "Option to not to overwrite response ContentType in FreemarkerServlet": Improved JavaDocs. Added version history entry.


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

Branch: refs/heads/2.3
Commit: 3bf80f67fda9fd87539681b90c61701d154362a2
Parents: 9e7f359
Author: ddekany <dd...@apache.org>
Authored: Sat Oct 17 20:05:05 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sat Oct 17 20:05:05 2015 +0200

----------------------------------------------------------------------
 .../ext/servlet/FreemarkerServlet.java          | 22 ++++++++++----------
 src/manual/book.xml                             | 14 +++++++++++++
 2 files changed, 25 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3bf80f67/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index 841c8b1..291df35 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -135,19 +135,19 @@ import freemarker.template.utility.StringUtil;
  * HTTP client not to cache the returned page. The default is <tt>false</tt>.</li>
  * 
  * <li><strong>{@value #INIT_PARAM_CONTENT_TYPE}</strong>: The Content-type HTTP header value used in the HTTP responses
- * (unless <strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> is set to {@code false} and the response
- * Content-type is already set by the time {@link FreemarkerServlet} is invoked). Defaults to <tt>"text/html"</tt>. The
- * value may include the charset (e.g. <tt>"text/html; charset=ISO-8859-1"</tt>). If the charset is not specified in
- * this init-param, then the charset (encoding) of the actual template file will be used (both in the response HTTP
- * header and for encoding the output stream). Note that this setting can be overridden on a per-template basis by
- * specifying a custom attribute named <tt>content_type</tt> in the <tt>attributes</tt> parameter of the
+ * (unless {@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE} is set to {@code false} and the response
+ * {@code contentType} is already set by the time {@link FreemarkerServlet} is invoked). Defaults to
+ * <tt>"text/html"</tt>. The value may include the charset (e.g. <tt>"text/html; charset=utf-8"</tt>). If the charset is
+ * not specified in this init-param, then the charset (encoding) of the actual template file will be used (both in the
+ * response HTTP header and for encoding the output stream). Note that this setting can be overridden on a per-template
+ * basis by specifying a custom attribute named <tt>content_type</tt> in the <tt>attributes</tt> parameter of the
  * <tt>&lt;#ftl&gt;</tt> directive.</li>
  *
- * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> (since 2.3.24): If set to {@code true}
- * (which is the default), the Content-type HTTP header of the response is always set to the value of the
- * <strong>{@value #INIT_PARAM_CONTENT_TYPE}</strong> setting. If set to {@code false}, {@link FreemarkerServlet} will
- * only set the Content-type HTTP response header if it isn't already set when {@link FreemarkerServlet} is invoked.
- * Thus, {@code false} allows you to specify the Content-type before forwarding to {@link FreemarkerServlet}.</li>
+ * <li><strong>{@value #INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE}</strong> (since 2.3.24): Specifies if we should
+ * always set the {@code contentType} in the {@link HttpServletResponse} to the value of the
+ * {@value #INIT_PARAM_CONTENT_TYPE} init-param (or to its default, {@code text/html}), or only if it wasn't already set
+ * (i.e., {@link HttpServletResponse#getContentType()} returns {@code null}). The default is {@code true}. Setting this
+ * to {@code false} allows you to specify the content type before forwarding to {@link FreemarkerServlet}.</li>
  *
  * <li><strong>{@value #INIT_PARAM_BUFFER_SIZE}</strong>: Sets the size of the output buffer in bytes, or if "KB" or
  * "MB" is written after the number (like {@code <param-value>256 KB</param-value>}) then in kilobytes or megabytes.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3bf80f67/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index da26c70..58bfc7a 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -26038,6 +26038,20 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para>New <literal>FreemarkerServlet</literal> init-param:
+              <literal>OverrideResponseContentType</literal>. This specifies
+              if we should always set the <literal>contentType</literal> in
+              the <literal>HttpServletResponse</literal> (to the value of the
+              to the value of the <literal>ContentType</literal> init-param),
+              or only if it wasn't already set. The default is
+              <literal>true</literal>, which gives the backward compatible
+              behavior. Now that this init-param exists, you can disable this
+              behavior, so the <literal>contentType</literal> you have
+              specified before forwarding to
+              <literal>FreemarkerServlet</literal> wins.</para>
+            </listitem>
+
+            <listitem>
               <para>Added
               <literal>freemarker.cache.ByteArrayTemplateLoader</literal>,
               which is similar to <literal>StringTemplateLoader</literal>, but


[21/25] incubator-freemarker git commit: More helpful warning log message when no ObjectWrapper was specified for the TaglibFactory.

Posted by dd...@apache.org.
More helpful warning log message when no ObjectWrapper was specified for the TaglibFactory.


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

Branch: refs/heads/2.3
Commit: 947428ae9c42e6a1aa8df82bfd95efc3938362c9
Parents: 52a618e
Author: ddekany <dd...@apache.org>
Authored: Tue Nov 24 22:36:53 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Tue Nov 24 22:36:53 2015 +0100

----------------------------------------------------------------------
 src/main/java/freemarker/ext/jsp/TaglibFactory.java | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/947428ae/src/main/java/freemarker/ext/jsp/TaglibFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/jsp/TaglibFactory.java b/src/main/java/freemarker/ext/jsp/TaglibFactory.java
index 73f0414..0be4d9d 100644
--- a/src/main/java/freemarker/ext/jsp/TaglibFactory.java
+++ b/src/main/java/freemarker/ext/jsp/TaglibFactory.java
@@ -1641,7 +1641,8 @@ public class TaglibFactory implements TemplateHashModel {
                 if (LOG.isWarnEnabled()) {
                     LOG.warn("Custom EL functions won't be loaded because "
                             + (wrapper == null
-                                    ? "no ObjectWrapper was specified "
+                                    ? "no ObjectWrapper was specified for the TaglibFactory "
+                                            + "(via TaglibFactory.setObjectWrapper(...), exists since 2.3.22)"
                                     : "the ObjectWrapper wasn't instance of " + BeansWrapper.class.getName())
                             + ".");
                 }


[05/25] incubator-freemarker git commit: Continued FREEMARKER-1 "Option to not to overwrite response ContentType in FreemarkerServlet": <#ftl attributes={ 'content_type': 'text/plain' }> wins even if content type overriding is disabled.

Posted by dd...@apache.org.
Continued FREEMARKER-1 "Option to not to overwrite response ContentType in FreemarkerServlet": <#ftl attributes={ 'content_type': 'text/plain' }> wins even if content type overriding is disabled.


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

Branch: refs/heads/2.3
Commit: 9e7f359a164112c813377a654fcd361405c5edd4
Parents: 64ec04b
Author: ddekany <dd...@apache.org>
Authored: Sat Oct 17 19:41:13 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sat Oct 17 19:41:13 2015 +0200

----------------------------------------------------------------------
 .../ext/servlet/FreemarkerServlet.java          | 10 +++----
 .../ext/servlet/FreemarkerServletTest.java      | 29 ++++++++++++++++++++
 .../freemarker/ext/servlet/contentTypeAttr.ftl  | 20 ++++++++++++++
 3 files changed, 54 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9e7f359a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
index 929bde4..841c8b1 100644
--- a/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
+++ b/src/main/java/freemarker/ext/servlet/FreemarkerServlet.java
@@ -725,11 +725,11 @@ public class FreemarkerServlet extends HttpServlet {
                     "Unexpected error when loading template " + StringUtil.jQuoteNoXSS(templatePath) + ".", e);
         }
 
-        if (overrideResponseContentType || response.getContentType() == null) {
-            Object attrContentType = template.getCustomAttribute("content_type");
-            if (attrContentType != null) {
-                response.setContentType(attrContentType.toString());
-            } else {
+        Object attrContentType = template.getCustomAttribute("content_type");
+        if (attrContentType != null) {
+            response.setContentType(attrContentType.toString());
+        } else {
+            if (overrideResponseContentType || response.getContentType() == null) {
                 if (noCharsetInContentType) {
                     response.setContentType(contentType + "; charset=" + template.getEncoding());
                 } else {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9e7f359a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
index 20ac78d..9239ee0 100644
--- a/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
+++ b/src/test/java/freemarker/ext/servlet/FreemarkerServletTest.java
@@ -169,6 +169,35 @@ public class FreemarkerServletTest {
         assertEquals(HttpServletResponse.SC_OK, response.getStatus());
         assertTrue(response.getContentType().contains("text/css"));
     }
+
+    @Test
+    public void testContentTypeInitParams_ftlAttrAlwaysWins_DefaultOverriding() throws ServletException, IOException {
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/contentTypeAttr.ftl");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        response.setContentType("application/json");
+        assertEquals("application/json", response.getContentType());
+
+        createFreemarkerServlet().doGet(request, response);
+        LOG.debug("response content: " + response.getContentAsString());
+
+        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+        assertEquals("text/plain", response.getContentType());
+    }
+    
+    @Test
+    public void testContentTypeInitParams_ftlAttrAlwaysWins_NoOverriding() throws ServletException, IOException {
+        MockHttpServletRequest request = createMockHttpServletRequest(servletContext, "/contentTypeAttr.ftl");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        response.setContentType("application/json");
+        assertEquals("application/json", response.getContentType());
+
+        createFreemarkerServlet(FreemarkerServlet.INIT_PARAM_OVERRIDE_RESPONSE_CONTENT_TYPE, "false")
+                .doGet(request, response);
+        LOG.debug("response content: " + response.getContentAsString());
+
+        assertEquals(HttpServletResponse.SC_OK, response.getStatus());
+        assertEquals("text/plain", response.getContentType());
+    }
     
     private FreemarkerServlet createFreemarkerServlet(String... initParams) throws ServletException {
         MockServletConfig servletConfig = new MockServletConfig(servletContext);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9e7f359a/src/test/resources/freemarker/ext/servlet/contentTypeAttr.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/ext/servlet/contentTypeAttr.ftl b/src/test/resources/freemarker/ext/servlet/contentTypeAttr.ftl
new file mode 100644
index 0000000..241f0b7
--- /dev/null
+++ b/src/test/resources/freemarker/ext/servlet/contentTypeAttr.ftl
@@ -0,0 +1,20 @@
+<#ftl attributes={ 'content_type': 'text/plain' }>
+<#--
+  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.
+-->
+foo
\ No newline at end of file