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