1 /*
2 * Copyright (c) 2002-2006 by OpenSymphony
3 * All rights reserved.
4 */
5 package com.opensymphony.xwork2;
6
7 import com.opensymphony.xwork2.config.Configuration;
8 import com.opensymphony.xwork2.config.ConfigurationException;
9 import com.opensymphony.xwork2.config.entities.ActionConfig;
10 import com.opensymphony.xwork2.config.entities.InterceptorMapping;
11 import com.opensymphony.xwork2.config.entities.ResultConfig;
12 import com.opensymphony.xwork2.inject.Container;
13 import com.opensymphony.xwork2.inject.Inject;
14 import com.opensymphony.xwork2.interceptor.PreResultListener;
15 import com.opensymphony.xwork2.util.ValueStack;
16 import com.opensymphony.xwork2.util.ValueStackFactory;
17 import com.opensymphony.xwork2.util.logging.Logger;
18 import com.opensymphony.xwork2.util.logging.LoggerFactory;
19 import com.opensymphony.xwork2.util.profiling.UtilTimerStack;
20
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.util.ArrayList;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27
28
29 /**
30 * The Default ActionInvocation implementation
31 *
32 * @author Rainer Hermanns
33 * @author tmjee
34 * @version $Date: 2009-07-22 19:18:39 +0200 (Mi, 22 Jul 2009) $ $Id: DefaultActionInvocation.java 2024 2009-07-22 17:18:39Z musachy $
35 * @see com.opensymphony.xwork2.DefaultActionProxy
36 */
37 public class DefaultActionInvocation implements ActionInvocation {
38
39 private static final long serialVersionUID = -585293628862447329L;
40
41 //static {
42 // if (ObjectFactory.getContinuationPackage() != null) {
43 // continuationHandler = new ContinuationHandler();
44 // }
45 //}
46 private static final Logger LOG = LoggerFactory.getLogger(DefaultActionInvocation.class);
47
48 protected Object action;
49 protected ActionProxy proxy;
50 protected List<PreResultListener> preResultListeners;
51 protected Map<String, Object> extraContext;
52 protected ActionContext invocationContext;
53 protected Iterator<InterceptorMapping> interceptors;
54 protected ValueStack stack;
55 protected Result result;
56 protected Result explicitResult;
57 protected String resultCode;
58 protected boolean executed = false;
59 protected boolean pushAction = true;
60 protected ObjectFactory objectFactory;
61 protected ActionEventListener actionEventListener;
62 protected ValueStackFactory valueStackFactory;
63 protected Container container;
64 private Configuration configuration;
65 protected UnknownHandlerManager unknownHandlerManager;
66
67 public DefaultActionInvocation(final Map<String, Object> extraContext, final boolean pushAction) {
68 DefaultActionInvocation.this.extraContext = extraContext;
69 DefaultActionInvocation.this.pushAction = pushAction;
70 }
71
72 @Inject
73 public void setUnknownHandlerManager(UnknownHandlerManager unknownHandlerManager) {
74 this.unknownHandlerManager = unknownHandlerManager;
75 }
76
77 @Inject
78 public void setValueStackFactory(ValueStackFactory fac) {
79 this.valueStackFactory = fac;
80 }
81
82 @Inject
83 public void setConfiguration(Configuration configuration) {
84 this.configuration = configuration;
85 }
86
87 @Inject
88 public void setObjectFactory(ObjectFactory fac) {
89 this.objectFactory = fac;
90 }
91
92 @Inject
93 public void setContainer(Container cont) {
94 this.container = cont;
95 }
96
97 @Inject(required=false)
98 public void setActionEventListener(ActionEventListener listener) {
99 this.actionEventListener = listener;
100 }
101
102 public Object getAction() {
103 return action;
104 }
105
106 public boolean isExecuted() {
107 return executed;
108 }
109
110 public ActionContext getInvocationContext() {
111 return invocationContext;
112 }
113
114 public ActionProxy getProxy() {
115 return proxy;
116 }
117
118 /**
119 * If the DefaultActionInvocation has been executed before and the Result is an instance of ActionChainResult, this method
120 * will walk down the chain of ActionChainResults until it finds a non-chain result, which will be returned. If the
121 * DefaultActionInvocation's result has not been executed before, the Result instance will be created and populated with
122 * the result params.
123 *
124 * @return a Result instance
125 * @throws Exception
126 */
127 public Result getResult() throws Exception {
128 Result returnResult = result;
129
130 // If we've chained to other Actions, we need to find the last result
131 while (returnResult instanceof ActionChainResult) {
132 ActionProxy aProxy = ((ActionChainResult) returnResult).getProxy();
133
134 if (aProxy != null) {
135 Result proxyResult = aProxy.getInvocation().getResult();
136
137 if ((proxyResult != null) && (aProxy.getExecuteResult())) {
138 returnResult = proxyResult;
139 } else {
140 break;
141 }
142 } else {
143 break;
144 }
145 }
146
147 return returnResult;
148 }
149
150 public String getResultCode() {
151 return resultCode;
152 }
153
154 public void setResultCode(String resultCode) {
155 if (isExecuted())
156 throw new IllegalStateException("Result has already been executed.");
157
158 this.resultCode = resultCode;
159 }
160
161
162 public ValueStack getStack() {
163 return stack;
164 }
165
166 /**
167 * Register a com.opensymphony.xwork2.interceptor.PreResultListener to be notified after the Action is executed and before the
168 * Result is executed. The ActionInvocation implementation must guarantee that listeners will be called in the order
169 * in which they are registered. Listener registration and execution does not need to be thread-safe.
170 *
171 * @param listener
172 */
173 public void addPreResultListener(PreResultListener listener) {
174 if (preResultListeners == null) {
175 preResultListeners = new ArrayList<PreResultListener>(1);
176 }
177
178 preResultListeners.add(listener);
179 }
180
181 public Result createResult() throws Exception {
182
183 if (explicitResult != null) {
184 Result ret = explicitResult;
185 explicitResult = null;
186
187 return ret;
188 }
189 ActionConfig config = proxy.getConfig();
190 Map<String, ResultConfig> results = config.getResults();
191
192 ResultConfig resultConfig = null;
193
194 try {
195 resultConfig = results.get(resultCode);
196 } catch (NullPointerException e) {
197 // swallow
198 }
199
200 if (resultConfig == null) {
201 // If no result is found for the given resultCode, try to get a wildcard '*' match.
202 resultConfig = results.get("*");
203 }
204
205 if (resultConfig != null) {
206 try {
207 return objectFactory.buildResult(resultConfig, invocationContext.getContextMap());
208 } catch (Exception e) {
209 LOG.error("There was an exception while instantiating the result of type " + resultConfig.getClassName(), e);
210 throw new XWorkException(e, resultConfig);
211 }
212 } else if (resultCode != null && !Action.NONE.equals(resultCode) && unknownHandlerManager.hasUnknownHandlers()) {
213 return unknownHandlerManager.handleUnknownResult(invocationContext, proxy.getActionName(), proxy.getConfig(), resultCode);
214 }
215 return null;
216 }
217
218 /**
219 * @throws ConfigurationException If no result can be found with the returned code
220 */
221 public String invoke() throws Exception {
222 String profileKey = "invoke: ";
223 try {
224 UtilTimerStack.push(profileKey);
225
226 if (executed) {
227 throw new IllegalStateException("Action has already executed");
228 }
229
230 if (interceptors.hasNext()) {
231 final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
232 String interceptorMsg = "interceptor: " + interceptor.getName();
233 UtilTimerStack.push(interceptorMsg);
234 try {
235 resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
236 }
237 finally {
238 UtilTimerStack.pop(interceptorMsg);
239 }
240 } else {
241 resultCode = invokeActionOnly();
242 }
243
244 // this is needed because the result will be executed, then control will return to the Interceptor, which will
245 // return above and flow through again
246 if (!executed) {
247 if (preResultListeners != null) {
248 for (Object preResultListener : preResultListeners) {
249 PreResultListener listener = (PreResultListener) preResultListener;
250
251 String _profileKey = "preResultListener: ";
252 try {
253 UtilTimerStack.push(_profileKey);
254 listener.beforeResult(this, resultCode);
255 }
256 finally {
257 UtilTimerStack.pop(_profileKey);
258 }
259 }
260 }
261
262 // now execute the result, if we're supposed to
263 if (proxy.getExecuteResult()) {
264 executeResult();
265 }
266
267 executed = true;
268 }
269
270 return resultCode;
271 }
272 finally {
273 UtilTimerStack.pop(profileKey);
274 }
275 }
276
277 public String invokeActionOnly() throws Exception {
278 return invokeAction(getAction(), proxy.getConfig());
279 }
280
281 protected void createAction(Map<String, Object> contextMap) {
282 // load action
283 String timerKey = "actionCreate: " + proxy.getActionName();
284 try {
285 UtilTimerStack.push(timerKey);
286 action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
287 } catch (InstantiationException e) {
288 throw new XWorkException("Unable to intantiate Action!", e, proxy.getConfig());
289 } catch (IllegalAccessException e) {
290 throw new XWorkException("Illegal access to constructor, is it public?", e, proxy.getConfig());
291 } catch (Exception e) {
292 String gripe = "";
293
294 if (proxy == null) {
295 gripe = "Whoa! No ActionProxy instance found in current ActionInvocation. This is bad ... very bad";
296 } else if (proxy.getConfig() == null) {
297 gripe = "Sheesh. Where'd that ActionProxy get to? I can't find it in the current ActionInvocation!?";
298 } else if (proxy.getConfig().getClassName() == null) {
299 gripe = "No Action defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
300 } else {
301 gripe = "Unable to instantiate Action, " + proxy.getConfig().getClassName() + ", defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
302 }
303
304 gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]");
305 throw new XWorkException(gripe, e, proxy.getConfig());
306 } finally {
307 UtilTimerStack.pop(timerKey);
308 }
309
310 if (actionEventListener != null) {
311 action = actionEventListener.prepare(action, stack);
312 }
313 }
314
315 protected Map<String, Object> createContextMap() {
316 Map<String, Object> contextMap;
317
318 if ((extraContext != null) && (extraContext.containsKey(ActionContext.VALUE_STACK))) {
319 // In case the ValueStack was passed in
320 stack = (ValueStack) extraContext.get(ActionContext.VALUE_STACK);
321
322 if (stack == null) {
323 throw new IllegalStateException("There was a null Stack set into the extra params.");
324 }
325
326 contextMap = stack.getContext();
327 } else {
328 // create the value stack
329 // this also adds the ValueStack to its context
330 stack = valueStackFactory.createValueStack();
331
332 // create the action context
333 contextMap = stack.getContext();
334 }
335
336 // put extraContext in
337 if (extraContext != null) {
338 contextMap.putAll(extraContext);
339 }
340
341 //put this DefaultActionInvocation into the context map
342 contextMap.put(ActionContext.ACTION_INVOCATION, this);
343 contextMap.put(ActionContext.CONTAINER, container);
344
345 return contextMap;
346 }
347
348 /**
349 * Uses getResult to get the final Result and executes it
350 *
351 * @throws ConfigurationException If not result can be found with the returned code
352 */
353 private void executeResult() throws Exception {
354 result = createResult();
355
356 String timerKey = "executeResult: " + getResultCode();
357 try {
358 UtilTimerStack.push(timerKey);
359 if (result != null) {
360 result.execute(this);
361 } else if (resultCode != null && !Action.NONE.equals(resultCode)) {
362 throw new ConfigurationException("No result defined for action " + getAction().getClass().getName()
363 + " and result " + getResultCode(), proxy.getConfig());
364 } else {
365 if (LOG.isDebugEnabled()) {
366 LOG.debug("No result returned for action " + getAction().getClass().getName() + " at " + proxy.getConfig().getLocation());
367 }
368 }
369 } finally {
370 UtilTimerStack.pop(timerKey);
371 }
372 }
373
374 public void init(ActionProxy proxy) {
375 this.proxy = proxy;
376 Map<String, Object> contextMap = createContextMap();
377
378 // Setting this so that other classes, like object factories, can use the ActionProxy and other
379 // contextual information to operate
380 ActionContext actionContext = ActionContext.getContext();
381
382 if (actionContext != null) {
383 actionContext.setActionInvocation(this);
384 }
385
386 createAction(contextMap);
387
388 if (pushAction) {
389 stack.push(action);
390 contextMap.put("action", action);
391 }
392
393 invocationContext = new ActionContext(contextMap);
394 invocationContext.setName(proxy.getActionName());
395
396 // get a new List so we don't get problems with the iterator if someone changes the list
397 List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
398 interceptors = interceptorList.iterator();
399 }
400
401 protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
402 String methodName = proxy.getMethod();
403
404 if (LOG.isDebugEnabled()) {
405 LOG.debug("Executing action method = " + actionConfig.getMethodName());
406 }
407
408 String timerKey = "invokeAction: " + proxy.getActionName();
409 try {
410 UtilTimerStack.push(timerKey);
411
412 boolean methodCalled = false;
413 Object methodResult = null;
414 Method method = null;
415 try {
416 method = getAction().getClass().getMethod(methodName, new Class[0]);
417 } catch (NoSuchMethodException e) {
418 // hmm -- OK, try doXxx instead
419 try {
420 String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
421 method = getAction().getClass().getMethod(altMethodName, new Class[0]);
422 } catch (NoSuchMethodException e1) {
423 // well, give the unknown handler a shot
424 if (unknownHandlerManager.hasUnknownHandlers()) {
425 try {
426 methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
427 methodCalled = true;
428 } catch (NoSuchMethodException e2) {
429 // throw the original one
430 throw e;
431 }
432 } else {
433 throw e;
434 }
435 }
436 }
437
438 if (!methodCalled) {
439 methodResult = method.invoke(action, new Object[0]);
440 }
441
442 if (methodResult instanceof Result) {
443 this.explicitResult = (Result) methodResult;
444
445 // Wire the result automatically
446 container.inject(explicitResult);
447 return null;
448 } else {
449 return (String) methodResult;
450 }
451 } catch (NoSuchMethodException e) {
452 throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
453 } catch (InvocationTargetException e) {
454 // We try to return the source exception.
455 Throwable t = e.getTargetException();
456
457 if (actionEventListener != null) {
458 String result = actionEventListener.handleException(t, getStack());
459 if (result != null) {
460 return result;
461 }
462 }
463 if (t instanceof Exception) {
464 throw (Exception) t;
465 } else {
466 throw e;
467 }
468 } finally {
469 UtilTimerStack.pop(timerKey);
470 }
471 }
472
473
474 }