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

kusal pushed a commit to branch kusal-depr-apis-7
in repository https://gitbox.apache.org/repos/asf/struts.git

commit e4d2877b609d11329f076c1bb582fd7d02a65040
Author: Kusal Kithul-Godage <g...@kusal.io>
AuthorDate: Thu Oct 17 17:53:30 2024 +1100

    WW-3714 Deprecate and migrate assorted Interceptors
---
 .../xwork2/conversion/impl/XWorkConverter.java     |  19 +-
 .../interceptor/PrefixMethodInvocationUtil.java    |  51 ++--
 .../opensymphony/xwork2/util/TextParseUtil.java    |   3 +
 .../xwork2/util/ValueStackFactory.java             |   7 +-
 .../struts2/interceptor/AliasInterceptor.java      | 294 +++++++++++++++++++
 .../struts2/interceptor/ChainingInterceptor.java   | 275 +++++++++++++++++
 .../interceptor/ConversionErrorInterceptor.java    | 150 ++++++++++
 .../interceptor/DefaultWorkflowInterceptor.java    | 248 ++++++++++++++++
 .../interceptor/ExceptionMappingInterceptor.java   | 324 +++++++++++++++++++++
 .../struts2/interceptor/LoggingInterceptor.java    |  90 ++++++
 .../interceptor/ModelDrivenInterceptor.java        | 148 ++++++++++
 .../interceptor/ParameterRemoverInterceptor.java   | 124 ++++++++
 .../struts2/interceptor/PrepareInterceptor.java    | 177 +++++++++++
 .../interceptor/ScopedModelDrivenInterceptor.java  | 166 +++++++++++
 .../interceptor/StaticParametersInterceptor.java   | 243 ++++++++++++++++
 15 files changed, 2290 insertions(+), 29 deletions(-)

diff --git 
a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java
 
b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java
index 3cda3e34d..5d16d0e4d 100644
--- 
a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java
+++ 
b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java
@@ -18,12 +18,21 @@
  */
 package com.opensymphony.xwork2.conversion.impl;
 
-import com.opensymphony.xwork2.*;
-import com.opensymphony.xwork2.conversion.*;
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.FileManager;
+import com.opensymphony.xwork2.FileManagerFactory;
+import com.opensymphony.xwork2.LocalizedTextProvider;
+import com.opensymphony.xwork2.conversion.ConversionAnnotationProcessor;
+import com.opensymphony.xwork2.conversion.ConversionFileProcessor;
+import com.opensymphony.xwork2.conversion.TypeConverter;
+import com.opensymphony.xwork2.conversion.TypeConverterHolder;
 import com.opensymphony.xwork2.conversion.annotations.Conversion;
 import com.opensymphony.xwork2.conversion.annotations.TypeConversion;
 import com.opensymphony.xwork2.inject.Inject;
-import com.opensymphony.xwork2.util.*;
+import com.opensymphony.xwork2.util.AnnotationUtils;
+import com.opensymphony.xwork2.util.ClassLoaderUtil;
+import com.opensymphony.xwork2.util.CompoundRoot;
+import com.opensymphony.xwork2.util.ValueStack;
 import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
@@ -220,6 +229,10 @@ public class XWorkConverter extends DefaultTypeConverter {
         return message;
     }
 
+    public static String getConversionErrorMessage(String propertyName, Class 
toClass, org.apache.struts2.util.ValueStack stack) {
+        return getConversionErrorMessage(propertyName, toClass, 
ValueStack.adapt(stack));
+    }
+
     private static String removeAllIndexesInPropertyName(String propertyName) {
         return propertyName.replaceAll(MESSAGE_INDEX_PATTERN, PERIOD);
     }
diff --git 
a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java
 
b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java
index 040080824..0ac840c7a 100644
--- 
a/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java
+++ 
b/core/src/main/java/com/opensymphony/xwork2/interceptor/PrefixMethodInvocationUtil.java
@@ -19,8 +19,8 @@
 package com.opensymphony.xwork2.interceptor;
 
 import com.opensymphony.xwork2.ActionInvocation;
