From b0a4c4f4177a36ff10cce28f64cfa0c7afe7fa51 Mon Sep 17 00:00:00 2001 From: Kusal Kithul-Godage Date: Sun, 3 Nov 2024 11:09:04 +1100 Subject: [PATCH] WW-5459 Deprecate and repackage ActionChainResult --- .../xwork2/ActionChainResult.java | 254 +--------------- .../xwork2/DefaultActionInvocation.java | 1 + .../interceptor/ChainingInterceptor.java | 4 +- .../org/apache/struts2/ActionInvocation.java | 2 +- .../interceptor/ChainingInterceptor.java | 2 +- .../struts2/result/ActionChainResult.java | 285 ++++++++++++++++++ ...onfigurationProviderOgnlAllowlistTest.java | 2 + 7 files changed, 298 insertions(+), 252 deletions(-) create mode 100644 core/src/main/java/org/apache/struts2/result/ActionChainResult.java diff --git a/core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java b/core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java index 4f7233046e..f987e531b8 100644 --- a/core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java +++ b/core/src/main/java/com/opensymphony/xwork2/ActionChainResult.java @@ -18,263 +18,21 @@ */ package com.opensymphony.xwork2; -import com.opensymphony.xwork2.inject.Inject; -import com.opensymphony.xwork2.util.TextParseUtil; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.struts2.StrutsException; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - /** -* -* -* This result invokes an entire other action, complete with it's own interceptor stack and result. -* -* -* -* This result type takes the following parameters: -* -* -* -* -* -* -* -* Example: -* -*

-* <package name="public" extends="struts-default">
-*     <!-- Chain creatAccount to login, using the default parameter -->
-*     <action name="createAccount" class="...">
-*         <result type="chain">login</result>
-*     </action>
-*
-*     <action name="login" class="...">
-*         <!-- Chain to another namespace -->
-*         <result type="chain">
-*             <param name="actionName">dashboard</param>
-*             <param name="namespace">/secure</param>
-*         </result>
-*     </action>
-* </package>
-*
-* <package name="secure" extends="struts-default" namespace="/secure">
-*     <action name="dashboard" class="...">
-*         <result>dashboard.jsp</result>
-*     </action>
-* </package>
-* 
-* -* @author Alexandru Popescu -*/ -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"; - - private ActionProxy proxy; - private String actionName; - - private String namespace; - - private String methodName; - - /** - * The list of actions to skip. - */ - private String skipActions; - - private ActionProxyFactory actionProxyFactory; + * @deprecated since 6.7.0, use {@link org.apache.struts2.result.ActionChainResult} instead. + */ +@Deprecated +public class ActionChainResult extends org.apache.struts2.result.ActionChainResult { public ActionChainResult() { super(); } public ActionChainResult(String namespace, String actionName, String methodName) { - this.namespace = namespace; - this.actionName = actionName; - this.methodName = methodName; + super(namespace, actionName, 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 namespace/action!method keys. - * - * @return the chain history as string list - */ - public static LinkedList getChainHistory() { - LinkedList chainHistory = (LinkedList) 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 (invocation == null) { - throw new IllegalArgumentException("Invocation cannot be null!"); - } - - String finalNamespace = namespace != null ? translateVariables(namespace) : invocation.getProxy() - .getNamespace(); - String finalActionName = translateVariables(actionName); - String finalMethodName = methodName != null ? translateVariables(methodName) : null; - - if (isInChainHistory(finalNamespace, finalActionName, finalMethodName)) { - addToHistory(finalNamespace, finalActionName, finalMethodName); - throw new StrutsException("Infinite recursion detected: " + ActionChainResult.getChainHistory()); - } - - if (ActionChainResult.getChainHistory().isEmpty() && invocation.getProxy() != null) { - addToHistory(finalNamespace, invocation.getProxy().getActionName(), invocation.getProxy().getMethod()); - } - addToHistory(finalNamespace, finalActionName, finalMethodName); - - Map extraContext = ActionContext.of() - .withValueStack(invocation.getInvocationContext().getValueStack()) - .withParameters(invocation.getInvocationContext().getParameters()) - .with(CHAIN_HISTORY, ActionChainResult.getChainHistory()) - .getContextMap(); - - LOG.debug("Chaining to action {}", finalActionName); - - proxy = actionProxyFactory.createActionProxy(finalNamespace, finalActionName, finalMethodName, extraContext); - proxy.execute(); - } - - protected String translateVariables(String text) { - return TextParseUtil.translateVariables(text, ActionContext.getContext().getValueStack()); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ActionChainResult that = (ActionChainResult) o; - return Objects.equals(actionName, that.actionName) && Objects.equals(methodName, - that.methodName) && Objects.equals(namespace, that.namespace); - } - - @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 chainHistory = ActionChainResult.getChainHistory(); - Set skipActionsList = new HashSet<>(); - if (skipActions != null && skipActions.length() > 0) { - String finalSkipActions = translateVariables(skipActions); - skipActionsList.addAll(TextParseUtil.commaDelimitedStringToSet(finalSkipActions)); - } - if (!skipActionsList.contains(actionName)) { - return chainHistory.contains(makeKey(namespace, actionName, methodName)); - } - return false; - } - - private void addToHistory(String namespace, String actionName, String methodName) { - List chainHistory = ActionChainResult.getChainHistory(); - chainHistory.add(makeKey(namespace, actionName, methodName)); - } - - private String makeKey(String namespace, String actionName, String methodName) { - return namespace + "/" + actionName + (methodName != null ? "!" + methodName : ""); + super(namespace, actionName, methodName, skipActions); } } diff --git a/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java b/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java index 84accbef79..db171d1915 100644 --- a/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java +++ b/core/src/main/java/com/opensymphony/xwork2/DefaultActionInvocation.java @@ -36,6 +36,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.StrutsException; +import org.apache.struts2.result.ActionChainResult; import java.util.ArrayList; import java.util.Iterator; diff --git a/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java b/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java index a21d18c56d..4bba31ce84 100644 --- a/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java +++ b/core/src/main/java/com/opensymphony/xwork2/interceptor/ChainingInterceptor.java @@ -18,7 +18,6 @@ */ package com.opensymphony.xwork2.interceptor; -import com.opensymphony.xwork2.ActionChainResult; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.Result; import com.opensymphony.xwork2.inject.Inject; @@ -31,6 +30,7 @@ import org.apache.logging.log4j.Logger; import org.apache.struts2.StrutsConstants; import org.apache.struts2.Unchainable; +import org.apache.struts2.result.ActionChainResult; import java.util.ArrayList; import java.util.Collection; @@ -117,7 +117,7 @@ * * @author mrdon * @author tm_jee ( tm_jee(at)yahoo.co.uk ) - * @see com.opensymphony.xwork2.ActionChainResult + * @see ActionChainResult * * @deprecated since 6.7.0, use {@link org.apache.struts2.interceptor.ChainingInterceptor} instead. */ diff --git a/core/src/main/java/org/apache/struts2/ActionInvocation.java b/core/src/main/java/org/apache/struts2/ActionInvocation.java index 97acc6cb14..2fd59e74b8 100644 --- a/core/src/main/java/org/apache/struts2/ActionInvocation.java +++ b/core/src/main/java/org/apache/struts2/ActionInvocation.java @@ -18,9 +18,9 @@ */ package org.apache.struts2; -import com.opensymphony.xwork2.ActionChainResult; import org.apache.struts2.action.Action; import org.apache.struts2.interceptor.PreResultListener; +import org.apache.struts2.result.ActionChainResult; import org.apache.struts2.result.Result; import org.apache.struts2.util.ValueStack; diff --git a/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java index aae3077c42..3870e822ce 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/ChainingInterceptor.java @@ -18,7 +18,6 @@ */ package org.apache.struts2.interceptor; -import com.opensymphony.xwork2.ActionChainResult; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.util.CompoundRoot; import com.opensymphony.xwork2.util.ProxyUtil; @@ -29,6 +28,7 @@ import org.apache.struts2.ActionInvocation; import org.apache.struts2.StrutsConstants; import org.apache.struts2.Unchainable; +import org.apache.struts2.result.ActionChainResult; import org.apache.struts2.result.Result; import org.apache.struts2.util.ValueStack; diff --git a/core/src/main/java/org/apache/struts2/result/ActionChainResult.java b/core/src/main/java/org/apache/struts2/result/ActionChainResult.java new file mode 100644 index 0000000000..ff53778a37 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/result/ActionChainResult.java @@ -0,0 +1,285 @@ +/* + * 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.result; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.ActionProxyFactory; +import com.opensymphony.xwork2.Result; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.TextParseUtil; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.StrutsException; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** +* +* +* This result invokes an entire other action, complete with it's own interceptor stack and result. +* +* +* +* This result type takes the following parameters: +* +* +* +*
    +* +*
  • actionName (default) - the name of the action that will be chained to
  • +* +*
  • namespace - used to determine which namespace the Action is in that we're chaining. If namespace is null, +* this defaults to the current namespace
  • +* +*
  • method - used to specify another method on target action to be invoked. +* If null, this defaults to execute method
  • +* +*
  • skipActions - (optional) the list of comma separated action names for the +* actions that could be chained to
  • +* +*
