1 /*
2 * Copyright (c) 2002-2006 by OpenSymphony
3 * All rights reserved.
4 */
5 package com.opensymphony.xwork2;
6
7 import com.opensymphony.xwork2.inject.Inject;
8 import com.opensymphony.xwork2.util.TextParseUtil;
9 import com.opensymphony.xwork2.util.ValueStack;
10 import com.opensymphony.xwork2.util.logging.Logger;
11 import com.opensymphony.xwork2.util.logging.LoggerFactory;
12
13 import java.util;
14
15
16 /**
17 * <!-- START SNIPPET: description -->
18 *
19 * This result invokes an entire other action, complete with it's own interceptor stack and result.
20 *
21 * <!-- END SNIPPET: description -->
22 *
23 * <b>This result type takes the following parameters:</b>
24 *
25 * <!-- START SNIPPET: params -->
26 *
27 * <ul>
28 *
29 * <li><b>actionName (default)</b> - the name of the action that will be chained to</li>
30 *
31 * <li><b>namespace</b> - used to determine which namespace the Action is in that we're chaining. If namespace is null,
32 * this defaults to the current namespace</li>
33 *
34 * <li><b>method</b> - used to specify another method on target action to be invoked.
35 * If null, this defaults to execute method</li>
36 *
37 * <li><b>skipActions</b> - (optional) the list of comma separated action names for the
38 * actions that could be chained to</li>
39 *
40 * </ul>
41 *
42 * <!-- END SNIPPET: params -->
43 *
44 * <b>Example:</b>
45 *
46 * <pre><!-- START SNIPPET: example -->
47 * <package name="public" extends="struts-default">
48 * <!-- Chain creatAccount to login, using the default parameter -->
49 * <action name="createAccount" class="...">
50 * <result type="chain">login</result>
51 * </action>
52 *
53 * <action name="login" class="...">
54 * <!-- Chain to another namespace -->
55 * <result type="chain">
56 * <param name="actionName">dashboard</param>
57 * <param name="namespace">/secure</param>
58 * </result>
59 * </action>
60 * </package>
61 *
62 * <package name="secure" extends="struts-default" namespace="/secure">
63 * <action name="dashboard" class="...">
64 * <result>dashboard.jsp</result>
65 * </action>
66 * </package>
67 * <!-- END SNIPPET: example --></pre>
68 *
69 * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
70 */
71 public class ActionChainResult implements Result {
72
73 private static final Logger LOG = LoggerFactory.getLogger(ActionChainResult.class);
74
75 /**
76 * The result parameter name to set the name of the action to chain to.
77 */
78 public static final String DEFAULT_PARAM = "actionName";
79
80 /**
81 * The action context key to save the chain history.
82 */
83 private static final String CHAIN_HISTORY = "CHAIN_HISTORY";
84
85 /**
86 * The result parameter name to set the name of the action to chain to.
87 */
88 public static final String SKIP_ACTIONS_PARAM = "skipActions";
89
90
91 private ActionProxy proxy;
92 private String actionName;
93
94 private String namespace;
95
96 private String methodName;
97
98 /**
99 * The list of actions to skip.
100 */
101 private String skipActions;
102
103 private ActionProxyFactory actionProxyFactory;
104
105 public ActionChainResult() {
106 super();
107 }
108
109 public ActionChainResult(String namespace, String actionName, String methodName) {
110 this.namespace = namespace;
111 this.actionName = actionName;
112 this.methodName = methodName;
113 }
114
115 public ActionChainResult(String namespace, String actionName, String methodName, String skipActions) {
116 this.namespace = namespace;
117 this.actionName = actionName;
118 this.methodName = methodName;
119 this.skipActions = skipActions;
120 }
121
122
123 /**
124 * @param actionProxyFactory the actionProxyFactory to set
125 */
126 @Inject
127 public void setActionProxyFactory(ActionProxyFactory actionProxyFactory) {
128 this.actionProxyFactory = actionProxyFactory;
129 }
130
131 /**
132 * Set the action name.
133 *
134 * @param actionName The action name.
135 */
136 public void setActionName(String actionName) {
137 this.actionName = actionName;
138 }
139
140 /**
141 * sets the namespace of the Action that we're chaining to. if namespace
142 * is null, this defaults to the current namespace.
143 *
144 * @param namespace the name of the namespace we're chaining to
145 */
146 public void setNamespace(String namespace) {
147 this.namespace = namespace;
148 }
149
150 /**
151 * Set the list of actions to skip.
152 * To test if an action should not throe an infinite recursion,
153 * only the action name is used, not the namespace.
154 *
155 * @param actions The list of action name separated by a white space.
156 */
157 public void setSkipActions(String actions) {
158 this.skipActions = actions;
159 }
160
161
162 public void setMethod(String method) {
163 this.methodName = method;
164 }
165
166 public ActionProxy getProxy() {
167 return proxy;
168 }
169
170 /**
171 * Get the XWork chain history.
172 * The stack is a list of <code>namespace/action!method</code> keys.
173 */
174 public static LinkedList<String> getChainHistory() {
175 LinkedList<String> chainHistory = (LinkedList<String>) ActionContext.getContext().get(CHAIN_HISTORY);
176 // Add if not exists
177 if (chainHistory == null) {
178 chainHistory = new LinkedList<String>();
179 ActionContext.getContext().put(CHAIN_HISTORY, chainHistory);
180 }
181
182 return chainHistory;
183 }
184
185 /**
186 * @param invocation the DefaultActionInvocation calling the action call stack
187 */
188 public void execute(ActionInvocation invocation) throws Exception {
189 // if the finalNamespace wasn't explicitly defined, assume the current one
190 if (this.namespace == null) {
191 this.namespace = invocation.getProxy().getNamespace();
192 }
193
194 ValueStack stack = ActionContext.getContext().getValueStack();
195 String finalNamespace = TextParseUtil.translateVariables(namespace, stack);
196 String finalActionName = TextParseUtil.translateVariables(actionName, stack);
197 String finalMethodName = this.methodName != null
198 ? TextParseUtil.translateVariables(this.methodName, stack)
199 : null;
200
201 if (isInChainHistory(finalNamespace, finalActionName, finalMethodName)) {
202 addToHistory(finalNamespace, finalActionName, finalMethodName);
203 throw new XWorkException("Infinite recursion detected: "
204 + ActionChainResult.getChainHistory().toString());
205 }
206
207 if (ActionChainResult.getChainHistory().isEmpty() && invocation != null && invocation.getProxy() != null) {
208 addToHistory(finalNamespace, invocation.getProxy().getActionName(), invocation.getProxy().getMethod());
209 }
210 addToHistory(finalNamespace, finalActionName, finalMethodName);
211
212 HashMap<String, Object> extraContext = new HashMap<String, Object>();
213 extraContext.put(ActionContext.VALUE_STACK, ActionContext.getContext().getValueStack());
214 extraContext.put(ActionContext.PARAMETERS, ActionContext.getContext().getParameters());
215 extraContext.put(CHAIN_HISTORY, ActionChainResult.getChainHistory());
216
217 if (LOG.isDebugEnabled()) {
218 LOG.debug("Chaining to action " + finalActionName);
219 }
220
221 proxy = actionProxyFactory.createActionProxy(finalNamespace, finalActionName, finalMethodName, extraContext);
222 proxy.execute();
223 }
224
225 @Override public boolean equals(Object o) {
226 if (this == o) return true;
227 if (o == null || getClass() != o.getClass()) return false;
228
229 final ActionChainResult that = (ActionChainResult) o;
230
231 if (actionName != null ? !actionName.equals(that.actionName) : that.actionName != null) return false;
232 if (methodName != null ? !methodName.equals(that.methodName) : that.methodName != null) return false;
233 if (namespace != null ? !namespace.equals(that.namespace) : that.namespace != null) return false;
234
235 return true;
236 }
237
238 @Override public int hashCode() {
239 int result;
240 result = (actionName != null ? actionName.hashCode() : 0);
241 result = 31 * result + (namespace != null ? namespace.hashCode() : 0);
242 result = 31 * result + (methodName != null ? methodName.hashCode() : 0);
243 return result;
244 }
245
246 private boolean isInChainHistory(String namespace, String actionName, String methodName) {
247 LinkedList<? extends String> chainHistory = ActionChainResult.getChainHistory();
248
249 if (chainHistory == null) {
250 return false;
251 } else {
252 // Actions to skip
253 Set<String> skipActionsList = new HashSet<String>();
254 if (skipActions != null && skipActions.length() > 0) {
255 ValueStack stack = ActionContext.getContext().getValueStack();
256 String finalSkipActions = TextParseUtil.translateVariables(this.skipActions, stack);
257 skipActionsList.addAll(TextParseUtil.commaDelimitedStringToSet(finalSkipActions));
258 }
259 if (!skipActionsList.contains(actionName)) {
260 // Get if key is in the chain history
261 return chainHistory.contains(makeKey(namespace, actionName, methodName));
262 }
263
264 return false;
265 }
266 }
267
268 private void addToHistory(String namespace, String actionName, String methodName) {
269 List<String> chainHistory = ActionChainResult.getChainHistory();
270 chainHistory.add(makeKey(namespace, actionName, methodName));
271 }
272
273 private String makeKey(String namespace, String actionName, String methodName) {
274 if (null == methodName) {
275 return namespace + "/" + actionName;
276 }
277
278 return namespace + "/" + actionName + "!" + methodName;
279 }
280 }