struts-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From lukaszlen...@apache.org
Subject [50/57] [partial] struts git commit: Merges xwork packages into struts
Date Wed, 17 Jun 2015 21:09:50 GMT
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java b/core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java
new file mode 100644
index 0000000..859ccfd
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed 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 com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.TextParseUtil;
+import com.opensymphony.xwork2.util.ValueStack;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.*;
+
+
+/**
+* <!-- START SNIPPET: description -->
+*
+* This result invokes an entire other action, complete with it's own interceptor stack and result.
+*
+* <!-- END SNIPPET: description -->
+*
+* <b>This result type takes the following parameters:</b>
+*
+* <!-- START SNIPPET: params -->
+*
+* <ul>
+*
+* <li><b>actionName (default)</b> - the name of the action that will be chained to</li>
+*
+* <li><b>namespace</b> - used to determine which namespace the Action is in that we're chaining. If namespace is null,
+* this defaults to the current namespace</li>
+*
+* <li><b>method</b> - used to specify another method on target action to be invoked.
+* If null, this defaults to execute method</li>
+*
+* <li><b>skipActions</b> - (optional) the list of comma separated action names for the
+* actions that could be chained to</li>
+*
+* </ul>
+*
+* <!-- END SNIPPET: params -->
+*
+* <b>Example:</b>
+*
+* <pre><!-- START SNIPPET: example -->
+* &lt;package name="public" extends="struts-default"&gt;
+*     &lt;!-- Chain creatAccount to login, using the default parameter --&gt;
+*     &lt;action name="createAccount" class="..."&gt;
+*         &lt;result type="chain"&gt;login&lt;/result&gt;
+*     &lt;/action&gt;
+*
+*     &lt;action name="login" class="..."&gt;
+*         &lt;!-- Chain to another namespace --&gt;
+*         &lt;result type="chain"&gt;
+*             &lt;param name="actionName"&gt;dashboard&lt;/param&gt;
+*             &lt;param name="namespace"&gt;/secure&lt;/param&gt;
+*         &lt;/result&gt;
+*     &lt;/action&gt;
+* &lt;/package&gt;
+*
+* &lt;package name="secure" extends="struts-default" namespace="/secure"&gt;
+*     &lt;action name="dashboard" class="..."&gt;
+*         &lt;result&gt;dashboard.jsp&lt;/result&gt;
+*     &lt;/action&gt;
+* &lt;/package&gt;
+* <!-- END SNIPPET: example --></pre>
+*
+* @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+*/
+public class ActionChainResult implements Result {
+
+    private static final Logger LOG = LogManager.getLogger(ActionChainResult.class);
+
+    /**
+     * The result parameter name to set the name of the action to chain to.
+     */
+    public static final String DEFAULT_PARAM = "actionName";
+
+    /**
+     * The action context key to save the chain history.
+     */
+    private static final String CHAIN_HISTORY = "CHAIN_HISTORY";
+
+    /**
+     * The result parameter name to set the name of the action to chain to.
+     */
+    public static final String SKIP_ACTIONS_PARAM = "skipActions";
+
+
+    private ActionProxy proxy;
+    private String actionName;
+    
+    private String namespace;
+
+    private String methodName;
+
+    /**
+     * The list of actions to skip.
+     */
+    private String skipActions;
+
+    private ActionProxyFactory actionProxyFactory;
+
+    public ActionChainResult() {
+        super();
+    }
+
+    public ActionChainResult(String namespace, String actionName, String methodName) {
+        this.namespace = namespace;
+        this.actionName = actionName;
+        this.methodName = methodName;
+    }
+
+    public ActionChainResult(String namespace, String actionName, String methodName, String skipActions) {
+        this.namespace = namespace;
+        this.actionName = actionName;
+        this.methodName = methodName;
+        this.skipActions = skipActions;
+    }
+
+
+    /**
+     * @param actionProxyFactory the actionProxyFactory to set
+     */
+    @Inject
+    public void setActionProxyFactory(ActionProxyFactory actionProxyFactory) {
+        this.actionProxyFactory = actionProxyFactory;
+    }
+
+    /**
+     * Set the action name.
+     *
+     * @param actionName The action name.
+     */
+    public void setActionName(String actionName) {
+        this.actionName = actionName;
+    }
+
+    /**
+     * sets the namespace of the Action that we're chaining to.  if namespace
+     * is null, this defaults to the current namespace.
+     *
+     * @param namespace the name of the namespace we're chaining to
+     */
+    public void setNamespace(String namespace) {
+        this.namespace = namespace;
+    }
+
+    /**
+     * Set the list of actions to skip.
+     * To test if an action should not throe an infinite recursion,
+     * only the action name is used, not the namespace.
+     *
+     * @param actions The list of action name separated by a white space.
+     */
+    public void setSkipActions(String actions) {
+        this.skipActions = actions;
+    }
+
+
+    public void setMethod(String method) {
+        this.methodName = method;
+    }
+
+    public ActionProxy getProxy() {
+        return proxy;
+    }
+
+    /**
+     * Get the XWork chain history.
+     * The stack is a list of <code>namespace/action!method</code> keys.
+     */
+    public static LinkedList<String> getChainHistory() {
+        LinkedList<String> chainHistory = (LinkedList<String>) ActionContext.getContext().get(CHAIN_HISTORY);
+        //  Add if not exists
+        if (chainHistory == null) {
+            chainHistory = new LinkedList<>();
+            ActionContext.getContext().put(CHAIN_HISTORY, chainHistory);
+        }
+
+        return chainHistory;
+    }
+
+    /**
+     * @param invocation the DefaultActionInvocation calling the action call stack
+     */
+    public void execute(ActionInvocation invocation) throws Exception {
+        // if the finalNamespace wasn't explicitly defined, assume the current one
+        if (this.namespace == null) {
+            this.namespace = invocation.getProxy().getNamespace();
+        }
+
+        ValueStack stack = ActionContext.getContext().getValueStack();
+        String finalNamespace = TextParseUtil.translateVariables(namespace, stack);
+        String finalActionName = TextParseUtil.translateVariables(actionName, stack);
+        String finalMethodName = this.methodName != null
+                ? TextParseUtil.translateVariables(this.methodName, stack)
+                : null;
+
+        if (isInChainHistory(finalNamespace, finalActionName, finalMethodName)) {
+            addToHistory(finalNamespace, finalActionName, finalMethodName);
+            throw new XWorkException("Infinite recursion detected: " + ActionChainResult.getChainHistory().toString());
+        }
+
+        if (ActionChainResult.getChainHistory().isEmpty() && invocation != null && invocation.getProxy() != null) {
+            addToHistory(finalNamespace, invocation.getProxy().getActionName(), invocation.getProxy().getMethod());
+        }
+        addToHistory(finalNamespace, finalActionName, finalMethodName);
+
+        HashMap<String, Object> extraContext = new HashMap<>();
+        extraContext.put(ActionContext.VALUE_STACK, ActionContext.getContext().getValueStack());
+        extraContext.put(ActionContext.PARAMETERS, ActionContext.getContext().getParameters());
+        extraContext.put(CHAIN_HISTORY, ActionChainResult.getChainHistory());
+
+        LOG.debug("Chaining to action {}", finalActionName);
+
+        proxy = actionProxyFactory.createActionProxy(finalNamespace, finalActionName, finalMethodName, extraContext);
+        proxy.execute();
+    }
+
+    @Override public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        final ActionChainResult that = (ActionChainResult) o;
+
+        if (actionName != null ? !actionName.equals(that.actionName) : that.actionName != null) return false;
+        if (methodName != null ? !methodName.equals(that.methodName) : that.methodName != null) return false;
+        if (namespace != null ? !namespace.equals(that.namespace) : that.namespace != null) return false;
+
+        return true;
+    }
+
+    @Override public int hashCode() {
+        int result;
+        result = (actionName != null ? actionName.hashCode() : 0);
+        result = 31 * result + (namespace != null ? namespace.hashCode() : 0);
+        result = 31 * result + (methodName != null ? methodName.hashCode() : 0);
+        return result;
+    }
+
+    private boolean isInChainHistory(String namespace, String actionName, String methodName) {
+        LinkedList<? extends String> chainHistory = ActionChainResult.getChainHistory();
+
+        if (chainHistory == null) {
+            return false;
+        } else {
+            //  Actions to skip
+            Set<String> skipActionsList = new HashSet<>();
+            if (skipActions != null && skipActions.length() > 0) {
+                ValueStack stack = ActionContext.getContext().getValueStack();
+                String finalSkipActions = TextParseUtil.translateVariables(this.skipActions, stack);
+                skipActionsList.addAll(TextParseUtil.commaDelimitedStringToSet(finalSkipActions));
+            }
+            if (!skipActionsList.contains(actionName)) {
+                //  Get if key is in the chain history
+                return chainHistory.contains(makeKey(namespace, actionName, methodName));
+            }
+
+            return false;
+        }
+    }
+
+    private void addToHistory(String namespace, String actionName, String methodName) {
+        List<String> chainHistory = ActionChainResult.getChainHistory();
+        chainHistory.add(makeKey(namespace, actionName, methodName));
+    }
+
+    private String makeKey(String namespace, String actionName, String methodName) {
+        if (null == methodName) {
+            return namespace + "/" + actionName;
+        }
+
+        return namespace + "/" + actionName + "!" + methodName;
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ActionContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionContext.java b/core/src/main/java/com/opensymphony/xwork2/ActionContext.java
new file mode 100644
index 0000000..60ff183
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionContext.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed 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 com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.util.ValueStack;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+
+/**
+ * The ActionContext is the context in which an {@link Action} is executed. Each context is basically a
+ * container of objects an action needs for execution like the session, parameters, locale, etc. <p>
+ * <p/>
+ * The ActionContext is thread local which means that values stored in the ActionContext are
+ * unique per thread. See the {@link ThreadLocal} class for more information. The benefit of
+ * this is you don't need to worry about a user specific action context, you just get it:
+ * <p/>
+ * <ul><code>ActionContext context = ActionContext.getContext();</code></ul>
+ * <p/>
+ * Finally, because of the thread local usage you don't need to worry about making your actions thread safe.
+ *
+ * @author Patrick Lightbody
+ * @author Bill Lynch (docs)
+ */
+public class ActionContext implements Serializable {
+
+    static ThreadLocal<ActionContext> actionContext = new ThreadLocal<>();
+
+    /**
+     * Constant for the name of the action being executed.
+     */
+    public static final String ACTION_NAME = "com.opensymphony.xwork2.ActionContext.name";
+
+    /**
+     * Constant for the {@link com.opensymphony.xwork2.util.ValueStack OGNL value stack}.
+     */
+    public static final String VALUE_STACK = ValueStack.VALUE_STACK;
+
+    /**
+     * Constant for the action's session.
+     */
+    public static final String SESSION = "com.opensymphony.xwork2.ActionContext.session";
+
+    /**
+     * Constant for the action's application context.
+     */
+    public static final String APPLICATION = "com.opensymphony.xwork2.ActionContext.application";
+
+    /**
+     * Constant for the action's parameters.
+     */
+    public static final String PARAMETERS = "com.opensymphony.xwork2.ActionContext.parameters";
+
+    /**
+     * Constant for the action's locale.
+     */
+    public static final String LOCALE = "com.opensymphony.xwork2.ActionContext.locale";
+
+    /**
+     * Constant for the action's type converter.
+     */
+    public static final String TYPE_CONVERTER = "com.opensymphony.xwork2.ActionContext.typeConverter";
+
+    /**
+     * Constant for the action's {@link com.opensymphony.xwork2.ActionInvocation invocation} context.
+     */
+    public static final String ACTION_INVOCATION = "com.opensymphony.xwork2.ActionContext.actionInvocation";
+
+    /**
+     * Constant for the map of type conversion errors.
+     */
+    public static final String CONVERSION_ERRORS = "com.opensymphony.xwork2.ActionContext.conversionErrors";
+
+
+    /**
+     * Constant for the container
+     */
+    public static final String CONTAINER = "com.opensymphony.xwork2.ActionContext.container";
+    
+    private Map<String, Object> context;
+
+    /**
+     * Creates a new ActionContext initialized with another context.
+     *
+     * @param context a context map.
+     */
+    public ActionContext(Map<String, Object> context) {
+        this.context = context;
+    }
+
+
+    /**
+     * Sets the action invocation (the execution state).
+     *
+     * @param actionInvocation the action execution state.
+     */
+    public void setActionInvocation(ActionInvocation actionInvocation) {
+        put(ACTION_INVOCATION, actionInvocation);
+    }
+
+    /**
+     * Gets the action invocation (the execution state).
+     *
+     * @return the action invocation (the execution state).
+     */
+    public ActionInvocation getActionInvocation() {
+        return (ActionInvocation) get(ACTION_INVOCATION);
+    }
+
+    /**
+     * Sets the action's application context.
+     *
+     * @param application the action's application context.
+     */
+    public void setApplication(Map<String, Object> application) {
+        put(APPLICATION, application);
+    }
+
+    /**
+     * Returns a Map of the ServletContext when in a servlet environment or a generic application level Map otherwise.
+     *
+     * @return a Map of ServletContext or generic application level Map
+     */
+    public Map<String, Object> getApplication() {
+        return (Map<String, Object>) get(APPLICATION);
+    }
+
+    /**
+     * Sets the action context for the current thread.
+     *
+     * @param context the action context.
+     */
+    public static void setContext(ActionContext context) {
+        actionContext.set(context);
+    }
+
+    /**
+     * Returns the ActionContext specific to the current thread.
+     *
+     * @return the ActionContext for the current thread, is never <tt>null</tt>.
+     */
+    public static ActionContext getContext() {
+        return actionContext.get();
+    }
+
+    /**
+     * Sets the action's context map.
+     *
+     * @param contextMap the context map.
+     */
+    public void setContextMap(Map<String, Object> contextMap) {
+        getContext().context = contextMap;
+    }
+
+    /**
+     * Gets the context map.
+     *
+     * @return the context map.
+     */
+    public Map<String, Object> getContextMap() {
+        return context;
+    }
+
+    /**
+     * Sets conversion errors which occurred when executing the action.
+     *
+     * @param conversionErrors a Map of errors which occurred when executing the action.
+     */
+    public void setConversionErrors(Map<String, Object> conversionErrors) {
+        put(CONVERSION_ERRORS, conversionErrors);
+    }
+
+    /**
+     * Gets the map of conversion errors which occurred when executing the action.
+     *
+     * @return the map of conversion errors which occurred when executing the action or an empty map if
+     *         there were no errors.
+     */
+    public Map<String, Object> getConversionErrors() {
+        Map<String, Object> errors = (Map) get(CONVERSION_ERRORS);
+
+        if (errors == null) {
+            errors = new HashMap<>();
+            setConversionErrors(errors);
+        }
+
+        return errors;
+    }
+
+    /**
+     * Sets the Locale for the current action.
+     *
+     * @param locale the Locale for the current action.
+     */
+    public void setLocale(Locale locale) {
+        put(LOCALE, locale);
+    }
+
+    /**
+     * Gets the Locale of the current action. If no locale was ever specified the platform's
+     * {@link java.util.Locale#getDefault() default locale} is used.
+     *
+     * @return the Locale of the current action.
+     */
+    public Locale getLocale() {
+        Locale locale = (Locale) get(LOCALE);
+
+        if (locale == null) {
+            locale = Locale.getDefault();
+            setLocale(locale);
+        }
+
+        return locale;
+    }
+
+    /**
+     * Sets the name of the current Action in the ActionContext.
+     *
+     * @param name the name of the current action.
+     */
+    public void setName(String name) {
+        put(ACTION_NAME, name);
+    }
+
+    /**
+     * Gets the name of the current Action.
+     *
+     * @return the name of the current action.
+     */
+    public String getName() {
+        return (String) get(ACTION_NAME);
+    }
+
+    /**
+     * Sets the action parameters.
+     *
+     * @param parameters the parameters for the current action.
+     */
+    public void setParameters(Map<String, Object> parameters) {
+        put(PARAMETERS, parameters);
+    }
+
+    /**
+     * Returns a Map of the HttpServletRequest parameters when in a servlet environment or a generic Map of
+     * parameters otherwise.
+     *
+     * @return a Map of HttpServletRequest parameters or a multipart map when in a servlet environment, or a
+     *         generic Map of parameters otherwise.
+     */
+    public Map<String, Object> getParameters() {
+        return (Map<String, Object>) get(PARAMETERS);
+    }
+
+    /**
+     * Sets a map of action session values.
+     *
+     * @param session  the session values.
+     */
+    public void setSession(Map<String, Object> session) {
+        put(SESSION, session);
+    }
+
+    /**
+     * Gets the Map of HttpSession values when in a servlet environment or a generic session map otherwise.
+     *
+     * @return the Map of HttpSession values when in a servlet environment or a generic session map otherwise.
+     */
+    public Map<String, Object> getSession() {
+        return (Map<String, Object>) get(SESSION);
+    }
+
+    /**
+     * Sets the OGNL value stack.
+     *
+     * @param stack the OGNL value stack.
+     */
+    public void setValueStack(ValueStack stack) {
+        put(VALUE_STACK, stack);
+    }
+
+    /**
+     * Gets the OGNL value stack.
+     *
+     * @return the OGNL value stack.
+     */
+    public ValueStack getValueStack() {
+        return (ValueStack) get(VALUE_STACK);
+    }
+    
+    /**
+     * Gets the container for this request
+     * 
+     * @param cont The container
+     */
+    public void setContainer(Container cont) {
+        put(CONTAINER, cont);
+    }
+    
+    /**
+     * Sets the container for this request
+     * 
+     * @return The container
+     */
+    public Container getContainer() {
+        return (Container) get(CONTAINER);
+    }
+    
+    public <T> T getInstance(Class<T> type) {
+        Container cont = getContainer();
+        if (cont != null) {
+            return cont.getInstance(type);
+        } else {
+            throw new XWorkException("Cannot find an initialized container for this request.");
+        }
+    }
+
+    /**
+     * Returns a value that is stored in the current ActionContext by doing a lookup using the value's key.
+     *
+     * @param key the key used to find the value.
+     * @return the value that was found using the key or <tt>null</tt> if the key was not found.
+     */
+    public Object get(String key) {
+        return context.get(key);
+    }
+
+    /**
+     * Stores a value in the current ActionContext. The value can be looked up using the key.
+     *
+     * @param key   the key of the value.
+     * @param value the value to be stored.
+     */
+    public void put(String key, Object value) {
+        context.put(key, value);
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java b/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java
new file mode 100644
index 0000000..58c992a
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionEventListener.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed 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 com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.util.ValueStack;
+
+/**
+ * Provides hooks for handling key action events
+ */
+public interface ActionEventListener {
+    /**
+     * Called after an action has been created. 
+     * 
+     * @param action The action
+     * @param stack The current value stack
+     * @return The action to use
+     */
+    public Object prepare(Object action, ValueStack stack);
+    
+    /**
+     * Called when an exception is thrown by the action
+     * 
+     * @param t The exception/error that was thrown
+     * @param stack The current value stack
+     * @return A result code to execute, can be null
+     */
+    public String handleException(Throwable t, ValueStack stack);
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
new file mode 100644
index 0000000..d94516b
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionInvocation.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ * 
+ * Licensed 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 com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.interceptor.PreResultListener;
+import com.opensymphony.xwork2.util.ValueStack;
+
+import java.io.Serializable;
+
+
+/**
+ * An {@link ActionInvocation} represents the execution state of an {@link Action}. It holds the Interceptors and the Action instance.
+ * By repeated re-entrant execution of the <code>invoke()</code> method, initially by the {@link ActionProxy}, then by the Interceptors, the
+ * Interceptors are all executed, and then the {@link Action} and the {@link Result}.
+ *
+ * @author Jason Carreira
+ * @see com.opensymphony.xwork2.ActionProxy
+ */
+public interface ActionInvocation extends Serializable {
+
+    /**
+     * Get the Action associated with this ActionInvocation.
+     *
+     * @return the Action
+     */
+    Object getAction();
+
+    /**
+     * Gets whether this ActionInvocation has executed before.
+     * This will be set after the Action and the Result have executed.
+     *
+     * @return <tt>true</tt> if this ActionInvocation has executed before.
+     */
+    boolean isExecuted();
+
+    /**
+     * Gets the ActionContext associated with this ActionInvocation. The ActionProxy is
+     * responsible for setting this ActionContext onto the ThreadLocal before invoking
+     * the ActionInvocation and resetting the old ActionContext afterwards.
+     *
+     * @return the ActionContext.
+     */
+    ActionContext getInvocationContext();
+
+    /**
+     * Get the ActionProxy holding this ActionInvocation.
+     *
+     * @return the ActionProxy.
+     */
+    ActionProxy getProxy();
+
+    /**
+     * If the ActionInvocation has been executed before and the Result is an instance of {@link ActionChainResult}, this method
+     * will walk down the chain of <code>ActionChainResult</code>s until it finds a non-chain result, which will be returned. If the
+     * ActionInvocation's result has not been executed before, the Result instance will be created and populated with
+     * the result params.
+     *
+     * @return the result.
+     * @throws Exception can be thrown.
+     */
+    Result getResult() throws Exception;
+
+    /**
+     * Gets the result code returned from this ActionInvocation.
+     *
+     * @return the result code
+     */
+    String getResultCode();
+
+    /**
+     * Sets the result code, possibly overriding the one returned by the
+     * action.
+     * <p/>
+     * The "intended" purpose of this method is to allow PreResultListeners to
+     * override the result code returned by the Action.
+     * <p/>
+     * If this method is used before the Action executes, the Action's returned
+     * result code will override what was set. However the Action could (if
+     * specifically coded to do so) inspect the ActionInvocation to see that
+     * someone "upstream" (e.g. an Interceptor) had suggested a value as the
+     * result, and it could therefore return the same value itself.
+     * <p/>
+     * If this method is called between the Action execution and the Result
+     * execution, then the value set here will override the result code the
+     * action had returned.  Creating an Interceptor that implements
+     * {@link PreResultListener} will give you this oportunity.
+     * <p/>
+     * If this method is called after the Result has been executed, it will
+     * have the effect of raising an IllegalStateException.
+     *
+     * @param resultCode  the result code.
+     * @throws IllegalStateException if called after the Result has been executed.
+     * @see #isExecuted()
+     */
+    void setResultCode(String resultCode);
+
+    /**
+     * Gets the ValueStack associated with this ActionInvocation.
+     *
+     * @return the ValueStack
+     */
+    ValueStack getStack();
+
+    /**
+     * Register a {@link PreResultListener} to be notified after the Action is executed and
+     * before the Result is executed.
+     * <p/>
+     * The ActionInvocation implementation must guarantee that listeners will be called in
+     * the order in which they are registered.
+     * <p/>
+     * Listener registration and execution does not need to be thread-safe.
+     *
+     * @param listener the listener to add.
+     */
+    void addPreResultListener(PreResultListener listener);
+
+    /**
+     * Invokes the next step in processing this ActionInvocation.
+     * <p/>
+     * If there are more Interceptors, this will call the next one. If Interceptors choose not to short-circuit
+     * ActionInvocation processing and return their own return code, they will call invoke() to allow the next Interceptor
+     * to execute. If there are no more Interceptors to be applied, the Action is executed.
+     * If the {@link ActionProxy#getExecuteResult()} method returns <tt>true</tt>, the Result is also executed.
+     *
+     * @throws Exception can be thrown.
+     * @return the return code.
+     */
+    String invoke() throws Exception;
+
+    /**
+     * Invokes only the Action (not Interceptors or Results).
+     * <p/>
+     * This is useful in rare situations where advanced usage with the interceptor/action/result workflow is
+     * being manipulated for certain functionality.
+     *
+     * @return the return code.
+     * @throws Exception can be thrown.
+     */
+    String invokeActionOnly() throws Exception;
+
+    /**
+     * Sets the action event listener to respond to key action events.
+     *
+     * @param listener the listener.
+     */
+    void setActionEventListener(ActionEventListener listener);
+
+    void init(ActionProxy proxy) ;
+
+    /**
+     * Prepares instance of ActionInvocation to be serializable,
+     * which simple means removing all unserializable fields, eg. Container
+     *
+     * @return ActionInvocation which can be serialize (eg. into HttpSession)
+     */
+    ActionInvocation serialize();
+
+    /**
+     * Performs opposite process to restore back ActionInvocation after deserialisation
+     *
+     * @param actionContext current {@link ActionContext}
+     * @return fully operational ActionInvocation
+     */
+    ActionInvocation deserialize(ActionContext actionContext);
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ActionProxy.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionProxy.java b/core/src/main/java/com/opensymphony/xwork2/ActionProxy.java
new file mode 100644
index 0000000..95110ac
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionProxy.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ * 
+ * Licensed 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 com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+
+
+/**
+ * ActionProxy is an extra layer between XWork and the action so that different proxies are possible.
+ * <p/>
+ * An example of this would be a remote proxy, where the layer between XWork and the action might be RMI or SOAP.
+ *
+ * @author Jason Carreira
+ */
+public interface ActionProxy {
+
+    /**
+     * Gets the Action instance for this Proxy.
+     *
+     * @return the Action instance
+     */
+    Object getAction();
+
+    /**
+     * Gets the alias name this ActionProxy is mapped to.
+     *
+     * @return the alias name
+     */
+    String getActionName();
+
+    /**
+     * Gets the ActionConfig this ActionProxy is built from.
+     *
+     * @return the ActionConfig
+     */
+    ActionConfig getConfig();
+
+    /**
+     * Sets whether this ActionProxy should also execute the Result after executing the Action.
+     *
+     * @param executeResult <tt>true</tt> to also execute the Result.
+     */
+    void setExecuteResult(boolean executeResult);
+
+    /**
+     * Gets the status of whether the ActionProxy is set to execute the Result after the Action is executed.
+     *
+     * @return the status
+     */
+    boolean getExecuteResult();
+
+    /**
+     * Gets the ActionInvocation associated with this ActionProxy.
+     *
+     * @return the ActionInvocation
+     */
+    ActionInvocation getInvocation();
+
+    /**
+     * Gets the namespace the ActionConfig for this ActionProxy is mapped to.
+     *
+     * @return the namespace
+     */
+    String getNamespace();
+
+    /**
+     * Execute this ActionProxy. This will set the ActionContext from the ActionInvocation into the ActionContext
+     * ThreadLocal before invoking the ActionInvocation, then set the old ActionContext back into the ThreadLocal.
+     *
+     * @return the result code returned from executing the ActionInvocation
+     * @throws Exception can be thrown.
+     * @see ActionInvocation
+     */
+    String execute() throws Exception;
+
+    /**
+     * Gets the method name to execute, or <tt>null</tt> if no method has been specified (meaning <code>execute</code> will be invoked).
+     *
+     * @return the method to execute
+     */
+    String getMethod();
+
+    /**
+     * Gets status of the method value's initialization.
+     *
+     * @return true if the method returned by getMethod() is not a default initializer value.
+     */
+    boolean isMethodSpecified();
+    
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ActionProxyFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionProxyFactory.java b/core/src/main/java/com/opensymphony/xwork2/ActionProxyFactory.java
new file mode 100644
index 0000000..5cf4de0
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionProxyFactory.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2002-2007,2009 The Apache Software Foundation.
+ * 
+ * Licensed 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 com.opensymphony.xwork2;
+
+import java.util.Map;
+
+
+/**
+ * The {@link ActionProxyFactory} is used to create {@link ActionProxy}s to be executed.
+ * <p/>
+ * It is the entry point to XWork that is used by a dispatcher to create an {@link ActionProxy} to execute
+ * for a particular namespace and action name.
+ *
+ * @author Jason Carreira
+ * @see DefaultActionProxyFactory
+ */
+public interface ActionProxyFactory {
+
+    /**
+     * Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy
+     * should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated.
+     * <p/>
+     * <b>Note:</b> This is the most used create method.
+     *
+     * @param namespace    the namespace of the action, can be <tt>null</tt>
+     * @param actionName   the name of the action
+     * @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be <tt>null</tt>
+     * @return ActionProxy  the created action proxy
+     * @deprecated Since 2.1.1, use {@link #createActionProxy(String,String,String,Map) instead}
+     */
+    @Deprecated public ActionProxy createActionProxy(String namespace, String actionName, Map<String, Object> extraContext);
+
+    /**
+     * Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy
+     * should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated.
+     * <p/>
+     * <b>Note:</b> This is the most used create method.
+     *
+     * @param namespace    the namespace of the action, can be <tt>null</tt>
+     * @param actionName   the name of the action
+     * @param methodName   the name of the method to execute
+     * @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be <tt>null</tt>
+     * @return ActionProxy  the created action proxy
+     * @since 2.1.1
+     */
+    public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext);
+
+    /**
+     * Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy
+     * should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated.
+     *
+     * @param namespace    the namespace of the action, can be <tt>null</tt>
+     * @param actionName   the name of the action
+     * @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be <tt>null</tt>
+     * @param executeResult flag which tells whether the result should be executed after the action
+     * @param cleanupContext flag which tells whether the original context should be preserved during execution of the proxy.
+     * @return ActionProxy  the created action proxy
+     * @deprecated Since 2.1.1, use {@link #createActionProxy(String,String,String,Map,boolean,boolean)} instead
+     */
+    @Deprecated public ActionProxy createActionProxy(String namespace, String actionName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext);
+
+    /**
+     * Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy
+     * should be fully initialized when it is returned, including having an {@link ActionInvocation} instance associated.
+     *
+     * @param namespace    the namespace of the action, can be <tt>null</tt>
+     * @param actionName   the name of the action
+     * @param methodName   the name of the method to execute
+     * @param extraContext a Map of extra parameters to be provided to the ActionProxy, can be <tt>null</tt>
+     * @param executeResult flag which tells whether the result should be executed after the action
+     * @param cleanupContext flag which tells whether the original context should be preserved during execution of the proxy.
+     * @return ActionProxy  the created action proxy
+     * @since 2.1.1
+     */
+    public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext);
+
+
+     /**
+     * Creates an {@link ActionProxy} for the given namespace and action name by looking up the configuration.The ActionProxy
+     * should be fully initialized when it is returned, including passed {@link ActionInvocation} instance.
+     *
+     * @param actionInvocation the action invocation instance to associate with
+     * @param namespace    the namespace of the action, can be <tt>null</tt>
+     * @param actionName   the name of the action
+     * @param methodName   the name of the method to execute
+     * @param executeResult flag which tells whether the result should be executed after the action
+     * @param cleanupContext flag which tells whether the original context should be preserved during execution of the proxy.
+     * @return ActionProxy  the created action proxy
+     * @since 2.1.1
+     */
+    public ActionProxy createActionProxy(ActionInvocation actionInvocation, String namespace, String actionName, String methodName,
+                                         boolean executeResult, boolean cleanupContext);
+    
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java b/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java
new file mode 100644
index 0000000..fd8675b
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/ActionSupport.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed 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 com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.ValueStack;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.Serializable;
+import java.util.*;
+
+
+/**
+ * Provides a default implementation for the most common actions.
+ * See the documentation for all the interfaces this class implements for more detailed information.
+ */
+public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable {
+
+    protected static Logger LOG = LogManager.getLogger(ActionSupport.class);
+
+    private final ValidationAwareSupport validationAware = new ValidationAwareSupport();
+
+    private transient TextProvider textProvider;
+    private Container container;
+
+    public void setActionErrors(Collection<String> errorMessages) {
+        validationAware.setActionErrors(errorMessages);
+    }
+
+    public Collection<String> getActionErrors() {
+        return validationAware.getActionErrors();
+    }
+
+    public void setActionMessages(Collection<String> messages) {
+        validationAware.setActionMessages(messages);
+    }
+
+    public Collection<String> getActionMessages() {
+        return validationAware.getActionMessages();
+    }
+
+    /**
+     * @deprecated Use {@link #getActionErrors()}.
+     */
+    @Deprecated
+    public Collection<String> getErrorMessages() {
+        return getActionErrors();
+    }
+
+    /**
+     * @deprecated Use {@link #getFieldErrors()}.
+     */
+    @Deprecated
+    public Map<String, List<String>> getErrors() {
+        return getFieldErrors();
+    }
+
+    public void setFieldErrors(Map<String, List<String>> errorMap) {
+        validationAware.setFieldErrors(errorMap);
+    }
+
+    public Map<String, List<String>> getFieldErrors() {
+        return validationAware.getFieldErrors();
+    }
+
+    public Locale getLocale() {
+        ActionContext ctx = ActionContext.getContext();
+        if (ctx != null) {
+            return ctx.getLocale();
+        } else {
+        	LOG.debug("Action context not initialized");
+            return null;
+        }
+    }
+
+    public boolean hasKey(String key) {
+        return getTextProvider().hasKey(key);
+    }
+
+    public String getText(String aTextName) {
+        return getTextProvider().getText(aTextName);
+    }
+
+    public String getText(String aTextName, String defaultValue) {
+        return getTextProvider().getText(aTextName, defaultValue);
+    }
+
+    public String getText(String aTextName, String defaultValue, String obj) {
+        return getTextProvider().getText(aTextName, defaultValue, obj);
+    }
+
+    public String getText(String aTextName, List<?> args) {
+        return getTextProvider().getText(aTextName, args);
+    }
+
+    public String getText(String key, String[] args) {
+        return getTextProvider().getText(key, args);
+    }
+
+    public String getText(String aTextName, String defaultValue, List<?> args) {
+        return getTextProvider().getText(aTextName, defaultValue, args);
+    }
+
+    public String getText(String key, String defaultValue, String[] args) {
+        return getTextProvider().getText(key, defaultValue, args);
+    }
+
+    public String getText(String key, String defaultValue, List<?> args, ValueStack stack) {
+        return getTextProvider().getText(key, defaultValue, args, stack);
+    }
+
+    public String getText(String key, String defaultValue, String[] args, ValueStack stack) {
+        return getTextProvider().getText(key, defaultValue, args, stack);
+    }
+
+    /**
+     * Dedicated method to support I10N and conversion errors
+     *
+     * @param key message which contains formatting string
+     * @param expr that should be formatted
+     * @return formatted expr with format specified by key
+     */
+    public String getFormatted(String key, String expr) {
+        Map<String, Object> conversionErrors = ActionContext.getContext().getConversionErrors();
+        if (conversionErrors.containsKey(expr)) {
+            String[] vals = (String[]) conversionErrors.get(expr);
+            return vals[0];
+        } else {
+            final ValueStack valueStack = ActionContext.getContext().getValueStack();
+            final Object val = valueStack.findValue(expr);
+            return getText(key, Arrays.asList(val));
+        }
+    }
+
+    public ResourceBundle getTexts() {
+        return getTextProvider().getTexts();
+    }
+
+    public ResourceBundle getTexts(String aBundleName) {
+        return getTextProvider().getTexts(aBundleName);
+    }
+
+    public void addActionError(String anErrorMessage) {
+        validationAware.addActionError(anErrorMessage);
+    }
+
+    public void addActionMessage(String aMessage) {
+        validationAware.addActionMessage(aMessage);
+    }
+
+    public void addFieldError(String fieldName, String errorMessage) {
+        validationAware.addFieldError(fieldName, errorMessage);
+    }
+
+    public String input() throws Exception {
+        return INPUT;
+    }
+
+    public String doDefault() throws Exception {
+        return SUCCESS;
+    }
+
+    /**
+     * A default implementation that does nothing an returns "success".
+     * <p/>
+     * Subclasses should override this method to provide their business logic.
+     * <p/>
+     * See also {@link com.opensymphony.xwork2.Action#execute()}.
+     *
+     * @return returns {@link #SUCCESS}
+     * @throws Exception can be thrown by subclasses.
+     */
+    public String execute() throws Exception {
+        return SUCCESS;
+    }
+
+    public boolean hasActionErrors() {
+        return validationAware.hasActionErrors();
+    }
+
+    public boolean hasActionMessages() {
+        return validationAware.hasActionMessages();
+    }
+
+    public boolean hasErrors() {
+        return validationAware.hasErrors();
+    }
+
+    public boolean hasFieldErrors() {
+        return validationAware.hasFieldErrors();
+    }
+
+    /**
+     * Clears field errors. Useful for Continuations and other situations
+     * where you might want to clear parts of the state on the same action.
+     */
+    public void clearFieldErrors() {
+        validationAware.clearFieldErrors();
+    }
+
+    /**
+     * Clears action errors. Useful for Continuations and other situations
+     * where you might want to clear parts of the state on the same action.
+     */
+    public void clearActionErrors() {
+        validationAware.clearActionErrors();
+    }
+
+    /**
+     * Clears messages. Useful for Continuations and other situations
+     * where you might want to clear parts of the state on the same action.
+     */
+    public void clearMessages() {
+        validationAware.clearMessages();
+    }
+
+    /**
+     * Clears all errors. Useful for Continuations and other situations
+     * where you might want to clear parts of the state on the same action.
+     */
+    public void clearErrors() {
+        validationAware.clearErrors();
+    }
+
+    /**
+     * Clears all errors and messages. Useful for Continuations and other situations
+     * where you might want to clear parts of the state on the same action.
+     */
+    public void clearErrorsAndMessages() {
+        validationAware.clearErrorsAndMessages();
+    }
+
+    /**
+     * A default implementation that validates nothing.
+     * Subclasses should override this method to provide validations.
+     */
+    public void validate() {
+    }
+
+    @Override
+    public Object clone() throws CloneNotSupportedException {
+        return super.clone();
+    }
+
+    /**
+     * <!-- START SNIPPET: pause-method -->
+     * Stops the action invocation immediately (by throwing a PauseException) and causes the action invocation to return
+     * the specified result, such as {@link #SUCCESS}, {@link #INPUT}, etc.
+     * <p/>
+     * <p/>
+     * The next time this action is invoked (and using the same continuation ID), the method will resume immediately
+     * after where this method was called, with the entire call stack in the execute method restored.
+     * <p/>
+     * <p/>
+     * Note: this method can <b>only</b> be called within the {@link #execute()} method.
+     * <!-- END SNIPPET: pause-method -->
+     *
+     * @param result the result to return - the same type of return value in the {@link #execute()} method.
+     */
+    public void pause(String result) {
+    }
+
+    /**
+     * If called first time it will create {@link com.opensymphony.xwork2.TextProviderFactory},
+     * inject dependency (if {@link com.opensymphony.xwork2.inject.Container} is accesible) into in,
+     * then will create new {@link com.opensymphony.xwork2.TextProvider} and store it in a field
+     * for future references and at the returns reference to that field
+     *
+     * @return reference to field with TextProvider
+     */
+    private TextProvider getTextProvider() {
+        if (textProvider == null) {
+            TextProviderFactory tpf = new TextProviderFactory();
+            if (container != null) {
+                container.inject(tpf);
+            }
+            textProvider = tpf.createInstance(getClass(), this);
+        }
+        return textProvider;
+    }
+
+    @Inject
+    public void setContainer(Container container) {
+        this.container = container;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/CompositeTextProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/CompositeTextProvider.java b/core/src/main/java/com/opensymphony/xwork2/CompositeTextProvider.java
new file mode 100644
index 0000000..3927610
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/CompositeTextProvider.java
@@ -0,0 +1,265 @@
+package com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.util.ValueStack;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.*;
+
+
+/**
+ * This is a composite {@link TextProvider} that takes in an array or {@link java.util.List} of {@link TextProvider}s, it will
+ * consult each of them in order to get a composite result. To know how each method behaves, please refer to the
+ * javadoc for each methods.
+ *
+ * @author tmjee
+ * @version $Date$ $Id$
+ */
+public class CompositeTextProvider implements TextProvider {
+
+    private static final Logger LOG = LogManager.getLogger(CompositeTextProvider.class);
+
+    private List<TextProvider> textProviders = new ArrayList<>();
+
+    /**
+     * Instantiates a {@link CompositeTextProvider} with some predefined <code>textProviders</code>.
+     *
+     * @param textProviders
+     */
+    public CompositeTextProvider(List<TextProvider> textProviders) {
+        this.textProviders.addAll(textProviders);
+    }
+
+    /**
+     * Instantiates a {@link CompositeTextProvider} with some predefined <code>textProviders</code>.
+     *
+     * @param textProviders
+     */
+    public CompositeTextProvider(TextProvider[] textProviders) {
+        this(Arrays.asList(textProviders));
+    }
+
+    /**
+     * @param key The key to lookup in ressource bundles.
+     * @return <tt>true</tt>, if the requested key is found in one of the ressource bundles.
+     * @see {@link com.opensymphony.xwork2.TextProvider#hasKey(String)}
+     *      It will consult each individual {@link TextProvider}s and return true if either one of the
+     *      {@link TextProvider} has such a <code>key></code> else false.
+     */
+    public boolean hasKey(String key) {
+        // if there's a key in either text providers we are ok, else try the next text provider
+        for (TextProvider tp : textProviders) {
+            if (tp.hasKey(key)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * It will consult each {@link TextProvider}s and return the first valid message for this
+     * <code>key</code>
+     *
+     * @param key The key to lookup in resource bundles.
+     * @return The i18n text for the requested key.
+     * @see {@link com.opensymphony.xwork2.TextProvider#getText(String)}
+     */
+    public String getText(String key) {
+        return getText(key, key, Collections.emptyList());
+    }
+
+    /**
+     * It will consult each {@link TextProvider}s and return the first valid message for this
+     * <code>key</code> before returning <code>defaultValue</code> if every else fails.
+     *
+     * @param key
+     * @param defaultValue
+     * @return
+     * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String)}
+     */
+    public String getText(String key, String defaultValue) {
+        return getText(key, defaultValue, Collections.emptyList());
+    }
+
+    /**
+     * It will consult each {@link TextProvider}s and return the first valid message for this
+     * <code>key</code>, before returning <code>defaultValue</code>
+     * if every else fails.
+     *
+     * @param key
+     * @param defaultValue
+     * @param obj
+     * @return
+     * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, String)}
+     */
+    public String getText(String key, String defaultValue, final String obj) {
+        return getText(key, defaultValue, new ArrayList<Object>() {
+            {
+                add(obj);
+            }
+        });
+    }
+
+    /**
+     * It will consult each {@link TextProvider}s and return the first valid message for this
+     * <code>key</code>.
+     *
+     * @param key
+     * @param args
+     * @return
+     * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, java.util.List)}
+     */
+    public String getText(String key, List<?> args) {
+        return getText(key, key, args);
+    }
+
+    /**
+     * It will consult each {@link TextProvider}s and return the first valid message for this
+     * <code>key</code>.
+     *
+     * @param key
+     * @param args
+     * @return
+     * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String[])}
+     */
+    public String getText(String key, String[] args) {
+        return getText(key, key, args);
+    }
+
+
+    /**
+     * It will consult each {@link TextProvider}s and return the first valid message for this
+     * <code>key</code>, before returning <code>defaultValue</code>
+     *
+     * @param key
+     * @param defaultValue
+     * @param args
+     * @return
+     * @see {@link com.opensymphony.xwork2.TextProvider#getText#getText(String, String, java.util.List)}
+     */
+    public String getText(String key, String defaultValue, List<?> args) {
+        // if there's one text provider that gives us a msg not the same as defaultValue
+        // for this key, we are ok, else try the next
+        // text provider
+        for (TextProvider textProvider : textProviders) {
+            String msg = textProvider.getText(key, defaultValue, args);
+            if (msg != null && (!msg.equals(defaultValue))) {
+                return msg;
+            }
+        }
+        return defaultValue;
+    }
+
+
+    /**
+     * It will consult each {@link TextProvider}s and return the first valid message for this
+     * <code>key</code>, before returning <code>defaultValue</code>.
+     *
+     * @param key
+     * @param defaultValue
+     * @param args
+     * @return
+     * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, String[])}
+     */
+    public String getText(String key, String defaultValue, String[] args) {
+        // if there's one text provider that gives us a msg not the same as defaultValue
+        // for this key, we are ok, else try the next
+        // text provider
+        for (TextProvider textProvider : textProviders) {
+            String msg = textProvider.getText(key, defaultValue, args);
+            if (msg != null && (!msg.equals(defaultValue))) {
+                return msg;
+            }
+        }
+        return defaultValue;
+    }
+
+
+    /**
+     * It will consult each {@link TextProvider}s and return the first valid message for this
+     * <code>key</code>, before returning <code>defaultValue</code>
+     *
+     * @param key
+     * @param defaultValue
+     * @param args
+     * @param stack
+     * @return
+     * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, java.util.List, com.opensymphony.xwork2.util.ValueStack)}
+     */
+    public String getText(String key, String defaultValue, List<?> args, ValueStack stack) {
+        // if there's one text provider that gives us a msg not the same as defaultValue
+        // for this key, we are ok, else try the next
+        // text provider
+        for (TextProvider textProvider : textProviders) {
+            String msg = textProvider.getText(key, defaultValue, args, stack);
+            if (msg != null && (!msg.equals(defaultValue))) {
+                return msg;
+            }
+        }
+        return defaultValue;
+    }
+
+    /**
+     * It will consult each {@link TextProvider}s and return the first valid message for this
+     * <code>key</code>, before returning <code>defaultValue</code>
+     *
+     * @param key
+     * @param defaultValue
+     * @param args
+     * @param stack
+     * @return
+     * @see {@link com.opensymphony.xwork2.TextProvider#getText(String, String, String[], com.opensymphony.xwork2.util.ValueStack)}
+     */
+    public String getText(String key, String defaultValue, String[] args, ValueStack stack) {
+        // if there's one text provider that gives us a msg not the same as defaultValue
+        // for this key, we are ok, else try the next
+        // text provider
+        for (TextProvider textProvider : textProviders) {
+            String msg = textProvider.getText(key, defaultValue, args, stack);
+            if (msg != null && (!msg.equals(defaultValue))) {
+                return msg;
+            }
+        }
+        return defaultValue;
+    }
+
+
+    /**
+     * It will consult each {@link TextProvider}s and return the first non-null {@link ResourceBundle}.
+     *
+     * @param bundleName
+     * @return
+     * @see {@link TextProvider#getTexts(String)}
+     */
+    public ResourceBundle getTexts(String bundleName) {
+        // if there's one text provider that gives us a non-null resource bundle for this bundleName, we are ok, else try the next
+        // text provider
+        for (TextProvider textProvider : textProviders) {
+            ResourceBundle bundle = textProvider.getTexts(bundleName);
+            if (bundle != null) {
+                return bundle;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * It will consult each {@link com.opensymphony.xwork2.TextProvider}s and return the first non-null {@link ResourceBundle}.
+     *
+     * @return
+     * @see {@link TextProvider#getTexts()}
+     */
+    public ResourceBundle getTexts() {
+        // if there's one text provider that gives us a non-null resource bundle, we are ok, else try the next
+        // text provider
+        for (TextProvider textProvider : textProviders) {
+            ResourceBundle bundle = textProvider.getTexts();
+            if (bundle != null) {
+                return bundle;
+            }
+        }
+        return null;
+    }
+}
+
+

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java b/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java
new file mode 100644
index 0000000..82c0eec
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed 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 com.opensymphony.xwork2;
+
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.config.entities.InterceptorMapping;
+import com.opensymphony.xwork2.config.entities.ResultConfig;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.interceptor.PreResultListener;
+import com.opensymphony.xwork2.ognl.OgnlUtil;
+import com.opensymphony.xwork2.util.ValueStack;
+import com.opensymphony.xwork2.util.ValueStackFactory;
+import com.opensymphony.xwork2.util.profiling.UtilTimerStack;
+import ognl.MethodFailedException;
+import ognl.NoSuchPropertyException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * The Default ActionInvocation implementation
+ *
+ * @author Rainer Hermanns
+ * @author tmjee
+ * @version $Date$ $Id$
+ * @see com.opensymphony.xwork2.DefaultActionProxy
+ */
+public class DefaultActionInvocation implements ActionInvocation {
+
+    private static final Logger LOG = LogManager.getLogger(DefaultActionInvocation.class);
+
+    protected Object action;
+    protected ActionProxy proxy;
+    protected List<PreResultListener> preResultListeners;
+    protected Map<String, Object> extraContext;
+    protected ActionContext invocationContext;
+    protected Iterator<InterceptorMapping> interceptors;
+    protected ValueStack stack;
+    protected Result result;
+    protected Result explicitResult;
+    protected String resultCode;
+    protected boolean executed = false;
+    protected boolean pushAction = true;
+    protected ObjectFactory objectFactory;
+    protected ActionEventListener actionEventListener;
+    protected ValueStackFactory valueStackFactory;
+    protected Container container;
+    protected UnknownHandlerManager unknownHandlerManager;
+    protected OgnlUtil ognlUtil;
+
+    public DefaultActionInvocation(final Map<String, Object> extraContext, final boolean pushAction) {
+        this.extraContext = extraContext;
+        this.pushAction = pushAction;
+    }
+
+    @Inject
+    public void setUnknownHandlerManager(UnknownHandlerManager unknownHandlerManager) {
+        this.unknownHandlerManager = unknownHandlerManager;
+    }
+
+    @Inject
+    public void setValueStackFactory(ValueStackFactory fac) {
+        this.valueStackFactory = fac;
+    }
+
+    @Inject
+    public void setObjectFactory(ObjectFactory fac) {
+        this.objectFactory = fac;
+    }
+
+    @Inject
+    public void setContainer(Container cont) {
+        this.container = cont;
+    }
+
+    @Inject(required=false)
+    public void setActionEventListener(ActionEventListener listener) {
+        this.actionEventListener = listener;
+    }
+
+    @Inject
+    public void setOgnlUtil(OgnlUtil ognlUtil) {
+        this.ognlUtil = ognlUtil;
+    }
+
+    public Object getAction() {
+        return action;
+    }
+
+    public boolean isExecuted() {
+        return executed;
+    }
+
+    public ActionContext getInvocationContext() {
+        return invocationContext;
+    }
+
+    public ActionProxy getProxy() {
+        return proxy;
+    }
+
+    /**
+     * If the DefaultActionInvocation has been executed before and the Result is an instance of ActionChainResult, this method
+     * will walk down the chain of ActionChainResults until it finds a non-chain result, which will be returned. If the
+     * DefaultActionInvocation's result has not been executed before, the Result instance will be created and populated with
+     * the result params.
+     *
+     * @return a Result instance
+     * @throws Exception
+     */
+    public Result getResult() throws Exception {
+        Result returnResult = result;
+
+        // If we've chained to other Actions, we need to find the last result
+        while (returnResult instanceof ActionChainResult) {
+            ActionProxy aProxy = ((ActionChainResult) returnResult).getProxy();
+
+            if (aProxy != null) {
+                Result proxyResult = aProxy.getInvocation().getResult();
+
+                if ((proxyResult != null) && (aProxy.getExecuteResult())) {
+                    returnResult = proxyResult;
+                } else {
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+
+        return returnResult;
+    }
+
+    public String getResultCode() {
+        return resultCode;
+    }
+
+    public void setResultCode(String resultCode) {
+        if (isExecuted()) {
+            throw new IllegalStateException("Result has already been executed.");
+        }
+        this.resultCode = resultCode;
+    }
+
+
+    public ValueStack getStack() {
+        return stack;
+    }
+
+    /**
+     * Register a com.opensymphony.xwork2.interceptor.PreResultListener to be notified after the Action is executed and before the
+     * Result is executed. The ActionInvocation implementation must guarantee that listeners will be called in the order
+     * in which they are registered. Listener registration and execution does not need to be thread-safe.
+     *
+     * @param listener to register
+     */
+    public void addPreResultListener(PreResultListener listener) {
+        if (preResultListeners == null) {
+            preResultListeners = new ArrayList<>(1);
+        }
+
+        preResultListeners.add(listener);
+    }
+
+    public Result createResult() throws Exception {
+
+        if (explicitResult != null) {
+            Result ret = explicitResult;
+            explicitResult = null;
+
+            return ret;
+        }
+        ActionConfig config = proxy.getConfig();
+        Map<String, ResultConfig> results = config.getResults();
+
+        ResultConfig resultConfig = null;
+
+        try {
+            resultConfig = results.get(resultCode);
+        } catch (NullPointerException e) {
+            LOG.debug("Got NPE trying to read result configuration for resultCode [{}]", resultCode);
+        }
+        
+        if (resultConfig == null) {
+            // If no result is found for the given resultCode, try to get a wildcard '*' match.
+            resultConfig = results.get("*");
+        }
+
+        if (resultConfig != null) {
+            try {
+                return objectFactory.buildResult(resultConfig, invocationContext.getContextMap());
+            } catch (Exception e) {
+                LOG.error("There was an exception while instantiating the result of type {}", resultConfig.getClassName(), e);
+                throw new XWorkException(e, resultConfig);
+            }
+        } else if (resultCode != null && !Action.NONE.equals(resultCode) && unknownHandlerManager.hasUnknownHandlers()) {
+            return unknownHandlerManager.handleUnknownResult(invocationContext, proxy.getActionName(), proxy.getConfig(), resultCode);
+        }
+        return null;
+    }
+
+    /**
+     * @throws ConfigurationException If no result can be found with the returned code
+     */
+    public String invoke() throws Exception {
+        String profileKey = "invoke: ";
+        try {
+            UtilTimerStack.push(profileKey);
+
+            if (executed) {
+                throw new IllegalStateException("Action has already executed");
+            }
+
+            if (interceptors.hasNext()) {
+                final InterceptorMapping interceptor = interceptors.next();
+                String interceptorMsg = "interceptor: " + interceptor.getName();
+                UtilTimerStack.push(interceptorMsg);
+                try {
+                    resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
+                } finally {
+                    UtilTimerStack.pop(interceptorMsg);
+                }
+            } else {
+                resultCode = invokeActionOnly();
+            }
+
+            // this is needed because the result will be executed, then control will return to the Interceptor, which will
+            // return above and flow through again
+            if (!executed) {
+                if (preResultListeners != null) {
+                    for (Object preResultListener : preResultListeners) {
+                        PreResultListener listener = (PreResultListener) preResultListener;
+
+                        String _profileKey = "preResultListener: ";
+                        try {
+                            UtilTimerStack.push(_profileKey);
+                            listener.beforeResult(this, resultCode);
+                        }
+                        finally {
+                            UtilTimerStack.pop(_profileKey);
+                        }
+                    }
+                }
+
+                // now execute the result, if we're supposed to
+                if (proxy.getExecuteResult()) {
+                    executeResult();
+                }
+
+                executed = true;
+            }
+
+            return resultCode;
+        }
+        finally {
+            UtilTimerStack.pop(profileKey);
+        }
+    }
+
+    public String invokeActionOnly() throws Exception {
+        return invokeAction(getAction(), proxy.getConfig());
+    }
+
+    protected void createAction(Map<String, Object> contextMap) {
+        // load action
+        String timerKey = "actionCreate: " + proxy.getActionName();
+        try {
+            UtilTimerStack.push(timerKey);
+            action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
+        } catch (InstantiationException e) {
+            throw new XWorkException("Unable to instantiate Action!", e, proxy.getConfig());
+        } catch (IllegalAccessException e) {
+            throw new XWorkException("Illegal access to constructor, is it public?", e, proxy.getConfig());
+        } catch (Exception e) {
+            String gripe;
+
+            if (proxy == null) {
+                gripe = "Whoa!  No ActionProxy instance found in current ActionInvocation.  This is bad ... very bad";
+            } else if (proxy.getConfig() == null) {
+                gripe = "Sheesh.  Where'd that ActionProxy get to?  I can't find it in the current ActionInvocation!?";
+            } else if (proxy.getConfig().getClassName() == null) {
+                gripe = "No Action defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
+            } else {
+                gripe = "Unable to instantiate Action, " + proxy.getConfig().getClassName() + ",  defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
+            }
+
+            gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]");
+            throw new XWorkException(gripe, e, proxy.getConfig());
+        } finally {
+            UtilTimerStack.pop(timerKey);
+        }
+
+        if (actionEventListener != null) {
+            action = actionEventListener.prepare(action, stack);
+        }
+    }
+
+    protected Map<String, Object> createContextMap() {
+        Map<String, Object> contextMap;
+
+        if ((extraContext != null) && (extraContext.containsKey(ActionContext.VALUE_STACK))) {
+            // In case the ValueStack was passed in
+            stack = (ValueStack) extraContext.get(ActionContext.VALUE_STACK);
+
+            if (stack == null) {
+                throw new IllegalStateException("There was a null Stack set into the extra params.");
+            }
+
+            contextMap = stack.getContext();
+        } else {
+            // create the value stack
+            // this also adds the ValueStack to its context
+            stack = valueStackFactory.createValueStack();
+
+            // create the action context
+            contextMap = stack.getContext();
+        }
+
+        // put extraContext in
+        if (extraContext != null) {
+            contextMap.putAll(extraContext);
+        }
+
+        //put this DefaultActionInvocation into the context map
+        contextMap.put(ActionContext.ACTION_INVOCATION, this);
+        contextMap.put(ActionContext.CONTAINER, container);
+
+        return contextMap;
+    }
+
+    /**
+     * Uses getResult to get the final Result and executes it
+     *
+     * @throws ConfigurationException If not result can be found with the returned code
+     */
+    private void executeResult() throws Exception {
+        result = createResult();
+
+        String timerKey = "executeResult: " + getResultCode();
+        try {
+            UtilTimerStack.push(timerKey);
+            if (result != null) {
+                result.execute(this);
+            } else if (resultCode != null && !Action.NONE.equals(resultCode)) {
+                throw new ConfigurationException("No result defined for action " + getAction().getClass().getName()
+                        + " and result " + getResultCode(), proxy.getConfig());
+            } else {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("No result returned for action {} at {}", getAction().getClass().getName(), proxy.getConfig().getLocation());
+                }
+            }
+        } finally {
+            UtilTimerStack.pop(timerKey);
+        }
+    }
+
+    public void init(ActionProxy proxy) {
+        this.proxy = proxy;
+        Map<String, Object> contextMap = createContextMap();
+
+        // Setting this so that other classes, like object factories, can use the ActionProxy and other
+        // contextual information to operate
+        ActionContext actionContext = ActionContext.getContext();
+
+        if (actionContext != null) {
+            actionContext.setActionInvocation(this);
+        }
+
+        createAction(contextMap);
+
+        if (pushAction) {
+            stack.push(action);
+            contextMap.put("action", action);
+        }
+
+        invocationContext = new ActionContext(contextMap);
+        invocationContext.setName(proxy.getActionName());
+
+        // get a new List so we don't get problems with the iterator if someone changes the list
+        List<InterceptorMapping> interceptorList = new ArrayList<>(proxy.getConfig().getInterceptors());
+        interceptors = interceptorList.iterator();
+    }
+
+    protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
+        String methodName = proxy.getMethod();
+
+        LOG.debug("Executing action method = {}", methodName);
+
+        String timerKey = "invokeAction: " + proxy.getActionName();
+        try {
+            UtilTimerStack.push(timerKey);
+
+            Object methodResult;
+            try {
+                methodResult = ognlUtil.getValue(methodName + "()", getStack().getContext(), action);
+            } catch (MethodFailedException e) {
+                // if reason is missing method, try find version with "do" prefix
+                if (e.getReason() instanceof NoSuchMethodException) {
+                    try {
+                        String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1) + "()";
+                        methodResult = ognlUtil.getValue(altMethodName, getStack().getContext(), action);
+                    } catch (MethodFailedException e1) {
+                        // if still method doesn't exist, try checking UnknownHandlers
+                        if (e1.getReason() instanceof NoSuchMethodException) {
+                            if (unknownHandlerManager.hasUnknownHandlers()) {
+                                try {
+                                    methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
+                                } catch (NoSuchMethodException e2) {
+                                    // throw the original one
+                                    throw e;
+                                }
+                            } else {
+                                // throw the original one
+                                throw e;
+                            }
+                            // throw the original exception as UnknownHandlers weren't able to handle invocation as well
+                            if (methodResult == null) {
+                                throw e;
+                            }
+                        } else {
+                            // exception isn't related to missing action method, throw it
+                            throw e1;
+                        }
+                    }
+                } else {
+                    // exception isn't related to missing action method, throw it
+                    throw e;
+                }
+            }
+            return saveResult(actionConfig, methodResult);
+        } catch (NoSuchPropertyException e) {
+            throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
+        } catch (MethodFailedException e) {
+            // We try to return the source exception.
+            Throwable t = e.getCause();
+
+            if (actionEventListener != null) {
+                String result = actionEventListener.handleException(t, getStack());
+                if (result != null) {
+                    return result;
+                }
+            }
+            if (t instanceof Exception) {
+                throw (Exception) t;
+            } else {
+                throw e;
+            }
+        } finally {
+            UtilTimerStack.pop(timerKey);
+        }
+    }
+
+    /**
+     * Save the result to be used later.
+     * @param actionConfig current ActionConfig
+     * @param methodResult the result of the action.
+     * @return the result code to process.
+     */
+    protected String saveResult(ActionConfig actionConfig, Object methodResult) {
+        if (methodResult instanceof Result) {
+            this.explicitResult = (Result) methodResult;
+
+            // Wire the result automatically
+            container.inject(explicitResult);
+            return null;
+        } else {
+            return (String) methodResult;
+        }
+    }
+
+    /**
+     * Version ready to be serialize
+     *
+     * @return instance without reference to {@link Container}
+     */
+    public ActionInvocation serialize() {
+        DefaultActionInvocation that = this;
+        that.container = null;
+        return that;
+    }
+
+    /**
+     * Restoring Container
+     *
+     * @param actionContext current {@link ActionContext}
+     * @return instance which can be used to invoke action
+     */
+    public ActionInvocation deserialize(ActionContext actionContext) {
+        DefaultActionInvocation that = this;
+        that.container = actionContext.getContainer();
+        return that;
+    }
+
+}


Mime
View raw message