You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2024/01/20 05:26:36 UTC

(struts) 01/01: Merge remote-tracking branch 'origin/master' into merge-master-to-7xx-2024-01-20

This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch merge-master-to-7xx-2024-01-20
in repository https://gitbox.apache.org/repos/asf/struts.git

commit 3a821c6314e2bf1328245e6f34b66ecc10d4790a
Merge: 273eb1f6a a763071d2
Author: Lukasz Lenart <lu...@apache.org>
AuthorDate: Sat Jan 20 06:19:47 2024 +0100

    Merge remote-tracking branch 'origin/master' into merge-master-to-7xx-2024-01-20
    
    # Conflicts:
    #       Jenkinsfile
    #       core/src/main/java/org/apache/struts2/dispatcher/MockDispatcher.java
    #       core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java
    #       core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java
    #       plugins/dwr/src/main/java/org/apache/struts2/validators/DWRValidator.java
    #       plugins/portlet/src/main/java/org/apache/struts2/portlet/PortletApplicationMap.java
    #       plugins/portlet/src/main/java/org/apache/struts2/portlet/dispatcher/Jsr168Dispatcher.java
    #       plugins/portlet/src/test/java/org/apache/struts2/StrutsTestCasePortletTests.java
    #       plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/OldDecorator2NewStrutsDecorator.java

 .github/workflows/scorecards-analysis.yaml         |   2 +-
 Jenkinsfile                                        |  23 +-
 assembly/src/main/assembly/min-lib.xml             |   2 +
 .../opensymphony/xwork2/XWorkJUnit4TestCase.java   |   4 -
 .../com/opensymphony/xwork2/XWorkTestCase.java     |  14 +-
 .../xwork2/config/impl/DefaultConfiguration.java   |  11 +-
 .../StrutsDefaultConfigurationProvider.java        |   4 -
 .../xwork2/ognl/OgnlValueStackFactory.java         |  75 +++-
 .../xwork2/security/AcceptedPatternsChecker.java   |  12 +-
 .../xwork2/security/ExcludedPatternsChecker.java   |  12 +-
 .../java/org/apache/struts2/StrutsConstants.java   |   3 +
 .../java/org/apache/struts2/components/Radio.java  |   8 -
 .../config/StrutsBeanSelectionProvider.java        |   2 +
 .../apache/struts2/dispatcher/ApplicationMap.java  |   9 +-
 .../apache/struts2/dispatcher/ContainerHolder.java |  12 +-
 .../org/apache/struts2/dispatcher/Dispatcher.java  | 159 ++++---
 .../struts2/dispatcher/ExecuteOperations.java      |   4 +-
 .../apache/struts2/dispatcher/InitOperations.java  |   2 +-
 .../org/apache/struts2/dispatcher/Parameter.java   |   8 +-
 .../struts2/dispatcher/PrepareOperations.java      |   8 +-
 .../org/apache/struts2/dispatcher/RequestMap.java  |   9 +-
 .../ActionMappingParametersInterceptor.java        |   6 +-
 .../struts2/interceptor/csp/CspInterceptor.java    |  43 +-
 .../parameter/ParametersInterceptor.java           | 274 ++++++------
 .../apache/struts2/util/StrutsTestCaseHelper.java  |  25 +-
 core/src/main/resources/struts-beans.xml           |   5 +
 .../src/main/resources/template/simple/a-close.ftl |   3 +
 .../xwork2/ognl/OgnlValueStackTest.java            | 115 ++---
 .../org/apache/struts2/StrutsInternalTestCase.java |  28 +-
 .../struts2/StrutsJUnit4InternalTestCase.java      |  61 ++-
 .../test/java/org/apache/struts2/TestAction.java   |   9 +
 .../org/apache/struts2/config/SettingsTest.java    |   2 +-
 .../apache/struts2/dispatcher/DispatcherTest.java  | 483 ++++++++++-----------
 .../struts2/interceptor/CspInterceptorTest.java    |  51 ++-
 .../exec/StrutsBackgroundProcessTest.java          |   2 +-
 .../parameter/ParametersInterceptorTest.java       |   2 +-
 .../apache/struts2/views/jsp/AbstractTagTest.java  |   6 +-
 .../apache/struts2/views/jsp/ui/AnchorTest.java    |  17 +
 .../org/apache/struts2/views/jsp/ui/RadioTest.java |  37 +-
 .../org/apache/struts2/views/jsp/ui/Radio-11.txt   |   4 +
 .../org/apache/struts2/views/jsp/ui/href-6.txt     |   1 +
 core/src/test/resources/struts.properties          |   2 +-
 .../apache/struts2/junit/StrutsJUnit4TestCase.java |  10 +-
 .../org/apache/struts2/junit/StrutsTestCase.java   |  11 +-
 .../sitemesh/OldDecorator2NewStrutsDecorator.java  | 403 ++++++++---------
 .../org/apache/struts2/testng/StrutsTestCase.java  |  12 +-
 pom.xml                                            |   4 +-
 47 files changed, 1087 insertions(+), 912 deletions(-)