-import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -28,7 +28,7 @@ import java.lang.reflect.Method;
 /**
  * <p>
  * A utility class for invoking prefixed methods in action class.
- * 
+ *
  * Interceptors that made use of this class are:
  * </p>
  * <ul>
@@ -37,7 +37,7 @@ import java.lang.reflect.Method;
  * </ul>
  *  *
  * <!-- START SNIPPET: javadocDefaultWorkflowInterceptor -->
- * 
+ *
  * <b>In DefaultWorkflowInterceptor</b>
  * <p>applies only when action implements {@link 
com.opensymphony.xwork2.Validateable}</p>
  * <ol>
@@ -45,12 +45,12 @@ import java.lang.reflect.Method;
  *    <li>else if the action class have validateDo{MethodName}(), it will be 
invoked</li>
  *    <li>no matter if 1] or 2] is performed, if alwaysInvokeValidate property 
of the interceptor is "true" (which is by default "true"), validate() will be 
invoked.</li>
  * </ol>
- * 
+ *
  * <!-- END SNIPPET: javadocDefaultWorkflowInterceptor -->
- * 
- * 
+ *
+ *
  * <!-- START SNIPPET: javadocPrepareInterceptor -->
- * 
+ *
  * <b>In PrepareInterceptor</b>
  * <p>Applies only when action implements Preparable</p>
  * <ol>
@@ -58,14 +58,14 @@ import java.lang.reflect.Method;
  *    <li>else if the action class have prepareDo(MethodName()}(), it will be 
invoked</li>
  *    <li>no matter if 1] or 2] is performed, if alwaysinvokePrepare property 
of the interceptor is "true" (which is by default "true"), prepare() will be 
invoked.</li>
  * </ol>
- * 
+ *
  * <!-- END SNIPPET: javadocPrepareInterceptor -->
- * 
+ *
  * @author Philip Luppens
  * @author tm_jee
  */
 public class PrefixMethodInvocationUtil {
-       
+
        private static final Logger LOG = 
LogManager.getLogger(PrefixMethodInvocationUtil.class);
 
     private static final String DEFAULT_INVOCATION_METHODNAME = "execute";
@@ -76,7 +76,7 @@ public class PrefixMethodInvocationUtil {
         * <p>
         * This method will prefix <code>actionInvocation</code>'s 
<code>ActionProxy</code>'s
         * <code>method</code> with <code>prefixes</code> before invoking the 
prefixed method.
-        * Order of the <code>prefixes</code> is important, as this method will 
return once 
+        * Order of the <code>prefixes</code> is important, as this method will 
return once
         * a prefixed method is found in the action class.
         * </p>
         *
@@ -89,7 +89,7 @@ public class PrefixMethodInvocationUtil {
         * </pre>
         *
         * <p>
-        * Assuming <code>actionInvocation.getProxy(),getMethod()</code> 
returns "submit", 
+        * Assuming <code>actionInvocation.getProxy(),getMethod()</code> 
returns "submit",
         * the order of invocation would be as follows:-
         * </p>
         *
@@ -99,12 +99,12 @@ public class PrefixMethodInvocationUtil {
         * </ol>
         *
         * <p>
-        * If <code>prepareSubmit()</code> exists, it will be invoked and this 
method 
-        * will return, <code>prepareDoSubmit()</code> will NOT be invoked. 
+        * If <code>prepareSubmit()</code> exists, it will be invoked and this 
method
+        * will return, <code>prepareDoSubmit()</code> will NOT be invoked.
         * </p>
         *
         * <p>
-        * On the other hand, if <code>prepareDoSubmit()</code> does not 
exists, and 
+        * On the other hand, if <code>prepareDoSubmit()</code> does not 
exists, and
         * <code>prepareDoSubmit()</code> exists, it will be invoked.
         * </p>
         *
@@ -119,29 +119,32 @@ public class PrefixMethodInvocationUtil {
         */
        public static void invokePrefixMethod(ActionInvocation 
actionInvocation, String[] prefixes) throws InvocationTargetException, 
IllegalAccessException {
                Object action = actionInvocation.getAction();
-               
+
                String methodName = actionInvocation.getProxy().getMethod();
-               
+
                if (methodName == null) {
-                       // if null returns (possible according to the docs), 
use the default execute 
+                       // if null returns (possible according to the docs), 
use the default execute
                methodName = DEFAULT_INVOCATION_METHODNAME;
                }
-               
+
                Method method = getPrefixedMethod(prefixes, methodName, action);
                if (method != null) {
                        method.invoke(action, new Object[0]);
                }
        }
-       
-       
+
+       public static void 
invokePrefixMethod(org.apache.struts2.ActionInvocation actionInvocation, 
String[] prefixes) throws InvocationTargetException, IllegalAccessException {
+               invokePrefixMethod(ActionInvocation.adapt(actionInvocation), 
prefixes);
+       }
+
        /**
-        * This method returns a {@link Method} in <code>action</code>. The 
method 
+        * This method returns a {@link Method} in <code>action</code>. The 
method
         * returned is found by searching for method in <code>action</code> 
whose method name
         * is equals to the result of appending each <code>prefixes</code>
         * to <code>methodName</code>. Only the first method found will be 
returned, hence
         * the order of <code>prefixes</code> is important. If none is found 
this method
         * will return null.
-        * 
+        *
         * @param prefixes the prefixes to prefix the <code>methodName</code>
         * @param methodName the method name to be prefixed with 
<code>prefixes</code>
         * @param action the action class of which the prefixed method is to be 
search for.
@@ -162,7 +165,7 @@ public class PrefixMethodInvocationUtil {
         }
                return null;
        }
-       
+
        /**
      * <p>
         * This method capitalized the first character of 
<code>methodName</code>.
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java 
b/core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java
index 2a2cad1bf..9220159bf 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/TextParseUtil.java
@@ -51,6 +51,9 @@ public class TextParseUtil {
         return translateVariables(new char[]{'$', '%'}, expression, stack, 
String.class, null).toString();
     }
 
+    public static String translateVariables(String expression, 
org.apache.struts2.util.ValueStack stack) {
+        return translateVariables(expression, ValueStack.adapt(stack));
+    }
 
     /**
      * Function similarly as {@link #translateVariables(char, String, 
ValueStack)}
diff --git 
a/core/src/main/java/com/opensymphony/xwork2/util/ValueStackFactory.java 
b/core/src/main/java/com/opensymphony/xwork2/util/ValueStackFactory.java
index 788c90453..70d072851 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/ValueStackFactory.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/ValueStackFactory.java
@@ -29,7 +29,7 @@ public interface ValueStackFactory {
      * @return  a new {@link com.opensymphony.xwork2.util.ValueStack}.
      */
     ValueStack createValueStack();
-    
+
     /**
      * Get a new instance of {@link com.opensymphony.xwork2.util.ValueStack}
      *
@@ -37,5 +37,8 @@ public interface ValueStackFactory {
      * @return  a new {@link com.opensymphony.xwork2.util.ValueStack}.
      */
     ValueStack createValueStack(ValueStack stack);
-    
+
+    default ValueStack createValueStack(org.apache.struts2.util.ValueStack 
stack) {
+        return createValueStack(ValueStack.adapt(stack));
+    }
 }
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java 
b/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java
new file mode 100644
index 000000000..7fa112a83
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/AliasInterceptor.java
@@ -0,0 +1,294 @@
+/*
+ * 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.interceptor;
+
+import com.opensymphony.xwork2.LocalizedTextProvider;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.interceptor.ParametersInterceptor;
+import com.opensymphony.xwork2.interceptor.ValidationAware;
+import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
+import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
+import com.opensymphony.xwork2.util.ClearableValueStack;
+import com.opensymphony.xwork2.util.Evaluated;
+import com.opensymphony.xwork2.util.ValueStackFactory;
+import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionContext;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.dispatcher.HttpParameters;
+import org.apache.struts2.dispatcher.Parameter;
+import org.apache.struts2.util.ValueStack;
+
+import java.util.Map;
+
+
+/**
+ * <!-- START SNIPPET: description -->
+ *
+ * The aim of this Interceptor is to alias a named parameter to a different 
named parameter. By acting as the glue
+ * between actions sharing similar parameters (but with different names), it 
can help greatly with action chaining.
+ *
+ * <p>Action's alias expressions should be in the form of  <code>#{ "name1" : 
"alias1", "name2" : "alias2" }</code>.
+ * This means that assuming an action (or something else in the stack) has a 
value for the expression named <i>name1</i> and the
+ * action this interceptor is applied to has a setter named <i>alias1</i>, 
<i>alias1</i> will be set with the value from
+ * <i>name1</i>.
+ * </p>
+ *
+ * <!-- END SNIPPET: description -->
+ *
+ * <p><u>Interceptor parameters:</u></p>
+ *
+ * <!-- START SNIPPET: parameters -->
+ *
+ * <ul>
+ *
+ * <li>aliasesKey (optional) - the name of the action parameter to look for 
the alias map (by default this is
+ * <i>aliases</i>).</li>
+ *
+ * </ul>
+ *
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <p><u>Extending the interceptor:</u></p>
+ *
+ * <!-- START SNIPPET: extending -->
+ *
+ * This interceptor does not have any known extension points.
+ *
+ * <!-- END SNIPPET: extending -->
+ *
+ * <p><u>Example code:</u></p>
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
+ *     &lt;!-- The value for the foo parameter will be applied as if it were 
named bar --&gt;
+ *     &lt;param name="aliases"&gt;#{ 'foo' : 'bar' }&lt;/param&gt;
+ *
+ *     &lt;interceptor-ref name="alias"/&gt;
+ *     &lt;interceptor-ref name="basicStack"/&gt;
+ *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
+ * &lt;/action&gt;
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @author Matthew Payne
+ */
+public class AliasInterceptor extends AbstractInterceptor {
+
+    private static final Logger LOG = 
LogManager.getLogger(AliasInterceptor.class);
+
+    private static final String DEFAULT_ALIAS_KEY = "aliases";
+    protected String aliasesKey = DEFAULT_ALIAS_KEY;
+
+    protected ValueStackFactory valueStackFactory;
+    protected LocalizedTextProvider localizedTextProvider;
+    protected boolean devMode = false;
+
+    private ExcludedPatternsChecker excludedPatterns;
+    private AcceptedPatternsChecker acceptedPatterns;
+
+    @Inject(StrutsConstants.STRUTS_DEVMODE)
+    public void setDevMode(String mode) {
+        this.devMode = Boolean.parseBoolean(mode);
+    }
+
+    @Inject
+    public void setValueStackFactory(ValueStackFactory valueStackFactory) {
+        this.valueStackFactory = valueStackFactory;
+    }
+
+    @Inject
+    public void setLocalizedTextProvider(LocalizedTextProvider 
localizedTextProvider) {
+        this.localizedTextProvider = localizedTextProvider;
+    }
+
+    @Inject
+    public void setExcludedPatterns(ExcludedPatternsChecker excludedPatterns) {
+        this.excludedPatterns = excludedPatterns;
+    }
+
+    @Inject
+    public void setAcceptedPatterns(AcceptedPatternsChecker acceptedPatterns) {
+        this.acceptedPatterns = acceptedPatterns;
+    }
+
+    /**
+     * <p>
+     * Sets the name of the action parameter to look for the alias map.
+     * </p>
+     *
+     * <p>
+     * Default is <code>aliases</code>.
+     * </p>
+     *
+     * @param aliasesKey  the name of the action parameter
+     */
+    public void setAliasesKey(String aliasesKey) {
+        this.aliasesKey = aliasesKey;
+    }
+
+    @Override public String intercept(ActionInvocation invocation) throws 
Exception {
+
+        ActionConfig config = invocation.getProxy().getConfig();
+        ActionContext ac = invocation.getInvocationContext();
+        Object action = invocation.getAction();
+
+        // get the action's parameters
+        final Map<String, String> parameters = config.getParams();
+
+        if (parameters.containsKey(aliasesKey)) {
+
+            String aliasExpression = parameters.get(aliasesKey);
+            ValueStack stack = ac.getValueStack();
+            Object obj = stack.findValue(aliasExpression);
+
+            if (obj instanceof Map) {
+                //get secure stack
+                ValueStack newStack = 
valueStackFactory.createValueStack(stack);
+                boolean clearableStack = newStack instanceof 
ClearableValueStack;
+                if (clearableStack) {
+                    //if the stack's context can be cleared, do that to 
prevent OGNL
+                    //from having access to objects in the stack, see XW-641
+                    ((ClearableValueStack)newStack).clearContextValues();
+                    Map<String, Object> context = newStack.getContext();
+                    ReflectionContextState.setCreatingNullObjects(context, 
true);
+                    ReflectionContextState.setDenyMethodExecution(context, 
true);
+                    
ReflectionContextState.setReportingConversionErrors(context, true);
+
+                    //keep locale from original context
+                    
newStack.getActionContext().withLocale(stack.getActionContext().getLocale());
+                }
+
+                // override
+                Map aliases = (Map) obj;
+                for (Object o : aliases.entrySet()) {
+                    Map.Entry entry = (Map.Entry) o;
+                    String name = entry.getKey().toString();
+                    if (isNotAcceptableExpression(name)) {
+                        continue;
+                    }
+                    String alias = (String) entry.getValue();
+                    if (isNotAcceptableExpression(alias)) {
+                        continue;
+                    }
+                    Evaluated value = new Evaluated(stack.findValue(name));
+                    if (!value.isDefined()) {
+                        // workaround
+                        HttpParameters contextParameters = 
ActionContext.getContext().getParameters();
+
+                        if (null != contextParameters) {
+                            Parameter param = contextParameters.get(name);
+                            if (param.isDefined()) {
+                                value = new Evaluated(param.getValue());
+                            }
+                        }
+                    }
+                    if (value.isDefined()) {
+                        try {
+                            newStack.setValue(alias, value.get());
+                        } catch (RuntimeException e) {
+                            if (devMode) {
+                                String developerNotification = 
localizedTextProvider.findText(ParametersInterceptor.class, 
"devmode.notification", ActionContext.getContext().getLocale(), "Developer 
Notification:\n{0}", new Object[]{
+                                        "Unexpected Exception caught setting 
'" + entry.getKey() + "' on '" + action.getClass() + ": " + e.getMessage()
+                                });
+                                LOG.error(developerNotification);
+                                if (action instanceof 
com.opensymphony.xwork2.interceptor.ValidationAware) {
+                                    ((ValidationAware) 
action).addActionMessage(developerNotification);
+                                }
+                            }
+                        }
+                    }
+                }
+
+                if (clearableStack) {
+                    
stack.getActionContext().withConversionErrors(newStack.getActionContext().getConversionErrors());
+                }
+            } else {
+                LOG.debug("invalid alias expression: {}", aliasesKey);
+            }
+        }
+
+        return invocation.invoke();
+    }
+
+    protected boolean isAccepted(String paramName) {
+        AcceptedPatternsChecker.IsAccepted result = 
acceptedPatterns.isAccepted(paramName);
+        if (result.isAccepted()) {
+            return true;
+        }
+
+        LOG.warn("Parameter [{}] didn't match accepted pattern [{}]! See 
Accepted / Excluded patterns at\n" +
+                        
"https://struts.apache.org/security/#accepted--excluded-patterns";,
+                paramName, result.getAcceptedPattern());
+
+        return false;
+    }
+
+    protected boolean isExcluded(String paramName) {
+        ExcludedPatternsChecker.IsExcluded result = 
excludedPatterns.isExcluded(paramName);
+        if (!result.isExcluded()) {
+            return false;
+        }
+
+        LOG.warn("Parameter [{}] matches excluded pattern [{}]! See Accepted / 
Excluded patterns at\n" +
+                        
"https://struts.apache.org/security/#accepted--excluded-patterns";,
+                paramName, result.getExcludedPattern());
+
+        return true;
+    }
+
+    /**
+     * Checks if expression contains vulnerable code
+     *
+     * @param expression of interceptor
+     * @return true|false
+     */
+    protected boolean isNotAcceptableExpression(String expression) {
+        return isExcluded(expression) || !isAccepted(expression);
+    }
+
+    /**
+     * Sets a comma-delimited list of regular expressions to match
+     * parameters that are allowed in the parameter map (aka whitelist).
+     * <p>
+     * Don't change the default unless you know what you are doing in terms
+     * of security implications.
+     * </p>
+     *
+     * @param commaDelim A comma-delimited list of regular expressions
+     */
+    public void setAcceptParamNames(String commaDelim) {
+        acceptedPatterns.setAcceptedPatterns(commaDelim);
+    }
+
+    /**
+     * Sets a comma-delimited list of regular expressions to match
+     * parameters that should be removed from the parameter map.
+     *
+     * @param commaDelim A comma-delimited list of regular expressions
+     */
+    public void setExcludeParams(String commaDelim) {
+        excludedPatterns.setExcludedPatterns(commaDelim);
+    }
+
+}
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java 
b/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java
new file mode 100644
index 000000000..adbb3a67a
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java
@@ -0,0 +1,275 @@
+/*
+ * 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.interceptor;
+
+import com.opensymphony.xwork2.ActionChainResult;
+import com.opensymphony.xwork2.Unchainable;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.CompoundRoot;
+import com.opensymphony.xwork2.util.ProxyUtil;
+import com.opensymphony.xwork2.util.TextParseUtil;
+import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.Result;
+import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.util.ValueStack;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * <!-- START SNIPPET: description -->
+ * <p>
+ * An interceptor that copies all the properties of every object in the value 
stack to the currently executing object,
+ * except for any object that implements {@link Unchainable}. A collection of 
optional <i>includes</i> and
+ * <i>excludes</i> may be provided to control how and which parameters are 
copied. Only includes or excludes may be
+ * specified. Specifying both results in undefined behavior. See the javadocs 
for {@link ReflectionProvider#copy(Object, Object,
+ * Map, Collection, Collection)} for more information.
+ * </p>
+ *
+ * <p>
+ * <b>Note:</b> It is important to remember that this interceptor does nothing 
if there are no objects already on the stack.
+ * <br>This means two things:
+ * <br><b>One</b>, you can safely apply it to all your actions without any 
worry of adverse affects.
+ * <br><b>Two</b>, it is up to you to ensure an object exists in the stack 
prior to invoking this action. The most typical way this is done
+ * is through the use of the <b>chain</b> result type, which combines with 
this interceptor to make up the action
+ * chaining feature.
+ * </p>
+ *
+ * <p>
+ * <b>Note:</b> By default Errors, Field errors and Message aren't copied 
during chaining, to change the behaviour you can specify
+ * the below three constants in struts.properties or struts.xml:
+ * </p>
+ *
+ * <ul>
+ * <li>struts.chaining.copyErrors - set to true to copy Action Errors</li>
+ * <li>struts.chaining.copyFieldErrors - set to true to copy Field Errors</li>
+ * <li>struts.chaining.copyMessages - set to true to copy Action Messages</li>
+ * </ul>
+ *
+ * <p>
+ * <u>Example:</u>
+ * </p>
+ *
+ * <pre>
+ * &lt;constant name="struts.xwork.chaining.copyErrors" value="true"/&gt;
+ * </pre>
+ *
+ * <p>
+ * <b>Note:</b> By default actionErrors and actionMessages are excluded when 
copping object's properties.
+ * </p>
+ * <!-- END SNIPPET: description -->
+ * <u>Interceptor parameters:</u>
+ * <!-- START SNIPPET: parameters -->
+ * <ul>
+ * <li>excludes (optional) - the list of parameter names to exclude from 
copying (all others will be included).</li>
+ * <li>includes (optional) - the list of parameter names to include when 
copying (all others will be excluded).</li>
+ * </ul>
+ * <!-- END SNIPPET: parameters -->
+ * <u>Extending the interceptor:</u>
+ * <!-- START SNIPPET: extending -->
+ * <p>
+ * There are no known extension points to this interceptor.
+ * </p>
+ * <!-- END SNIPPET: extending -->
+ * <u>Example code:</u>
+ *
+ * <!-- START SNIPPET: example -->
+ * <pre>
+ * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
+ *     &lt;interceptor-ref name="basicStack"/&gt;
+ *     &lt;result name="success" type="chain"&gt;otherAction&lt;/result&gt;
+ * &lt;/action&gt;
+ * </pre>
+ *
+ * <pre>
+ * &lt;action name="otherAction" class="com.examples.OtherAction"&gt;
+ *     &lt;interceptor-ref name="chain"/&gt;
+ *     &lt;interceptor-ref name="basicStack"/&gt;
+ *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
+ * &lt;/action&gt;
+ * </pre>
+ * <!-- END SNIPPET: example -->
+ *
+ *
+ * @author mrdon
+ * @author tm_jee ( tm_jee(at)yahoo.co.uk )
+ * @see ActionChainResult
+ */
+public class ChainingInterceptor extends AbstractInterceptor {
+
+    private static final Logger LOG = 
LogManager.getLogger(ChainingInterceptor.class);
+
+    private static final String ACTION_ERRORS = "actionErrors";
+    private static final String FIELD_ERRORS = "fieldErrors";
+    private static final String ACTION_MESSAGES = "actionMessages";
+
+    private boolean copyMessages = false;
+    private boolean copyErrors = false;
+    private boolean copyFieldErrors = false;
+
+    protected Collection<String> excludes;
+
+    protected Collection<String> includes;
+    protected ReflectionProvider reflectionProvider;
+
+    @Inject
+    public void setReflectionProvider(ReflectionProvider prov) {
+        this.reflectionProvider = prov;
+    }
+
+    @Inject(value = StrutsConstants.STRUTS_CHAINING_COPY_ERRORS, required = 
false)
+    public void setCopyErrors(String copyErrors) {
+        this.copyErrors = "true".equalsIgnoreCase(copyErrors);
+    }
+
+    @Inject(value = StrutsConstants.STRUTS_CHAINING_COPY_FIELD_ERRORS, 
required = false)
+    public void setCopyFieldErrors(String copyFieldErrors) {
+        this.copyFieldErrors = "true".equalsIgnoreCase(copyFieldErrors);
+    }
+
+    @Inject(value = StrutsConstants.STRUTS_CHAINING_COPY_MESSAGES, required = 
false)
+    public void setCopyMessages(String copyMessages) {
+        this.copyMessages = "true".equalsIgnoreCase(copyMessages);
+    }
+
+    @Override
+    public String intercept(ActionInvocation invocation) throws Exception {
+        ValueStack stack = invocation.getStack();
+        CompoundRoot root = stack.getRoot();
+        if (shouldCopyStack(invocation, root)) {
+            copyStack(invocation, root);
+        }
+        return invocation.invoke();
+    }
+
+    private void copyStack(ActionInvocation invocation, CompoundRoot root) {
+        List list = prepareList(root);
+        Map<String, Object> ctxMap = 
invocation.getInvocationContext().getContextMap();
+        for (Object object : list) {
+            if (shouldCopy(object)) {
+                Object action = invocation.getAction();
+                Class<?> editable = null;
+                if(ProxyUtil.isProxy(action)) {
+                    editable = ProxyUtil.ultimateTargetClass(action);
+                }
+                reflectionProvider.copy(object, action, ctxMap, 
prepareExcludes(), includes, editable);
+            }
+        }
+    }
+
+    private Collection<String> prepareExcludes() {
+        Collection<String> localExcludes = excludes;
+        if (!copyErrors || !copyMessages ||!copyFieldErrors) {
+            if (localExcludes == null) {
+                localExcludes = new HashSet<String>();
+                if (!copyErrors) {
+                    localExcludes.add(ACTION_ERRORS);
+                }
+                if (!copyMessages) {
+                    localExcludes.add(ACTION_MESSAGES);
+                }
+                if (!copyFieldErrors) {
+                    localExcludes.add(FIELD_ERRORS);
+                }
+            }
+        }
+        return localExcludes;
+    }
+
+    private boolean shouldCopy(Object o) {
+        return o != null && !(o instanceof Unchainable);
+    }
+
+    @SuppressWarnings("unchecked")
+    private List prepareList(CompoundRoot root) {
+        List list = new ArrayList(root);
+        list.remove(0);
+        Collections.reverse(list);
+        return list;
+    }
+
+    private boolean shouldCopyStack(ActionInvocation invocation, CompoundRoot 
root) throws Exception {
+        Result result = invocation.getResult();
+        return root.size() > 1 && (result == null || 
ActionChainResult.class.isAssignableFrom(result.getClass()));
+    }
+
+    /**
+     * Gets list of parameter names to exclude
+     *
+     * @return the exclude list
+     */
+    public Collection<String> getExcludes() {
+        return excludes;
+    }
+
+    /**
+     * Sets the list of parameter names to exclude from copying (all others 
will be included).
+     *
+     * @param excludes the excludes list as comma separated String
+     */
+    public void setExcludes(String excludes) {
+        this.excludes = TextParseUtil.commaDelimitedStringToSet(excludes);
+    }
+
+    /**
+     * Sets the list of parameter names to exclude from copying (all others 
will be included).
+     *
+     * @param excludes the excludes list
+     */
+    public void setExcludesCollection(Collection<String> excludes) {
+        this.excludes = excludes;
+    }
+
+    /**
+     * Gets list of parameter names to include
+     *
+     * @return the include list
+     */
+    public Collection<String> getIncludes() {
+        return includes;
+    }
+
+    /**
+     * Sets the list of parameter names to include when copying (all others 
will be excluded).
+     *
+     * @param includes the includes list as comma separated String
+     */
+    public void setIncludes(String includes) {
+        this.includes = TextParseUtil.commaDelimitedStringToSet(includes);
+    }
+
+
+    /**
+     * Sets the list of parameter names to include when copying (all others 
will be excluded).
+     *
+     * @param includes the includes list
+     */
+    public void setIncludesCollection(Collection<String> includes) {
+        this.includes = includes;
+    }
+
+}
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/ConversionErrorInterceptor.java
 
b/core/src/main/java/org/apache/struts2/interceptor/ConversionErrorInterceptor.java
new file mode 100644
index 000000000..0301441e5
--- /dev/null
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/ConversionErrorInterceptor.java
@@ -0,0 +1,150 @@
+/*
+ * 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.interceptor;
+
+import com.opensymphony.xwork2.conversion.impl.ConversionData;
+import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
+import com.opensymphony.xwork2.interceptor.ValidationAware;
+import org.apache.commons.text.StringEscapeUtils;
+import org.apache.struts2.ActionContext;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.util.ValueStack;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * <!-- START SNIPPET: description -->
+ * ConversionErrorInterceptor adds conversion errors from the ActionContext to 
the Action's field errors.
+ *
+ * <p>
+ * This interceptor adds any error found in the {@link ActionContext}'s 
conversionErrors map as a field error (provided
+ * that the action implements {@link 
com.opensymphony.xwork2.interceptor.ValidationAware}). In addition, any field 
that contains a validation error has its
+ * original value saved such that any subsequent requests for that value 
return the original value rather than the value
+ * in the action. This is important because if the value "abc" is submitted 
and can't be converted to an int, we want to
+ * display the original string ("abc") again rather than the int value (likely 
0, which would make very little sense to
+ * the user).
+ * </p>
+ *
+ * <p>
+ * <b>Note:</b> Since 2.5.2, this interceptor extends {@link 
com.opensymphony.xwork2.interceptor.MethodFilterInterceptor}, therefore being
+ * able to deal with excludeMethods / includeMethods parameters. See [Workflow 
Interceptor]
+ * (class {@link DefaultWorkflowInterceptor}) for documentation and examples 
on how to use this feature.
+ * </p>
+ *
+ * <!-- END SNIPPET: description -->
+ *
+ * <p><u>Interceptor parameters:</u></p>
+ *
+ * <!-- START SNIPPET: parameters -->
+ *
+ * <ul>
+ *  <li>None</li>
+ * </ul>
+ *
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <p> <u>Extending the interceptor:</u></p>
+ *
+ * <!-- START SNIPPET: extending -->
+ *
+ * Because this interceptor is not web-specific, it abstracts the logic for 
whether an error should be added. This
+ * allows for web-specific interceptors to use more complex logic in the 
{@link #shouldAddError} method for when a value
+ * has a conversion error but is null or empty or otherwise indicates that the 
value was never actually entered by the
+ * user.
+ *
+ * <!-- END SNIPPET: extending -->
+ *
+ * <p> <u>Example code:</u></p>
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
+ *     &lt;interceptor-ref name="params"/&gt;
+ *     &lt;interceptor-ref name="conversionError"/&gt;
+ *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
+ * &lt;/action&gt;
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @author Jason Carreira
+ */
+public class ConversionErrorInterceptor extends MethodFilterInterceptor {
+
+    public static final String ORIGINAL_PROPERTY_OVERRIDE = 
"original.property.override";
+
+    protected Object getOverrideExpr(ActionInvocation invocation, Object 
value) {
+        return escape(value);
+    }
+
+    protected String escape(Object value) {
+        return "\"" + StringEscapeUtils.escapeJava(String.valueOf(value)) + 
"\"";
+    }
+
+    @Override
+    public String doIntercept(ActionInvocation invocation) throws Exception {
+
+        ActionContext invocationContext = invocation.getInvocationContext();
+        Map<String, ConversionData> conversionErrors = 
invocationContext.getConversionErrors();
+        ValueStack stack = invocationContext.getValueStack();
+
+        HashMap<Object, Object> fakie = null;
+
+        for (Map.Entry<String, ConversionData> entry : 
conversionErrors.entrySet()) {
+            String propertyName = entry.getKey();
+            ConversionData conversionData = entry.getValue();
+
+            if (shouldAddError(propertyName, conversionData.getValue())) {
+                String message = 
XWorkConverter.getConversionErrorMessage(propertyName, 
conversionData.getToClass(), stack);
+
+                Object action = invocation.getAction();
+                if (action instanceof 
com.opensymphony.xwork2.interceptor.ValidationAware) {
+                    com.opensymphony.xwork2.interceptor.ValidationAware va = 
(ValidationAware) action;
+                    va.addFieldError(propertyName, message);
+                }
+
+                if (fakie == null) {
+                    fakie = new HashMap<>();
+                }
+
+                fakie.put(propertyName, getOverrideExpr(invocation, 
conversionData.getValue()));
+            }
+        }
+
+        if (fakie != null) {
+            // if there were some errors, put the original (fake) values in 
place right before the result
+            stack.getContext().put(ORIGINAL_PROPERTY_OVERRIDE, fakie);
+            invocation.addPreResultListener(new PreResultListener() {
+                public void beforeResult(ActionInvocation invocation, String 
resultCode) {
+                    Map<Object, Object> fakie = (Map<Object, Object>) 
invocation.getInvocationContext().get(ORIGINAL_PROPERTY_OVERRIDE);
+
+                    if (fakie != null) {
+                        invocation.getStack().setExprOverrides(fakie);
+                    }
+                }
+            });
+        }
+        return invocation.invoke();
+    }
+
+    protected boolean shouldAddError(String propertyName, Object value) {
+        return true;
+    }
+}
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/DefaultWorkflowInterceptor.java
 
b/core/src/main/java/org/apache/struts2/interceptor/DefaultWorkflowInterceptor.java
new file mode 100644
index 000000000..c51554d01
--- /dev/null
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/DefaultWorkflowInterceptor.java
@@ -0,0 +1,248 @@
+/*
+ * 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.interceptor;
+
+import com.opensymphony.xwork2.Action;
+import com.opensymphony.xwork2.interceptor.ValidationAware;
+import com.opensymphony.xwork2.interceptor.ValidationErrorAware;
+import com.opensymphony.xwork2.interceptor.ValidationWorkflowAware;
+import com.opensymphony.xwork2.interceptor.annotations.InputConfig;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.reflect.MethodUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionInvocation;
+
+/**
+ * <!-- START SNIPPET: description -->
+ * <p>
+ * An interceptor that makes sure there are not validation, conversion or 
action errors before allowing the interceptor chain to continue.
+ * If a single FieldError or ActionError (including the ones replicated by the 
Message Store Interceptor in a redirection) is found, the INPUT result will be 
triggered.
+ * <b>This interceptor does not perform any validation</b>.
+ * </p>
+ *
+ * <p>
+ * This interceptor does nothing if the name of the method being invoked is 
specified in the <b>excludeMethods</b>
+ * parameter. <b>excludeMethods</b> accepts a comma-delimited list of method 
names. For example, requests to
+ * <b>foo!input.action</b> and <b>foo!back.action</b> will be skipped by this 
interceptor if you set the
+ * <b>excludeMethods</b> parameter to "input, back".
+ * </p>
+ *
+ * <p>
+ * <b>Note:</b> As this method extends off MethodFilterInterceptor, it is 
capable of
+ * deciding if it is applicable only to selective methods in the action class. 
This is done by adding param tags
+ * for the interceptor element, naming either a list of excluded method names 
and/or a list of included method
+ * names, whereby includeMethods overrides excludedMethods. A single * sign is 
interpreted as wildcard matching
+ * all methods for both parameters.
+ * See {@link com.opensymphony.xwork2.interceptor.MethodFilterInterceptor} for 
more info.
+ * </p>
+ *
+ * <p>
+ * This interceptor also supports the following interfaces which can 
implemented by actions:
+ * </p>
+ *
+ * <ul>
+ *     <li>ValidationAware - implemented by ActionSupport class</li>
+ *     <li>ValidationWorkflowAware - allows changing result name 
programmatically</li>
+ *     <li>ValidationErrorAware - notifies action about errors and also allow 
change result name</li>
+ * </ul>
+ *
+ * <p>
+ * You can also use InputConfig annotation to change result name returned when 
validation errors occurred.
+ * </p>
+ *
+ * <!-- END SNIPPET: description -->
+ *
+ * <p><u>Interceptor parameters:</u></p>
+ *
+ * <!-- START SNIPPET: parameters -->
+ * <ul>
+ * <li>inputResultName - Default to "input". Determine the result name to be 
returned when
+ * an action / field error is found.</li>
+ * </ul>
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <p><u>Extending the interceptor:</u></p>
+ *
+ * <!-- START SNIPPET: extending -->
+ *
+ * <p>There are no known extension points for this interceptor.</p>
+ *
+ * <!-- END SNIPPET: extending -->
+ *
+ * <p><u>Example code:</u></p>
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ *
+ * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
+ *     &lt;interceptor-ref name="params"/&gt;
+ *     &lt;interceptor-ref name="validation"/&gt;
+ *     &lt;interceptor-ref name="workflow"/&gt;
+ *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
+ * &lt;/action&gt;
+ *
+ * &lt;-- In this case myMethod as well as mySecondMethod of the action class
+ *        will not pass through the workflow process --&gt;
+ * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
+ *     &lt;interceptor-ref name="params"/&gt;
+ *     &lt;interceptor-ref name="validation"/&gt;
+ *     &lt;interceptor-ref name="workflow"&gt;
+ *         &lt;param 
name="excludeMethods"&gt;myMethod,mySecondMethod&lt;/param&gt;
+ *     &lt;/interceptor-ref name="workflow"&gt;
+ *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
+ * &lt;/action&gt;
+ *
+ * &lt;-- In this case, the result named "error" will be used when
+ *        an action / field error is found --&gt;
+ * &lt;-- The Interceptor will only be applied for myWorkflowMethod method of 
action
+ *        classes, since this is the only included method while any others are 
excluded --&gt;
+ * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
+ *     &lt;interceptor-ref name="params"/&gt;
+ *     &lt;interceptor-ref name="validation"/&gt;
+ *     &lt;interceptor-ref name="workflow"&gt;
+ *        &lt;param name="inputResultName"&gt;error&lt;/param&gt;
+ *         &lt;param name="excludeMethods"&gt;*&lt;/param&gt;
+ *         &lt;param name="includeMethods"&gt;myWorkflowMethod&lt;/param&gt;
+ *     &lt;/interceptor-ref&gt;
+ *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
+ * &lt;/action&gt;
+ *
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @author Jason Carreira
+ * @author Rainer Hermanns
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru 
Popescu</a>
+ * @author Philip Luppens
+ * @author tm_jee
+ */
+public class DefaultWorkflowInterceptor extends MethodFilterInterceptor {
+
+    private static final long serialVersionUID = 7563014655616490865L;
+
+    private static final Logger LOG = 
LogManager.getLogger(DefaultWorkflowInterceptor.class);
+
+    private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
+
+    private String inputResultName = Action.INPUT;
+
+    /**
+     * Set the <code>inputResultName</code> (result name to be returned when
+     * a action / field error is found registered). Default to {@link 
Action#INPUT}
+     *
+     * @param inputResultName what result name to use when there was 
validation error(s).
+     */
+    public void setInputResultName(String inputResultName) {
+        this.inputResultName = inputResultName;
+    }
+
+    /**
+     * Intercept {@link ActionInvocation} and returns a 
<code>inputResultName</code>
+     * when action / field errors is found registered.
+     *
+     * @param invocation the action invocation
+     * @return String result name
+     */
+    @Override
+    protected String doIntercept(ActionInvocation invocation) throws Exception 
{
+        Object action = invocation.getAction();
+
+        if (action instanceof 
com.opensymphony.xwork2.interceptor.ValidationAware) {
+            com.opensymphony.xwork2.interceptor.ValidationAware 
validationAwareAction = (ValidationAware) action;
+
+            if (validationAwareAction.hasErrors()) {
+                LOG.debug("Errors on action [{}], returning result name [{}]", 
validationAwareAction, inputResultName);
+
+                String resultName = inputResultName;
+                resultName = processValidationWorkflowAware(action, 
resultName);
+                resultName = processInputConfig(action, 
invocation.getProxy().getMethod(), resultName);
+                resultName = processValidationErrorAware(action, resultName);
+
+                return resultName;
+            }
+        }
+
+        return invocation.invoke();
+    }
+
+    /**
+     * Process {@link 
com.opensymphony.xwork2.interceptor.ValidationWorkflowAware} interface
+     *
+     * @param action action object
+     * @param currentResultName current result name
+     *
+     * @return result name
+     */
+    private String processValidationWorkflowAware(final Object action, final 
String currentResultName) {
+        String resultName = currentResultName;
+        if (action instanceof 
com.opensymphony.xwork2.interceptor.ValidationWorkflowAware) {
+            resultName = 
((com.opensymphony.xwork2.interceptor.ValidationWorkflowAware) 
action).getInputResultName();
+            LOG.debug("Changing result name from [{}] to [{}] because of 
processing [{}] interface applied to [{}]",
+                        currentResultName, resultName, 
ValidationWorkflowAware.class.getSimpleName(), action);
+        }
+        return resultName;
+    }
+
+    /**
+     * Process {@link InputConfig} annotation applied to method
+     * @param action action object
+     * @param method method
+     * @param currentResultName current result name
+     *
+     * @return result name
+     *
+     * @throws Exception in case of any errors
+     */
+    protected String processInputConfig(final Object action, final String 
method, final String currentResultName) throws Exception {
+        String resultName = currentResultName;
+        InputConfig annotation = 
MethodUtils.getAnnotation(action.getClass().getMethod(method, 
EMPTY_CLASS_ARRAY),
+                InputConfig.class ,true,true);
+        if (annotation != null) {
+            if (StringUtils.isNotEmpty(annotation.methodName())) {
+                resultName = (String) MethodUtils.invokeMethod(action, true, 
annotation.methodName());
+            } else {
+                resultName = annotation.resultName();
+            }
+            LOG.debug("Changing result name from [{}] to [{}] because of 
processing annotation [{}] on action [{}]",
+                        currentResultName, resultName, 
InputConfig.class.getSimpleName(), action);
+        }
+        return resultName;
+    }
+
+    /**
+     * Notify action if it implements {@link 
com.opensymphony.xwork2.interceptor.ValidationErrorAware} interface
+     *
+     * @param action action object
+     * @param currentResultName current result name
+     *
+     * @return result name
+     * @see com.opensymphony.xwork2.interceptor.ValidationErrorAware
+     */
+    protected String processValidationErrorAware(final Object action, final 
String currentResultName) {
+        String resultName = currentResultName;
+        if (action instanceof 
com.opensymphony.xwork2.interceptor.ValidationErrorAware) {
+            resultName = 
((com.opensymphony.xwork2.interceptor.ValidationErrorAware) 
action).actionErrorOccurred(currentResultName);
+            LOG.debug("Changing result name from [{}] to [{}] because of 
processing interface [{}] on action [{}]",
+                        currentResultName, resultName, 
ValidationErrorAware.class.getSimpleName(), action);
+        }
+        return resultName;
+    }
+
+}
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/ExceptionMappingInterceptor.java
 
b/core/src/main/java/org/apache/struts2/interceptor/ExceptionMappingInterceptor.java
new file mode 100644
index 000000000..277fc33df
--- /dev/null
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/ExceptionMappingInterceptor.java
@@ -0,0 +1,324 @@
+/*
+ * 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.interceptor;
+
+import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
+import com.opensymphony.xwork2.interceptor.ExceptionHolder;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.dispatcher.HttpParameters;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <!-- START SNIPPET: description -->
+ * <p>
+ * This interceptor forms the core functionality of the exception handling 
feature. Exception handling allows you to map
+ * an exception to a result code, just as if the action returned a result code 
instead of throwing an unexpected
+ * exception. When an exception is encountered, it is wrapped with an {@link 
ExceptionHolder} and pushed on the stack,
+ * providing easy access to the exception from within your result.
+ * </p>
+ *
+ * <p>
+ * <b>Note:</b> While you can configure exception mapping in your 
configuration file at any point, the configuration
+ * will not have any effect if this interceptor is not in the interceptor 
stack for your actions. It is recommended that
+ * you make this interceptor the first interceptor on the stack, ensuring that 
it has full access to catch any
+ * exception, even those caused by other interceptors.
+ * </p>
+ *
+ * <!-- END SNIPPET: description -->
+ *
+ * <p><u>Interceptor parameters:</u></p>
+ *
+ * <!-- START SNIPPET: parameters -->
+ *
+ * <ul>
+ *
+ * <li>logEnabled (optional) - Should exceptions also be logged? (boolean 
true|false)</li>
+ *
+ * <li>logLevel (optional) - what log level should we use (<code>trace, debug, 
info, warn, error, fatal</code>)? - defaut is <code>debug</code></li>
+ *
+ * <li>logCategory (optional) - If provided we would use this category (eg. 
<code>com.mycompany.app</code>).
+ * Default is to use 
<code>com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor</code>.</li>
+ *
+ * </ul>
+ *
+ * <p>
+ * The parameters above enables us to log all thrown exceptions with stacktace 
in our own logfile,
+ * and present a friendly webpage (with no stacktrace) to the end user.
+ * </p>
+ *
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <p><u>Extending the interceptor:</u></p>
+ *
+ * <!-- START SNIPPET: extending -->
+ * <p>
+ * If you want to add custom handling for publishing the Exception, you may 
override
+ * {@link #publishException(ActionInvocation, ExceptionHolder)}. The default 
implementation
+ * pushes the given ExceptionHolder on value stack. A custom implementation 
could add additional logging etc.
+ * </p>
+ * <!-- END SNIPPET: extending -->
+ *
+ * <p><u>Example code:</u></p>
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * &lt;xwork&gt;
+ *     &lt;package name="default" extends="xwork-default"&gt;
+ *         &lt;global-results&gt;
+ *             &lt;result name="error" 
type="freemarker"&gt;error.ftl&lt;/result&gt;
+ *         &lt;/global-results&gt;
+ *
+ *         &lt;global-exception-mappings&gt;
+ *             &lt;exception-mapping exception="java.lang.Exception" 
result="error"/&gt;
+ *         &lt;/global-exception-mappings&gt;
+ *
+ *         &lt;action name="test"&gt;
+ *             &lt;interceptor-ref name="exception"/&gt;
+ *             &lt;interceptor-ref name="basicStack"/&gt;
+ *             &lt;exception-mapping exception="com.acme.CustomException" 
result="custom_error"/&gt;
+ *             &lt;result 
name="custom_error"&gt;custom_error.ftl&lt;/result&gt;
+ *             &lt;result name="success" 
type="freemarker"&gt;test.ftl&lt;/result&gt;
+ *         &lt;/action&gt;
+ *     &lt;/package&gt;
+ * &lt;/xwork&gt;
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * <p>
+ * This second example will also log the exceptions using our own category
+ * <code>com.mycompany.app.unhandled</code> at WARN level.
+ * </p>
+ *
+ * <pre>
+ * <!-- START SNIPPET: example2 -->
+ * &lt;xwork&gt;
+ *   &lt;package name="something" extends="xwork-default"&gt;
+ *      &lt;interceptors&gt;
+ *          &lt;interceptor-stack name="exceptionmappingStack"&gt;
+ *              &lt;interceptor-ref name="exception"&gt;
+ *                  &lt;param name="logEnabled"&gt;true&lt;/param&gt;
+ *                  &lt;param 
name="logCategory"&gt;com.mycompany.app.unhandled&lt;/param&gt;
+ *                  &lt;param name="logLevel"&gt;WARN&lt;/param&gt;
+ *              &lt;/interceptor-ref&gt;
+ *              &lt;interceptor-ref name="i18n"/&gt;
+ *              &lt;interceptor-ref name="staticParams"/&gt;
+ *              &lt;interceptor-ref name="params"/&gt;
+ *              &lt;interceptor-ref name="validation"&gt;
+ *                  &lt;param 
name="excludeMethods"&gt;input,back,cancel,browse&lt;/param&gt;
+ *              &lt;/interceptor-ref&gt;
+ *          &lt;/interceptor-stack&gt;
+ *      &lt;/interceptors&gt;
+ *
+ *      &lt;default-interceptor-ref name="exceptionmappingStack"/&gt;
+ *
+ *      &lt;global-results&gt;
+ *           &lt;result 
name="unhandledException"&gt;/unhandled-exception.jsp&lt;/result&gt;
+ *      &lt;/global-results&gt;
+ *
+ *      &lt;global-exception-mappings&gt;
+ *           &lt;exception-mapping exception="java.lang.Exception" 
result="unhandledException"/&gt;
+ *      &lt;/global-exception-mappings&gt;
+ *
+ *      &lt;action name="exceptionDemo" 
class="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingAction"&gt;
+ *          &lt;exception-mapping 
exception="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingException"
+ *                             result="damm"/&gt;
+ *          &lt;result name="input"&gt;index.jsp&lt;/result&gt;
+ *          &lt;result name="success"&gt;success.jsp&lt;/result&gt;
+ *          &lt;result name="damm"&gt;damm.jsp&lt;/result&gt;
+ *      &lt;/action&gt;
+ *
+ *   &lt;/package&gt;
+ * &lt;/xwork&gt;
+ * <!-- END SNIPPET: example2 -->
+ * </pre>
+ *
+ * @author Matthew E. Porter (matthew dot porter at metissian dot com)
+ * @author Claus Ibsen
+ */
+public class ExceptionMappingInterceptor extends AbstractInterceptor {
+
+    private static final Logger LOG = 
LogManager.getLogger(ExceptionMappingInterceptor.class);
+
+    protected Logger categoryLogger;
+    protected boolean logEnabled = false;
+    protected String logCategory;
+    protected String logLevel;
+
+
+    public boolean isLogEnabled() {
+        return logEnabled;
+    }
+
+    public void setLogEnabled(boolean logEnabled) {
+        this.logEnabled = logEnabled;
+    }
+
+    public String getLogCategory() {
+               return logCategory;
+       }
+
+       public void setLogCategory(String logCatgory) {
+               this.logCategory = logCatgory;
+       }
+
+       public String getLogLevel() {
+               return logLevel;
+       }
+
+       public void setLogLevel(String logLevel) {
+               this.logLevel = logLevel;
+       }
+
+    @Override
+    public String intercept(ActionInvocation invocation) throws Exception {
+        String result;
+
+        try {
+            result = invocation.invoke();
+        } catch (Exception e) {
+            if (isLogEnabled()) {
+                handleLogging(e);
+            }
+            List<ExceptionMappingConfig> exceptionMappings = 
invocation.getProxy().getConfig().getExceptionMappings();
+            ExceptionMappingConfig mappingConfig = 
this.findMappingFromExceptions(exceptionMappings, e);
+            if (mappingConfig != null && mappingConfig.getResult()!=null) {
+                Map<String, String> mappingParams = mappingConfig.getParams();
+                // create a mutable HashMap since some interceptors will 
remove parameters, and parameterMap is immutable
+                HttpParameters parameters = 
HttpParameters.create(mappingParams).build();
+                invocation.getInvocationContext().withParameters(parameters);
+                result = mappingConfig.getResult();
+                publishException(invocation, new ExceptionHolder(e));
+            } else {
+                throw e;
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Handles the logging of the exception.
+     *
+     * @param e the exception to log.
+     */
+    protected void handleLogging(Exception e) {
+       if (logCategory != null) {
+               if (categoryLogger == null) {
+                       // init category logger
+                       categoryLogger = LogManager.getLogger(logCategory);
+               }
+               doLog(categoryLogger, e);
+       } else {
+               doLog(LOG, e);
+       }
+    }
+
+    /**
+     * Performs the actual logging.
+     *
+     * @param logger  the provided logger to use.
+     * @param e  the exception to log.
+     */
+    protected void doLog(Logger logger, Exception e) {
+       if (logLevel == null) {
+               logger.debug(e.getMessage(), e);
+               return;
+       }
+
+       if ("trace".equalsIgnoreCase(logLevel)) {
+               logger.trace(e.getMessage(), e);
+       } else if ("debug".equalsIgnoreCase(logLevel)) {
+               logger.debug(e.getMessage(), e);
+       } else if ("info".equalsIgnoreCase(logLevel)) {
+               logger.info(e.getMessage(), e);
+       } else if ("warn".equalsIgnoreCase(logLevel)) {
+               logger.warn(e.getMessage(), e);
+       } else if ("error".equalsIgnoreCase(logLevel)) {
+               logger.error(e.getMessage(), e);
+       } else if ("fatal".equalsIgnoreCase(logLevel)) {
+               logger.fatal(e.getMessage(), e);
+       } else {
+               throw new IllegalArgumentException("LogLevel [" + logLevel + "] 
is not supported");
+       }
+    }
+
+    /**
+     * Try to find appropriate {@link ExceptionMappingConfig} based on 
provided Throwable
+     *
+     * @param exceptionMappings list of defined exception mappings
+     * @param t caught exception
+     * @return appropriate mapping or null
+     */
+    protected ExceptionMappingConfig 
findMappingFromExceptions(List<ExceptionMappingConfig> exceptionMappings, 
Throwable t) {
+       ExceptionMappingConfig config = null;
+        // Check for specific exception mappings.
+        if (exceptionMappings != null) {
+            int deepest = Integer.MAX_VALUE;
+            for (Object exceptionMapping : exceptionMappings) {
+                ExceptionMappingConfig exceptionMappingConfig = 
(ExceptionMappingConfig) exceptionMapping;
+                int depth = 
getDepth(exceptionMappingConfig.getExceptionClassName(), t);
+                if (depth >= 0 && depth < deepest) {
+                    deepest = depth;
+                    config = exceptionMappingConfig;
+                }
+            }
+        }
+        return config;
+    }
+
+    /**
+     * Return the depth to the superclass matching. 0 means ex matches 
exactly. Returns -1 if there's no match.
+     * Otherwise, returns depth. Lowest depth wins.
+     *
+     * @param exceptionMapping  the mapping classname
+     * @param t  the cause
+     * @return the depth, if not found -1 is returned.
+     */
+    public int getDepth(String exceptionMapping, Throwable t) {
+        return getDepth(exceptionMapping, t.getClass(), 0);
+    }
+
+    private int getDepth(String exceptionMapping, Class exceptionClass, int 
depth) {
+        if (exceptionClass.getName().contains(exceptionMapping)) {
+            // Found it!
+            return depth;
+        }
+        // If we've gone as far as we can go and haven't found it...
+        if (exceptionClass.equals(Throwable.class)) {
+            return -1;
+        }
+        return getDepth(exceptionMapping, exceptionClass.getSuperclass(), 
depth + 1);
+    }
+
+    /**
+     * Default implementation to handle ExceptionHolder publishing. Pushes 
given ExceptionHolder on the stack.
+     * Subclasses may override this to customize publishing.
+     *
+     * @param invocation The invocation to publish Exception for.
+     * @param exceptionHolder The exceptionHolder wrapping the Exception to 
publish.
+     */
+    protected void publishException(ActionInvocation invocation, 
ExceptionHolder exceptionHolder) {
+        invocation.getStack().push(exceptionHolder);
+    }
+
+}
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/LoggingInterceptor.java 
b/core/src/main/java/org/apache/struts2/interceptor/LoggingInterceptor.java
new file mode 100644
index 000000000..4536d462b
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/LoggingInterceptor.java
@@ -0,0 +1,90 @@
+/*
+ * 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.interceptor;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionInvocation;
+
+
+/**
+ * <!-- START SNIPPET: description -->
+ * <p>
+ * This interceptor logs the start and end of the execution an action (in 
English-only, not internationalized).
+ * <br>
+ * <b>Note:</b>: This interceptor will log at <tt>INFO</tt> level.
+ * </p>
+ * <!-- END SNIPPET: description -->
+ *
+ * <!-- START SNIPPET: parameters -->
+ * There are no parameters for this interceptor.
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <!-- START SNIPPET: extending -->
+ * There are no obvious extensions to the existing interceptor.
+ * <!-- END SNIPPET: extending -->
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * &lt;!-- prints out a message before and after the immediate action 
execution --&gt;
+ * &lt;action name=&quot;someAction&quot; 
class=&quot;com.examples.SomeAction&quot;&gt;
+ *     &lt;interceptor-ref name=&quot;completeStack&quot;/&gt;
+ *     &lt;interceptor-ref name=&quot;logger&quot;/&gt;
+ *     &lt;result name=&quot;success&quot;&gt;good_result.ftl&lt;/result&gt;
+ * &lt;/action&gt;
+ *
+ * &lt;!-- prints out a message before any more interceptors continue and 
after they have finished --&gt;
+ * &lt;action name=&quot;someAction&quot; 
class=&quot;com.examples.SomeAction&quot;&gt;
+ *     &lt;interceptor-ref name=&quot;logger&quot;/&gt;
+ *     &lt;interceptor-ref name=&quot;completeStack&quot;/&gt;
+ *     &lt;result name=&quot;success&quot;&gt;good_result.ftl&lt;/result&gt;
+ * &lt;/action&gt;
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @author Jason Carreira
+ */
+public class LoggingInterceptor extends AbstractInterceptor {
+    private static final Logger LOG = 
LogManager.getLogger(LoggingInterceptor.class);
+    private static final String FINISH_MESSAGE = "Finishing execution stack 
for action ";
+    private static final String START_MESSAGE = "Starting execution stack for 
action ";
+
+    @Override
+    public String intercept(ActionInvocation invocation) throws Exception {
+        logMessage(invocation, START_MESSAGE);
+        String result = invocation.invoke();
+        logMessage(invocation, FINISH_MESSAGE);
+        return result;
+    }
+
+    private void logMessage(ActionInvocation invocation, String baseMessage) {
+        if (LOG.isInfoEnabled()) {
+            StringBuilder message = new StringBuilder(baseMessage);
+            String namespace = invocation.getProxy().getNamespace();
+
+            if ((namespace != null) && (namespace.trim().length() > 0)) {
+                message.append(namespace).append("/");
+            }
+
+            message.append(invocation.getProxy().getActionName());
+               LOG.info(message.toString());
+        }
+    }
+
+}
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/ModelDrivenInterceptor.java 
b/core/src/main/java/org/apache/struts2/interceptor/ModelDrivenInterceptor.java
new file mode 100644
index 000000000..aeaf0a040
--- /dev/null
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/ModelDrivenInterceptor.java
@@ -0,0 +1,148 @@
+/*
+ * 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.interceptor;
+
+import com.opensymphony.xwork2.ModelDriven;
+import com.opensymphony.xwork2.util.CompoundRoot;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.interceptor.parameter.ParametersInterceptor;
+import org.apache.struts2.util.ValueStack;
+
+/**
+ * <!-- START SNIPPET: description -->
+ *
+ * Watches for {@link ModelDriven} actions and adds the action's model on to 
the value stack.
+ *
+ * <p> <b>Note:</b>  The ModelDrivenInterceptor must come before the both 
{@link StaticParametersInterceptor} and
+ * {@link ParametersInterceptor} if you want the parameters to be applied to 
the model.
+ * </p>
+ * <p> <b>Note:</b>  The ModelDrivenInterceptor will only push the model into 
the stack when the
+ * model is not null, else it will be ignored.
+ * </p>
+ *
+ * <!-- END SNIPPET: description -->
+ *
+ * <p><u>Interceptor parameters:</u></p>
+ *
+ * <!-- START SNIPPET: parameters -->
+ *
+ * <ul>
+ *
+ * <li>refreshModelBeforeResult - set to true if you want the model to be 
refreshed on the value stack after action
+ * execution and before result execution.  The setting is useful if you want 
to change the model instance during the
+ * action execution phase, like when loading it from the data layer.  This 
will result in getModel() being called at
+ * least twice.</li>
+ *
+ * </ul>
+ *
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <p><u>Extending the interceptor:</u></p>
+ *
+ * <!-- START SNIPPET: extending -->
+ *
+ * There are no known extension points to this interceptor.
+ *
+ * <!-- END SNIPPET: extending -->
+ *
+ * <p><u>Example code:</u></p>
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * &lt;action name=&quot;someAction&quot; 
class=&quot;com.examples.SomeAction&quot;&gt;
+ *     &lt;interceptor-ref name=&quot;modelDriven&quot;/&gt;
+ *     &lt;interceptor-ref name=&quot;basicStack&quot;/&gt;
+ *     &lt;result name=&quot;success&quot;&gt;good_result.ftl&lt;/result&gt;
+ * &lt;/action&gt;
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @author tm_jee
+ * @version $Date$ $Id$
+ */
+public class ModelDrivenInterceptor extends AbstractInterceptor {
+
+    protected boolean refreshModelBeforeResult = false;
+
+    public void setRefreshModelBeforeResult(boolean val) {
+        this.refreshModelBeforeResult = val;
+    }
+
+    @Override
+    public String intercept(ActionInvocation invocation) throws Exception {
+        Object action = invocation.getAction();
+
+        if (action instanceof ModelDriven) {
+            ModelDriven modelDriven = (ModelDriven) action;
+            ValueStack stack = invocation.getStack();
+            Object model = modelDriven.getModel();
+            if (model !=  null) {
+               stack.push(model);
+            }
+            if (refreshModelBeforeResult) {
+                invocation.addPreResultListener(new 
RefreshModelBeforeResult(modelDriven, model));
+            }
+        }
+        return invocation.invoke();
+    }
+
+    /**
+     * Refreshes the model instance on the value stack, if it has changed
+     */
+    protected static class RefreshModelBeforeResult implements 
PreResultListener {
+        private Object originalModel;
+        protected ModelDriven action;
+
+
+        public RefreshModelBeforeResult(ModelDriven action, Object model) {
+            this.originalModel = model;
+            this.action = action;
+        }
+
+        public void beforeResult(ActionInvocation invocation, String 
resultCode) {
+            ValueStack stack = invocation.getStack();
+            CompoundRoot root = stack.getRoot();
+
+            boolean needsRefresh = true;
+            Object newModel = action.getModel();
+
+            // Check to see if the new model instance is already on the stack
+            if (newModel != null) {
+                for (Object item : root) {
+                    if (item == newModel) {
+                        needsRefresh = false;
+                        break;
+                    }
+                }
+            }
+
+            // Add the new model on the stack
+            if (needsRefresh) {
+
+                // Clear off the old model instance
+                if (originalModel != null) {
+                    root.remove(originalModel);
+                }
+                if (newModel != null) {
+                    stack.push(newModel);
+                }
+            }
+        }
+    }
+}
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/ParameterRemoverInterceptor.java
 
b/core/src/main/java/org/apache/struts2/interceptor/ParameterRemoverInterceptor.java
new file mode 100644
index 000000000..ad72d5853
--- /dev/null
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/ParameterRemoverInterceptor.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 org.apache.struts2.interceptor;
+
+import com.opensymphony.xwork2.util.TextParseUtil;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionContext;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.action.NoParameters;
+import org.apache.struts2.dispatcher.HttpParameters;
+import org.apache.struts2.dispatcher.Parameter;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * This is a simple XWork interceptor that allows parameters (matching
+ * one of the paramNames attribute csv value) to be
+ * removed from the parameter map if they match a certain value
+ * (matching one of the paramValues attribute csv value), before they
+ * are set on the action. A typical usage would be to want a dropdown/select
+ * to map onto a boolean value on an action. The select had the options
+ * none, yes and no with values -1, true and false. The true and false would
+ * map across correctly. However the -1 would be set to false.
+ * This was not desired as one might needed the value on the action to stay 
null.
+ * This interceptor fixes this by preventing the parameter from ever reaching
+ * the action.
+ *
+ * <ul>
+ *     <li>paramNames - A comma separated value (csv) indicating the parameter 
name
+ *                   whose param value should be considered that if they match 
any of the
+ *                   comma separated value (csv) from paramValues attribute, 
shall be
+ *                   removed from the parameter map such that they will not be 
applied
+ *                   to the action</li>
+ *     <li>paramValues - A comma separated value (csv) indicating the 
parameter value that if
+ *                    matched shall have its parameter be removed from the 
parameter map
+ *                    such that they will not be applied to the action</li>
+ * </ul>
+ * <p>
+ * No intended extension point
+ *
+ * <pre>
+ * &lt;action name="sample" class="org.martingilday.Sample"&gt;
+ *     &lt;interceptor-ref name="paramRemover"&gt;
+ *          &lt;param name="paramNames"&gt;aParam,anotherParam&lt;/param&gt;
+ *          &lt;param name="paramValues"&gt;--,-1&lt;/param&gt;
+ *     &lt;/interceptor-ref&gt;
+ *     &lt;interceptor-ref name="defaultStack" /&gt;
+ *     ...
+ * &lt;/action&gt;
+ * </pre>
+ */
+public class ParameterRemoverInterceptor extends AbstractInterceptor {
+
+    private static final Logger LOG = 
LogManager.getLogger(ParameterRemoverInterceptor.class);
+
+    private Set<String> paramNames = Collections.emptySet();
+    private Set<String> paramValues = Collections.emptySet();
+
+    /**
+     * Decide if the parameter should be removed from the parameter map based 
on
+     * <code>paramNames</code> and <code>paramValues</code>.
+     *
+     * @see AbstractInterceptor
+     */
+    @Override
+    public String intercept(ActionInvocation invocation) throws Exception {
+        if (!(invocation.getAction() instanceof NoParameters)
+            && (null != this.paramNames)) {
+            ActionContext ac = invocation.getInvocationContext();
+            HttpParameters parameters = ac.getParameters();
+
+            if (parameters != null) {
+                for (String removeName : paramNames) {
+                    try {
+                        Parameter parameter = parameters.get(removeName);
+                        if (parameter.isDefined() && 
this.paramValues.contains(parameter.getValue())) {
+                            parameters.remove(removeName);
+                        }
+                    } catch (Exception e) {
+                        LOG.error("Failed to convert parameter to string", e);
+                    }
+                }
+            }
+        }
+        return invocation.invoke();
+    }
+
+    /**
+     * Allows <code>paramNames</code> attribute to be set as 
comma-separated-values (csv).
+     *
+     * @param paramNames the paramNames to set
+     */
+    public void setParamNames(String paramNames) {
+        this.paramNames = TextParseUtil.commaDelimitedStringToSet(paramNames);
+    }
+
+    /**
+     * Allows <code>paramValues</code> attribute to be set as a 
comma-separated-values (csv).
+     *
+     * @param paramValues the paramValues to set
+     */
+    public void setParamValues(String paramValues) {
+        this.paramValues = 
TextParseUtil.commaDelimitedStringToSet(paramValues);
+    }
+
+}
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/PrepareInterceptor.java 
b/core/src/main/java/org/apache/struts2/interceptor/PrepareInterceptor.java
new file mode 100644
index 000000000..f45a501d4
--- /dev/null
+++ b/core/src/main/java/org/apache/struts2/interceptor/PrepareInterceptor.java
@@ -0,0 +1,177 @@
+/*
+ * 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.interceptor;
+
+import com.opensymphony.xwork2.Preparable;
+import com.opensymphony.xwork2.interceptor.PrefixMethodInvocationUtil;
+import org.apache.struts2.ActionInvocation;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * <!-- START SNIPPET: description -->
+ *
+ * This interceptor calls <code>prepare()</code> on actions which implement
+ * {@link Preparable}. This interceptor is very useful for any situation where
+ * you need to ensure some logic runs before the actual execute method runs.
+ *
+ * <p>
+ * A typical use of this is to run some logic to load an object from the
+ * database so that when parameters are set they can be set on this object. For
+ * example, suppose you have a User object with two properties: <i>id</i> and
+ * <i>name</i>. Provided that the params interceptor is called twice (once
+ * before and once after this interceptor), you can load the User object using
+ * the id property, and then when the second params interceptor is called the
+ * parameter <i>user.name</i> will be set, as desired, on the actual object
+ * loaded from the database. See the example for more info.
+ * </p>
+ * <p>
+ * <b>Note:</b> Since XWork 2.0.2, this interceptor extends {@link 
com.opensymphony.xwork2.interceptor.MethodFilterInterceptor}, therefore being
+ * able to deal with excludeMethods / includeMethods parameters. See [Workflow 
Interceptor]
+ * (class {@link DefaultWorkflowInterceptor}) for documentation and examples 
on how to use this feature.
+ * </p>
+ *
+ * <p>
+ * <b>Update</b>: Added logic to execute a prepare{MethodName} and 
conditionally
+ * the a general prepare() Method, depending on the 'alwaysInvokePrepare' 
parameter/property
+ * which is by default true. This allows us to run some logic based on the 
method
+ * name we specify in the {@link com.opensymphony.xwork2.ActionProxy}. For 
example, you can specify a
+ * prepareInput() method that will be run before the invocation of the input 
method.
+ * </p>
+ *
+ * <!-- END SNIPPET: description -->
+ *
+ * <p><u>Interceptor parameters:</u></p>
+ *
+ * <!-- START SNIPPET: parameters -->
+ *
+ * <ul>
+ *
+ * <li>alwaysInvokePrepare - Default to true. If true, prepare will always be 
invoked,
+ * otherwise it will not.</li>
+ *
+ * </ul>
+ *
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <p><u>Extending the interceptor:</u></p>
+ *
+ * <!-- START SNIPPET: extending -->
+ *
+ * There are no known extension points to this interceptor.
+ *
+ * <!-- END SNIPPET: extending -->
+ *
+ * <p> <u>Example code:</u></p>
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * &lt;!-- Calls the params interceptor twice, allowing you to
+ *       pre-load data for the second time parameters are set --&gt;
+ *  &lt;action name="someAction" class="com.examples.SomeAction"&gt;
+ *      &lt;interceptor-ref name="params"/&gt;
+ *      &lt;interceptor-ref name="prepare"/&gt;
+ *      &lt;interceptor-ref name="basicStack"/&gt;
+ *      &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
+ *  &lt;/action&gt;
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @author Jason Carreira
+ * @author Philip Luppens
+ * @author tm_jee
+ * @see Preparable
+ */
+public class PrepareInterceptor extends MethodFilterInterceptor {
+
+    private static final long serialVersionUID = -5216969014510719786L;
+
+    private final static String PREPARE_PREFIX = "prepare";
+    private final static String ALT_PREPARE_PREFIX = "prepareDo";
+
+    private boolean alwaysInvokePrepare = true;
+    private boolean firstCallPrepareDo = false;
+
+    /**
+     * Sets if the <code>prepare</code> method should always be executed.
+     * <p>
+     * Default is <tt>true</tt>.
+     * </p>
+     *
+     * @param alwaysInvokePrepare if <code>prepare</code> should always be 
executed or not.
+     */
+    public void setAlwaysInvokePrepare(String alwaysInvokePrepare) {
+        this.alwaysInvokePrepare = Boolean.parseBoolean(alwaysInvokePrepare);
+    }
+
+    /**
+     * Sets if the <code>prepareDoXXX</code> method should be called first
+     * <p>
+     * Default is <tt>false</tt> for backward compatibility
+     * </p>
+     * @param firstCallPrepareDo if <code>prepareDoXXX</code> should be called 
first
+     */
+    public void setFirstCallPrepareDo(String firstCallPrepareDo) {
+        this.firstCallPrepareDo = Boolean.parseBoolean(firstCallPrepareDo);
+    }
+
+    @Override
+    public String doIntercept(ActionInvocation invocation) throws Exception {
+        Object action = invocation.getAction();
+
+        if (action instanceof Preparable) {
+            try {
+                String[] prefixes;
+                if (firstCallPrepareDo) {
+                    prefixes = new String[] {ALT_PREPARE_PREFIX, 
PREPARE_PREFIX};
+                } else {
+                    prefixes = new String[] {PREPARE_PREFIX, 
ALT_PREPARE_PREFIX};
+                }
+                PrefixMethodInvocationUtil.invokePrefixMethod(invocation, 
prefixes);
+            }
+            catch (InvocationTargetException e) {
+                /*
+                 * The invoked method threw an exception and reflection 
wrapped it
+                 * in an InvocationTargetException.
+                 * If possible re-throw the original exception so that normal
+                 * exception handling will take place.
+                 */
+                Throwable cause = e.getCause();
+                if (cause instanceof Exception) {
+                    throw (Exception) cause;
+                } else if(cause instanceof Error) {
+                    throw (Error) cause;
+                } else {
+                    /*
+                     * The cause is not an Exception or Error (must be 
Throwable) so
+                     * just re-throw the wrapped exception.
+                     */
+                    throw e;
+                }
+            }
+
+            if (alwaysInvokePrepare) {
+                ((Preparable) action).prepare();
+            }
+        }
+
+        return invocation.invoke();
+    }
+
+}
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/ScopedModelDrivenInterceptor.java
 
b/core/src/main/java/org/apache/struts2/interceptor/ScopedModelDrivenInterceptor.java
new file mode 100644
index 000000000..f474770f0
--- /dev/null
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/ScopedModelDrivenInterceptor.java
@@ -0,0 +1,166 @@
+/*
+ * 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.interceptor;
+
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.interceptor.ScopedModelDriven;
+import org.apache.struts2.ActionContext;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.StrutsException;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * <!-- START SNIPPET: description -->
+ *
+ * An interceptor that enables scoped model-driven actions.
+ *
+ * <p>This interceptor only activates on actions that implement the {@link 
com.opensymphony.xwork2.interceptor.ScopedModelDriven} interface.  If
+ * detected, it will retrieve the model class from the configured scope, then 
provide it to the Action.</p>
+ *
+ * <!-- END SNIPPET: description -->
+ *
+ * <p><u>Interceptor parameters:</u></p>
+ *
+ * <!-- START SNIPPET: parameters -->
+ *
+ * <ul>
+ *
+ * <li>className - The model class name.  Defaults to the class name of the 
object returned by the getModel() method.</li>
+ *
+ * <li>name - The key to use when storing or retrieving the instance in a 
scope.  Defaults to the model
+ *            class name.</li>
+ *
+ * <li>scope - The scope to store and retrieve the model.  Defaults to 
'request' but can also be 'session'.</li>
+ * </ul>
+ *
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <p><u>Extending the interceptor:</u></p>
+ *
+ * <!-- START SNIPPET: extending -->
+ *
+ * There are no known extension points for this interceptor.
+ *
+ * <!-- END SNIPPET: extending -->
+ *
+ * <p><u>Example code:</u></p>
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ *
+ * &lt;-- Basic usage --&gt;
+ * &lt;interceptor name="scopedModelDriven" 
class="com.opensymphony.interceptor.ScopedModelDrivenInterceptor" /&gt;
+ *
+ * &lt;-- Using all available parameters --&gt;
+ * &lt;interceptor name="gangsterForm" 
class="com.opensymphony.interceptor.ScopedModelDrivenInterceptor"&gt;
+ *      &lt;param name="scope"&gt;session&lt;/param&gt;
+ *      &lt;param name="name"&gt;gangsterForm&lt;/param&gt;
+ *      &lt;param 
name="className"&gt;com.opensymphony.example.GangsterForm&lt;/param&gt;
+ *  &lt;/interceptor&gt;
+ *
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ */
+public class ScopedModelDrivenInterceptor extends AbstractInterceptor {
+
+    private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
+
+    private static final String GET_MODEL = "getModel";
+    private String scope;
+    private String name;
+    private String className;
+    private ObjectFactory objectFactory;
+
+    @Inject
+    public void setObjectFactory(ObjectFactory factory) {
+        this.objectFactory = factory;
+    }
+
+    protected Object resolveModel(ObjectFactory factory, ActionContext 
actionContext, String modelClassName, String modelScope, String modelName) 
throws Exception {
+        Object model;
+        Map<String, Object> scopeMap = actionContext.getContextMap();
+        if ("session".equals(modelScope)) {
+            scopeMap = actionContext.getSession();
+        }
+
+        model = scopeMap.get(modelName);
+        if (model == null) {
+            model = factory.buildBean(modelClassName, null);
+            scopeMap.put(modelName, model);
+        }
+        return model;
+    }
+
+    @Override
+    public String intercept(ActionInvocation invocation) throws Exception {
+        Object action = invocation.getAction();
+
+        if (action instanceof 
com.opensymphony.xwork2.interceptor.ScopedModelDriven) {
+            com.opensymphony.xwork2.interceptor.ScopedModelDriven modelDriven 
= (ScopedModelDriven) action;
+            if (modelDriven.getModel() == null) {
+                ActionContext ctx = ActionContext.getContext();
+                ActionConfig config = invocation.getProxy().getConfig();
+
+                String cName = className;
+                if (cName == null) {
+                    try {
+                        Method method = action.getClass().getMethod(GET_MODEL, 
EMPTY_CLASS_ARRAY);
+                        Class cls = method.getReturnType();
+                        cName = cls.getName();
+                    } catch (NoSuchMethodException e) {
+                        throw new StrutsException("The " + GET_MODEL + "() is 
not defined in action " + action.getClass() + "", config);
+                    }
+                }
+                String modelName = name;
+                if (modelName == null) {
+                    modelName = cName;
+                }
+                Object model = resolveModel(objectFactory, ctx, cName, scope, 
modelName);
+                modelDriven.setModel(model);
+                modelDriven.setScopeKey(modelName);
+            }
+        }
+        return invocation.invoke();
+    }
+
+    /**
+     * @param className the className to set
+     */
+    public void setClassName(String className) {
+        this.className = className;
+    }
+
+    /**
+     * @param name the name to set
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * @param scope the scope to set
+     */
+    public void setScope(String scope) {
+        this.scope = scope;
+    }
+}
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/StaticParametersInterceptor.java
 
b/core/src/main/java/org/apache/struts2/interceptor/StaticParametersInterceptor.java
new file mode 100644
index 000000000..ec0581a73
--- /dev/null
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/StaticParametersInterceptor.java
@@ -0,0 +1,243 @@
+/*
+ * 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.interceptor;
+
+import com.opensymphony.xwork2.LocalizedTextProvider;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.config.entities.Parameterizable;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.interceptor.ParametersInterceptor;
+import com.opensymphony.xwork2.interceptor.ValidationAware;
+import com.opensymphony.xwork2.util.ClearableValueStack;
+import com.opensymphony.xwork2.util.TextParseUtil;
+import com.opensymphony.xwork2.util.ValueStackFactory;
+import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.ActionContext;
+import org.apache.struts2.ActionInvocation;
+import org.apache.struts2.StrutsConstants;
+import org.apache.struts2.dispatcher.HttpParameters;
+import org.apache.struts2.util.ValueStack;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * <!-- START SNIPPET: description -->
+ *
+ * This interceptor populates the action with the static parameters defined in 
the action configuration. If the action
+ * implements {@link Parameterizable}, a map of the static parameters will be 
also be passed directly to the action.
+ * The static params will be added to the request params map, unless "merge" 
is set to false.
+ *
+ * <p> Parameters are typically defined with &lt;param&gt; elements within 
xwork.xml.</p>
+ *
+ * <!-- END SNIPPET: description -->
+ *
+ * <p><u>Interceptor parameters:</u></p>
+ *
+ * <!-- START SNIPPET: parameters -->
+ *
+ * <ul>
+ *
+ * <li>None</li>
+ *
+ * </ul>
+ *
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <p><u>Extending the interceptor:</u></p>
+ *
+ * <!-- START SNIPPET: extending -->
+ *
+ * <p>There are no extension points to this interceptor.</p>
+ *
+ * <!-- END SNIPPET: extending -->
+ *
+ * <p> <u>Example code:</u></p>
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
+ *     &lt;interceptor-ref name="staticParams"&gt;
+ *          &lt;param name="parse"&gt;true&lt;/param&gt;
+ *          &lt;param name="overwrite"&gt;false&lt;/param&gt;
+ *     &lt;/interceptor-ref&gt;
+ *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
+ * &lt;/action&gt;
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @author Patrick Lightbody
+ */
+public class StaticParametersInterceptor extends AbstractInterceptor {
+
+    private boolean parse;
+    private boolean overwrite;
+    private boolean merge = true;
+    private boolean devMode = false;
+
+    private static final Logger LOG = 
LogManager.getLogger(StaticParametersInterceptor.class);
+
+    private ValueStackFactory valueStackFactory;
+    private LocalizedTextProvider localizedTextProvider;
+
+    @Inject
+    public void setValueStackFactory(ValueStackFactory valueStackFactory) {
+        this.valueStackFactory = valueStackFactory;
+    }
+
+    @Inject(StrutsConstants.STRUTS_DEVMODE)
+    public void setDevMode(String mode) {
+        devMode = BooleanUtils.toBoolean(mode);
+    }
+
+    @Inject
+    public void setLocalizedTextProvider(LocalizedTextProvider 
localizedTextProvider) {
+        this.localizedTextProvider = localizedTextProvider;
+    }
+
+    public void setParse(String value) {
+        this.parse = BooleanUtils.toBoolean(value);
+    }
+
+     public void setMerge(String value) {
+         this.merge = BooleanUtils.toBoolean(value);
+    }
+
+    /**
+     * Overwrites already existing parameters from other sources.
+     * Static parameters are the successor over previously set parameters, if 
true.
+     *
+     * @param value enable overwrites of already existing parameters from 
other sources
+     */
+    public void setOverwrite(String value) {
+        this.overwrite = BooleanUtils.toBoolean(value);
+    }
+
+    @Override
+    public String intercept(ActionInvocation invocation) throws Exception {
+        ActionConfig config = invocation.getProxy().getConfig();
+        Object action = invocation.getAction();
+
+        final Map<String, String> parameters = config.getParams();
+
+        LOG.debug("Setting static parameters: {}", parameters);
+
+        // for actions marked as Parameterizable, pass the static parameters 
directly
+        if (action instanceof Parameterizable) {
+            ((Parameterizable) action).setParams(parameters);
+        }
+
+        if (parameters != null) {
+            ActionContext ac = ActionContext.getContext();
+            Map<String, Object> contextMap = ac.getContextMap();
+            try {
+                ReflectionContextState.setCreatingNullObjects(contextMap, 
true);
+                
ReflectionContextState.setReportingConversionErrors(contextMap, true);
+                final ValueStack stack = ac.getValueStack();
+
+                ValueStack newStack = 
valueStackFactory.createValueStack(stack);
+                boolean clearableStack = newStack instanceof 
ClearableValueStack;
+                if (clearableStack) {
+                    //if the stack's context can be cleared, do that to 
prevent OGNL
+                    //from having access to objects in the stack, see XW-641
+                    ((ClearableValueStack)newStack).clearContextValues();
+                    Map<String, Object> context = newStack.getContext();
+                    ReflectionContextState.setCreatingNullObjects(context, 
true);
+                    ReflectionContextState.setDenyMethodExecution(context, 
true);
+                    
ReflectionContextState.setReportingConversionErrors(context, true);
+
+                    //keep locale from original context
+                    
newStack.getActionContext().withLocale(stack.getActionContext().getLocale());
+                }
+
+                for (Map.Entry<String, String> entry : parameters.entrySet()) {
+                    Object val = entry.getValue();
+                    if (parse && val instanceof String) {
+                        val = TextParseUtil.translateVariables(val.toString(), 
stack);
+                    }
+                    try {
+                        newStack.setValue(entry.getKey(), val);
+                    } catch (RuntimeException e) {
+                        if (devMode) {
+
+                            String developerNotification = 
localizedTextProvider.findText(ParametersInterceptor.class, 
"devmode.notification", ActionContext.getContext().getLocale(), "Developer 
Notification:\n{0}", new Object[]{
+                                    "Unexpected Exception caught setting '" + 
entry.getKey() + "' on '" + action.getClass() + ": " + e.getMessage()
+                            });
+                            LOG.error(developerNotification);
+                            if (action instanceof 
com.opensymphony.xwork2.interceptor.ValidationAware) {
+                                ((ValidationAware) 
action).addActionMessage(developerNotification);
+                            }
+                        }
+                    }
+                }
+
+                 if (clearableStack) {
+                     
stack.getActionContext().withConversionErrors(newStack.getActionContext().getConversionErrors());
+                 }
+
+                if (merge)
+                    addParametersToContext(ac, parameters);
+            } finally {
+                ReflectionContextState.setCreatingNullObjects(contextMap, 
false);
+                
ReflectionContextState.setReportingConversionErrors(contextMap, false);
+            }
+        }
+        return invocation.invoke();
+    }
+
+
+    /**
+     * @param ac The action context
+     * @return the parameters from the action mapping in the context.  If none 
found, returns
+     *         an empty map.
+     */
+    protected Map<String, String> retrieveParameters(ActionContext ac) {
+        ActionConfig config = ac.getActionInvocation().getProxy().getConfig();
+        if (config != null) {
+            return config.getParams();
+        } else {
+            return Collections.emptyMap();
+        }
+    }
+
+    /**
+     * Adds the parameters into context's ParameterMap.
+     * As default, static parameters will not overwrite existing parameters 
from other sources.
+     * If you want the static parameters as successor over already existing 
parameters, set overwrite to <tt>true</tt>.
+     *
+     * @param ac        The action context
+     * @param newParams The parameter map to apply
+     */
+    protected void addParametersToContext(ActionContext ac, Map<String, ?> 
newParams) {
+        HttpParameters previousParams = ac.getParameters();
+
+        HttpParameters.Builder combinedParams;
+        if (overwrite) {
+            combinedParams = HttpParameters.create().withParent( 
previousParams);
+            combinedParams = combinedParams.withExtraParams(newParams);
+        } else {
+            combinedParams = HttpParameters.create(newParams);
+            combinedParams = combinedParams.withExtraParams(previousParams);
+        }
+        ac.withParameters(combinedParams.build());
+    }
+}

Reply via email to