+* +* +* +* Example: +* +*

+* <package name="public" extends="struts-default">
+*     <!-- Chain creatAccount to login, using the default parameter -->
+*     <action name="createAccount" class="...">
+*         <result type="chain">login</result>
+*     </action>
+*
+*     <action name="login" class="...">
+*         <!-- Chain to another namespace -->
+*         <result type="chain">
+*             <param name="actionName">dashboard</param>
+*             <param name="namespace">/secure</param>
+*         </result>
+*     </action>
+* </package>
+*
+* <package name="secure" extends="struts-default" namespace="/secure">
+*     <action name="dashboard" class="...">
+*         <result>dashboard.jsp</result>
+*     </action>
+* </package>
+* 
+* +* @author Alexandru Popescu +*/ +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"; + + 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 namespace/action!method keys. + * + * @return the chain history as string list + */ + public static LinkedList getChainHistory() { + LinkedList chainHistory = (LinkedList) 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 + */ + @Override + public void execute(ActionInvocation invocation) throws Exception { + if (invocation == null) { + throw new IllegalArgumentException("Invocation cannot be null!"); + } + + String finalNamespace = namespace != null ? translateVariables(namespace) : invocation.getProxy() + .getNamespace(); + String finalActionName = translateVariables(actionName); + String finalMethodName = methodName != null ? translateVariables(methodName) : null; + + if (isInChainHistory(finalNamespace, finalActionName, finalMethodName)) { + addToHistory(finalNamespace, finalActionName, finalMethodName); + throw new StrutsException("Infinite recursion detected: " + ActionChainResult.getChainHistory()); + } + + if (ActionChainResult.getChainHistory().isEmpty() && invocation.getProxy() != null) { + addToHistory(finalNamespace, invocation.getProxy().getActionName(), invocation.getProxy().getMethod()); + } + addToHistory(finalNamespace, finalActionName, finalMethodName); + + Map extraContext = ActionContext.of() + .withValueStack(invocation.getInvocationContext().getValueStack()) + .withParameters(invocation.getInvocationContext().getParameters()) + .with(CHAIN_HISTORY, ActionChainResult.getChainHistory()) + .getContextMap(); + + LOG.debug("Chaining to action {}", finalActionName); + + proxy = actionProxyFactory.createActionProxy(finalNamespace, finalActionName, finalMethodName, extraContext); + proxy.execute(); + } + + protected String translateVariables(String text) { + return TextParseUtil.translateVariables(text, ActionContext.getContext().getValueStack()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ActionChainResult that = (ActionChainResult) o; + return Objects.equals(actionName, that.actionName) && Objects.equals(methodName, + that.methodName) && Objects.equals(namespace, that.namespace); + } + + @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 chainHistory = ActionChainResult.getChainHistory(); + Set skipActionsList = new HashSet<>(); + if (skipActions != null && skipActions.length() > 0) { + String finalSkipActions = translateVariables(skipActions); + skipActionsList.addAll(TextParseUtil.commaDelimitedStringToSet(finalSkipActions)); + } + if (!skipActionsList.contains(actionName)) { + return chainHistory.contains(makeKey(namespace, actionName, methodName)); + } + return false; + } + + private void addToHistory(String namespace, String actionName, String methodName) { + List chainHistory = ActionChainResult.getChainHistory(); + chainHistory.add(makeKey(namespace, actionName, methodName)); + } + + private String makeKey(String namespace, String actionName, String methodName) { + return namespace + "/" + actionName + (methodName != null ? "!" + methodName : ""); + } +} diff --git a/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java b/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java index 1c86c917ba..b89b6d58aa 100644 --- a/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java +++ b/core/src/test/java/com/opensymphony/xwork2/config/providers/ConfigurationProviderOgnlAllowlistTest.java @@ -52,6 +52,7 @@ public void allowlist() throws Exception { Class.forName("com.opensymphony.xwork2.interceptor.ConditionalInterceptor"), Class.forName("org.apache.struts2.ActionSupport"), Class.forName("com.opensymphony.xwork2.ActionSupport"), + Class.forName("org.apache.struts2.result.ActionChainResult"), Class.forName("com.opensymphony.xwork2.ActionChainResult"), Class.forName("com.opensymphony.xwork2.TextProvider"), Class.forName("org.apache.struts2.interceptor.NoOpInterceptor"), @@ -117,6 +118,7 @@ public void allowlist_2only() throws Exception { Class.forName("com.opensymphony.xwork2.interceptor.ConditionalInterceptor"), Class.forName("org.apache.struts2.ActionSupport"), Class.forName("com.opensymphony.xwork2.ActionSupport"), + Class.forName("org.apache.struts2.result.ActionChainResult"), Class.forName("com.opensymphony.xwork2.ActionChainResult"), Class.forName("com.opensymphony.xwork2.TextProvider"), Class.forName("org.apache.struts2.interceptor.NoOpInterceptor"),