diff --cc assembly/src/main/assembly/min-lib.xml
index 9f85bf1a1,a88b175fd..9c51fb418
--- a/assembly/src/main/assembly/min-lib.xml
+++ b/assembly/src/main/assembly/min-lib.xml
@@@ -39,8 -39,10 +39,10 @@@
          <include>org.apache.commons:commons-text</include>
          <include>org.apache.logging.log4j:log4j-api</include>
          <include>ognl:ognl</include>
 -        <include>commons-fileupload:commons-fileupload</include>
 +        <include>org.apache.commons:commons-fileupload2-jakarta</include>
          <include>org.apache.commons:commons-io</include>
+         <include>com.github.ben-manes.caffeine:caffeine</include>
+         <include>org.javassist:javassist</include>
        </includes>
      </dependencySet>
    </dependencySets>
diff --cc core/src/main/java/org/apache/struts2/dispatcher/ExecuteOperations.java
index cf5cb1827,769ca0a39..81418153e
--- a/core/src/main/java/org/apache/struts2/dispatcher/ExecuteOperations.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/ExecuteOperations.java
@@@ -18,12 -18,12 +18,12 @@@
   */
  package org.apache.struts2.dispatcher;
  
- import org.apache.struts2.dispatcher.mapper.ActionMapping;
  import org.apache.struts2.RequestUtils;
+ import org.apache.struts2.dispatcher.mapper.ActionMapping;
  
 -import javax.servlet.ServletException;
 -import javax.servlet.http.HttpServletRequest;
 -import javax.servlet.http.HttpServletResponse;
 +import jakarta.servlet.ServletException;
 +import jakarta.servlet.http.HttpServletRequest;
 +import jakarta.servlet.http.HttpServletResponse;
  import java.io.IOException;
  
  /**
diff --cc core/src/main/java/org/apache/struts2/dispatcher/PrepareOperations.java
index 88352b0ad,6888c5b7a..95e0f2198
--- a/core/src/main/java/org/apache/struts2/dispatcher/PrepareOperations.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/PrepareOperations.java
@@@ -26,12 -25,11 +25,11 @@@ import org.apache.logging.log4j.Logger
  import org.apache.struts2.RequestUtils;
  import org.apache.struts2.ServletActionContext;
  import org.apache.struts2.StrutsException;
- import org.apache.struts2.dispatcher.mapper.ActionMapper;
  import org.apache.struts2.dispatcher.mapper.ActionMapping;
  
 -import javax.servlet.ServletException;
 -import javax.servlet.http.HttpServletRequest;
 -import javax.servlet.http.HttpServletResponse;
 +import jakarta.servlet.ServletException;
 +import jakarta.servlet.http.HttpServletRequest;
 +import jakarta.servlet.http.HttpServletResponse;
  import java.io.IOException;
  import java.util.HashMap;
  import java.util.List;
diff --cc core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java
index b7d39bfcf,418db330c..aa799ebe7
--- a/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java
+++ b/core/src/main/java/org/apache/struts2/util/StrutsTestCaseHelper.java
@@@ -25,22 -25,20 +25,21 @@@ import com.opensymphony.xwork2.util.Val
  import org.apache.struts2.dispatcher.Dispatcher;
  import org.apache.struts2.dispatcher.DispatcherErrorHandler;
  
 -import javax.servlet.ServletContext;
 -import javax.servlet.http.HttpServletRequest;
 -import javax.servlet.http.HttpServletResponse;
 +import jakarta.servlet.ServletContext;
 +import jakarta.servlet.http.HttpServletRequest;
 +import jakarta.servlet.http.HttpServletResponse;
 +import java.util.HashMap;
  import java.util.Map;
  
+ import static java.util.Collections.emptyMap;
+ 
  /**
-  * Generic test setup methods to be used with any unit testing framework. 
+  * Generic test setup methods to be used with any unit testing framework.
   */
  public class StrutsTestCaseHelper {
-     
-     public static Dispatcher initDispatcher(ServletContext ctx, Map<String,String> params) {
-         if (params == null) {
-             params = new HashMap<>();
-         }
-         Dispatcher du = new DispatcherWrapper(ctx, params);
+ 
+     public static Dispatcher initDispatcher(ServletContext ctx, Map<String, String> params) {
+         Dispatcher du = new DispatcherWrapper(ctx, params != null ? params : emptyMap());
          du.init();
          Dispatcher.setInstance(du);
  
diff --cc core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java
index 03aa83e02,888466720..aabe90e70
--- a/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java
+++ b/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java
@@@ -44,9 -46,9 +46,10 @@@ import org.springframework.mock.web.Moc
  import org.springframework.mock.web.MockHttpSession;
  import org.springframework.mock.web.MockServletContext;
  
 -import javax.servlet.http.HttpServletRequest;
 -import javax.servlet.http.HttpServletResponse;
 +import jakarta.servlet.http.HttpServletRequest;
 +import jakarta.servlet.http.HttpServletResponse;
- import java.util.Collections;
++
+ import java.nio.charset.StandardCharsets;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Locale;
diff --cc core/src/test/java/org/apache/struts2/views/jsp/AbstractTagTest.java
index 4d3899b61,59afa3a1c..2cf04791d
--- a/core/src/test/java/org/apache/struts2/views/jsp/AbstractTagTest.java
+++ b/core/src/test/java/org/apache/struts2/views/jsp/AbstractTagTest.java
@@@ -37,14 -37,14 +37,14 @@@ import org.apache.struts2.dispatcher.Ht
  import org.apache.struts2.dispatcher.RequestMap;
  import org.apache.struts2.dispatcher.SessionMap;
  
 -import javax.servlet.http.HttpServletResponse;
 -import javax.servlet.jsp.JspWriter;
 +import jakarta.servlet.http.HttpServletResponse;
 +import jakarta.servlet.jsp.JspWriter;
  import java.io.File;
  import java.io.StringWriter;
- import java.util.HashMap;
  import java.util.Map;
  
+ import static java.util.Collections.emptyMap;
+ 
  /**
   * Base class to extend for unit testing UI Tags.
   */
@@@ -104,11 -104,14 +104,11 @@@ public abstract class AbstractTagTest e
          servletContext.setRealPath(new File("nosuchfile.properties").getAbsolutePath());
          servletContext.setServletInfo("Resin");
  
 -        pageContext = new StrutsMockPageContext();
 -        pageContext.setRequest(request);
 -        pageContext.setResponse(response);
 +        pageContext = new StrutsMockPageContext(servletContext, request, response);
          pageContext.setJspWriter(jspWriter);
 -        pageContext.setServletContext(servletContext);
 -
 +        
          mockContainer = new Mock(Container.class);
-         MockDispatcher du = new MockDispatcher(pageContext.getServletContext(), new HashMap<>(), configurationManager);
+         Dispatcher du = new Dispatcher(pageContext.getServletContext(), emptyMap());
          du.init();
          Dispatcher.setInstance(du);
          session = new SessionMap(request);
diff --cc plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/OldDecorator2NewStrutsDecorator.java
index e435ccc0d,989141044..37b033aa8
--- a/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/OldDecorator2NewStrutsDecorator.java
+++ b/plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/OldDecorator2NewStrutsDecorator.java
@@@ -1,199 -1,204 +1,204 @@@
- /*
-  * Licensed to the Apache Software Foundation (ASF) under one
-  * or more contributor license agreements.  See the NOTICE file
-  * distributed with this work for additional information
-  * regarding copyright ownership.  The ASF licenses this file
-  * to you under the Apache License, Version 2.0 (the
-  * "License"); you may not use this file except in compliance
-  * with the License.  You may obtain a copy of the License at
-  *
-  *  http://www.apache.org/licenses/LICENSE-2.0
-  *
-  * Unless required by applicable law or agreed to in writing,
-  * software distributed under the License is distributed on an
-  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-  * KIND, either express or implied.  See the License for the
-  * specific language governing permissions and limitations
-  * under the License.
-  */
- package org.apache.struts2.sitemesh;
- 
- import com.opensymphony.module.sitemesh.RequestConstants;
- import com.opensymphony.sitemesh.Content;
- import com.opensymphony.sitemesh.webapp.SiteMeshWebAppContext;
- import com.opensymphony.sitemesh.webapp.decorator.BaseWebAppDecorator;
- import com.opensymphony.xwork2.*;
- import com.opensymphony.xwork2.interceptor.PreResultListener;
- import com.opensymphony.xwork2.util.ValueStack;
- import com.opensymphony.xwork2.util.ValueStackFactory;
- import freemarker.template.Configuration;
- import org.apache.struts2.ServletActionContext;
- import org.apache.struts2.dispatcher.Dispatcher;
- 
- import jakarta.servlet.ServletContext;
- import jakarta.servlet.ServletException;
- import jakarta.servlet.http.HttpServletRequest;
- import jakarta.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.util.Locale;
- 
- /**
-  * Adapts a SiteMesh 2 {@link com.opensymphony.module.sitemesh.Decorator} to a
-  * SiteMesh 3 {@link com.opensymphony.sitemesh.Decorator}.
-  *
-  * @since SiteMesh 3
-  */
- public abstract class OldDecorator2NewStrutsDecorator extends BaseWebAppDecorator implements RequestConstants {
- 
-     protected com.opensymphony.module.sitemesh.Decorator oldDecorator;
-     private static String customEncoding;
- 
-     public OldDecorator2NewStrutsDecorator(com.opensymphony.module.sitemesh.Decorator oldDecorator) {
-         this.oldDecorator = oldDecorator;
-     }
- 
-     public OldDecorator2NewStrutsDecorator() {
-         oldDecorator = null;
-     }
- 
- 
-     /**
-      * Applies the decorator, using the relevent contexts
-      *
-      * @param content        The content
-      * @param request        The servlet request
-      * @param response       The servlet response
-      * @param servletContext The servlet context
-      * @param ctx            The action context for this request, populated with the server state
-      */
-     protected abstract void render(Content content, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, ActionContext ctx) throws ServletException, IOException;
- 
-     /**
-      * Applies the decorator, creating the relevent contexts and delegating to
-      * the extended applyDecorator().
-      *
-      * @param content        The content
-      * @param request        The servlet request
-      * @param response       The servlet response
-      * @param servletContext The servlet context
-      * @param webAppContext  The web app context
-      */
- 
-     protected void render(Content content, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, SiteMeshWebAppContext webAppContext) throws IOException, ServletException {
- 
-         // see if the URI path (webapp) is set
-         if (oldDecorator.getURIPath() != null) {
-             // in a security conscious environment, the servlet container
-             // may return null for a given URL
-             if (servletContext.getContext(oldDecorator.getURIPath()) != null) {
-                 servletContext = servletContext.getContext(oldDecorator.getURIPath());
-             }
-         }
- 
-         ActionContext ctx = ServletActionContext.getActionContext(request);
-         if (ctx == null) {
-             // ok, one isn't associated with the request, so let's create one using the current Dispatcher
-             ValueStack vs = Dispatcher.getInstance().getContainer().getInstance(ValueStackFactory.class).createValueStack();
-             vs.getContext().putAll(Dispatcher.getInstance().createContextMap(request, response, null));
-             ctx = ActionContext.of(vs.getContext());
-             if (ctx.getActionInvocation() == null) {
-                 // put in a dummy ActionSupport so basic functionality still works
-                 ActionSupport action = new ActionSupport();
-                 vs.push(action);
-                 ctx.withActionInvocation(new DummyActionInvocation(action));
-             }
-         }
- 
-         // delegate to the actual page decorator
-         render(content, request, response, servletContext, ctx);
- 
-     }
- 
-     /**
-      * Returns the locale used for the {@link freemarker.template.Configuration#getTemplate(String, java.util.Locale)} call. The base implementation
-      * simply returns the locale setting of the action (assuming the action implements {@link LocaleProvider}) or, if
-      * the action does not the configuration's locale is returned. Override this method to provide different behaviour,
-      */
-     protected Locale getLocale(ActionInvocation invocation, Configuration configuration) {
-         if (invocation.getAction() instanceof LocaleProvider) {
-             return ((LocaleProvider) invocation.getAction()).getLocale();
-         } else {
-             return configuration.getLocale();
-         }
-     }
- 
- 
-     /**
-      * Gets the L18N encoding of the system.  The default is UTF-8.
-      */
-     protected String getEncoding() {
-         String encoding = customEncoding;
-         if (encoding == null) {
-             encoding = System.getProperty("file.encoding");
-         }
-         if (encoding == null) {
-             encoding = "UTF-8";
-         }
-         return encoding;
-     }
- 
- 
-     static class DummyActionInvocation implements ActionInvocation {
- 
-         ActionSupport action;
- 
-         public DummyActionInvocation(ActionSupport action) {
-             this.action = action;
-         }
- 
-         public Object getAction() {
-             return action;
-         }
- 
-         public boolean isExecuted() {
-             return false;
-         }
- 
-         public ActionContext getInvocationContext() {
-             return null;
-         }
- 
-         public ActionProxy getProxy() {
-             return null;
-         }
- 
-         public Result getResult() throws Exception {
-             return null;
-         }
- 
-         public String getResultCode() {
-             return null;
-         }
- 
-         public void setResultCode(String resultCode) {
-         }
- 
-         public ValueStack getStack() {
-             return null;
-         }
- 
-         public void addPreResultListener(PreResultListener listener) {
-         }
- 
-         public String invoke() throws Exception {
-             return null;
-         }
- 
-         public String invokeActionOnly() throws Exception {
-             return null;
-         }
- 
-         public void setActionEventListener(ActionEventListener listener) {
-         }
- 
-         public void init(ActionProxy proxy) {
-         }
- 
-     }
- 
- }
+ /*
+  * Licensed to the Apache Software Foundation (ASF) under one
+  * or more contributor license agreements.  See the NOTICE file
+  * distributed with this work for additional information
+  * regarding copyright ownership.  The ASF licenses this file
+  * to you under the Apache License, Version 2.0 (the
+  * "License"); you may not use this file except in compliance
+  * with the License.  You may obtain a copy of the License at
+  *
+  *  http://www.apache.org/licenses/LICENSE-2.0
+  *
+  * Unless required by applicable law or agreed to in writing,
+  * software distributed under the License is distributed on an
+  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  * KIND, either express or implied.  See the License for the
+  * specific language governing permissions and limitations
+  * under the License.
+  */
+ package org.apache.struts2.sitemesh;
+ 
+ import com.opensymphony.module.sitemesh.RequestConstants;
+ import com.opensymphony.sitemesh.Content;
+ import com.opensymphony.sitemesh.webapp.SiteMeshWebAppContext;
+ import com.opensymphony.sitemesh.webapp.decorator.BaseWebAppDecorator;
+ import com.opensymphony.xwork2.ActionContext;
+ import com.opensymphony.xwork2.ActionEventListener;
+ import com.opensymphony.xwork2.ActionInvocation;
+ import com.opensymphony.xwork2.ActionProxy;
+ import com.opensymphony.xwork2.ActionSupport;
+ import com.opensymphony.xwork2.LocaleProvider;
+ import com.opensymphony.xwork2.Result;
+ import com.opensymphony.xwork2.interceptor.PreResultListener;
+ import com.opensymphony.xwork2.util.ValueStack;
+ import freemarker.template.Configuration;
+ import org.apache.struts2.ServletActionContext;
+ import org.apache.struts2.dispatcher.Dispatcher;
+ 
 -import javax.servlet.ServletContext;
 -import javax.servlet.ServletException;
 -import javax.servlet.http.HttpServletRequest;
 -import javax.servlet.http.HttpServletResponse;
++import jakarta.servlet.ServletContext;
++import jakarta.servlet.ServletException;
++import jakarta.servlet.http.HttpServletRequest;
++import jakarta.servlet.http.HttpServletResponse;
+ import java.io.IOException;
+ import java.util.Locale;
+ 
+ /**
+  * Adapts a SiteMesh 2 {@link com.opensymphony.module.sitemesh.Decorator} to a
+  * SiteMesh 3 {@link com.opensymphony.sitemesh.Decorator}.
+  *
+  * @since SiteMesh 3
+  */
+ public abstract class OldDecorator2NewStrutsDecorator extends BaseWebAppDecorator implements RequestConstants {
+ 
+     protected com.opensymphony.module.sitemesh.Decorator oldDecorator;
+     private static String customEncoding;
+ 
+     public OldDecorator2NewStrutsDecorator(com.opensymphony.module.sitemesh.Decorator oldDecorator) {
+         this.oldDecorator = oldDecorator;
+     }
+ 
+     public OldDecorator2NewStrutsDecorator() {
+         oldDecorator = null;
+     }
+ 
+ 
+     /**
+      * Applies the decorator, using the relevent contexts
+      *
+      * @param content        The content
+      * @param request        The servlet request
+      * @param response       The servlet response
+      * @param servletContext The servlet context
+      * @param ctx            The action context for this request, populated with the server state
+      */
+     protected abstract void render(Content content, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, ActionContext ctx) throws ServletException, IOException;
+ 
+     /**
+      * Applies the decorator, creating the relevent contexts and delegating to
+      * the extended applyDecorator().
+      *
+      * @param content        The content
+      * @param request        The servlet request
+      * @param response       The servlet response
+      * @param servletContext The servlet context
+      * @param webAppContext  The web app context
+      */
+ 
+     protected void render(Content content, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, SiteMeshWebAppContext webAppContext) throws IOException, ServletException {
+ 
+         // see if the URI path (webapp) is set
+         if (oldDecorator.getURIPath() != null) {
+             // in a security conscious environment, the servlet container
+             // may return null for a given URL
+             if (servletContext.getContext(oldDecorator.getURIPath()) != null) {
+                 servletContext = servletContext.getContext(oldDecorator.getURIPath());
+             }
+         }
+ 
+         ActionContext ctx = ServletActionContext.getActionContext(request);
+         if (ctx == null) {
+             // ok, one isn't associated with the request, so let's create one using the current Dispatcher
+             ValueStack vs = Dispatcher.getInstance().getValueStackFactory().createValueStack();
+             vs.getContext().putAll(Dispatcher.getInstance().createContextMap(request, response, null));
+             ctx = ActionContext.of(vs.getContext());
+             if (ctx.getActionInvocation() == null) {
+                 // put in a dummy ActionSupport so basic functionality still works
+                 ActionSupport action = new ActionSupport();
+                 vs.push(action);
+                 ctx.withActionInvocation(new DummyActionInvocation(action));
+             }
+         }
+ 
+         // delegate to the actual page decorator
+         render(content, request, response, servletContext, ctx);
+ 
+     }
+ 
+     /**
+      * Returns the locale used for the {@link freemarker.template.Configuration#getTemplate(String, java.util.Locale)} call. The base implementation
+      * simply returns the locale setting of the action (assuming the action implements {@link LocaleProvider}) or, if
+      * the action does not the configuration's locale is returned. Override this method to provide different behaviour,
+      */
+     protected Locale getLocale(ActionInvocation invocation, Configuration configuration) {
+         if (invocation.getAction() instanceof LocaleProvider) {
+             return ((LocaleProvider) invocation.getAction()).getLocale();
+         } else {
+             return configuration.getLocale();
+         }
+     }
+ 
+ 
+     /**
+      * Gets the L18N encoding of the system.  The default is UTF-8.
+      */
+     protected String getEncoding() {
+         String encoding = customEncoding;
+         if (encoding == null) {
+             encoding = System.getProperty("file.encoding");
+         }
+         if (encoding == null) {
+             encoding = "UTF-8";
+         }
+         return encoding;
+     }
+ 
+ 
+     static class DummyActionInvocation implements ActionInvocation {
+ 
+         ActionSupport action;
+ 
+         public DummyActionInvocation(ActionSupport action) {
+             this.action = action;
+         }
+ 
+         public Object getAction() {
+             return action;
+         }
+ 
+         public boolean isExecuted() {
+             return false;
+         }
+ 
+         public ActionContext getInvocationContext() {
+             return null;
+         }
+ 
+         public ActionProxy getProxy() {
+             return null;
+         }
+ 
+         public Result getResult() throws Exception {
+             return null;
+         }
+ 
+         public String getResultCode() {
+             return null;
+         }
+ 
+         public void setResultCode(String resultCode) {
+         }
+ 
+         public ValueStack getStack() {
+             return null;
+         }
+ 
+         public void addPreResultListener(PreResultListener listener) {
+         }
+ 
+         public String invoke() throws Exception {
+             return null;
+         }
+ 
+         public String invokeActionOnly() throws Exception {
+             return null;
+         }
+ 
+         public void setActionEventListener(ActionEventListener listener) {
+         }
+ 
+         public void init(ActionProxy proxy) {
+         }
+ 
+     }
+ 
+ }