001: /*
002: * Copyright (c) 2002-2006 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.xwork;
006:
007: import com.opensymphony.xwork.config.entities.ActionConfig;
008: import com.opensymphony.xwork.config.entities.ResultConfig;
009: import com.opensymphony.xwork.config.entities.InterceptorMapping;
010: import com.opensymphony.xwork.interceptor.PreResultListener;
011: import com.opensymphony.xwork.util.OgnlValueStack;
012: import com.opensymphony.xwork.util.XWorkContinuationConfig;
013: import com.uwyn.rife.continuations.ContinuableObject;
014: import com.uwyn.rife.continuations.ContinuationConfig;
015: import com.uwyn.rife.continuations.ContinuationContext;
016: import com.uwyn.rife.continuations.ContinuationManager;
017: import com.uwyn.rife.continuations.exceptions.PauseException;
018: import org.apache.commons.logging.Log;
019: import org.apache.commons.logging.LogFactory;
020:
021: import java.lang.reflect.InvocationTargetException;
022: import java.lang.reflect.Method;
023: import java.util.*;
024:
025: /**
026: * The Default ActionInvocation implementation
027: *
028: * @author Rainer Hermanns
029: * @version $Revision: 1387 $
030: * @see com.opensymphony.xwork.DefaultActionProxy
031: */
032: public class DefaultActionInvocation implements ActionInvocation {
033:
034: private static final long serialVersionUID = 3857082261177147501L;
035:
036: public static ContinuationManager m;
037:
038: static {
039: if (ContinuationConfig.getInstance() != null) {
040: m = new ContinuationManager();
041: }
042: }
043:
044: private static final Log LOG = LogFactory
045: .getLog(DefaultActionInvocation.class);
046:
047: protected Object action;
048: protected ActionProxy proxy;
049: protected List preResultListeners;
050: protected Map extraContext;
051: protected ActionContext invocationContext;
052: protected Iterator interceptors;
053: protected OgnlValueStack stack;
054: protected Result result;
055: protected String resultCode;
056: protected boolean executed = false;
057: protected boolean pushAction = true;
058:
059: protected DefaultActionInvocation(ActionProxy proxy)
060: throws Exception {
061: this (proxy, null);
062: }
063:
064: protected DefaultActionInvocation(ActionProxy proxy,
065: Map extraContext) throws Exception {
066: this (proxy, extraContext, true);
067: }
068:
069: protected DefaultActionInvocation(ActionProxy proxy,
070: Map extraContext, boolean pushAction) throws Exception {
071: this .proxy = proxy;
072: this .extraContext = extraContext;
073: this .pushAction = pushAction;
074: init();
075: }
076:
077: public Object getAction() {
078: return action;
079: }
080:
081: public boolean isExecuted() {
082: return executed;
083: }
084:
085: public ActionContext getInvocationContext() {
086: return invocationContext;
087: }
088:
089: public ActionProxy getProxy() {
090: return proxy;
091: }
092:
093: /**
094: * If the DefaultActionInvocation has been executed before and the Result is an instance of ActionChainResult, this method
095: * will walk down the chain of ActionChainResults until it finds a non-chain result, which will be returned. If the
096: * DefaultActionInvocation's result has not been executed before, the Result instance will be created and populated with
097: * the result params.
098: *
099: * @return a Result instance
100: * @throws Exception
101: */
102: public Result getResult() throws Exception {
103: Result returnResult = result;
104:
105: // If we've chained to other Actions, we need to find the last result
106: while (returnResult instanceof ActionChainResult) {
107: ActionProxy aProxy = ((ActionChainResult) returnResult)
108: .getProxy();
109:
110: if (aProxy != null) {
111: Result proxyResult = aProxy.getInvocation().getResult();
112:
113: if ((proxyResult != null)
114: && (aProxy.getExecuteResult())) {
115: returnResult = proxyResult;
116: } else {
117: break;
118: }
119: } else {
120: break;
121: }
122: }
123:
124: return returnResult;
125: }
126:
127: public String getResultCode() {
128: return resultCode;
129: }
130:
131: public void setResultCode(String resultCode) {
132: if (isExecuted())
133: throw new IllegalStateException(
134: "Result has already been executed.");
135:
136: this .resultCode = resultCode;
137: }
138:
139: public OgnlValueStack getStack() {
140: return stack;
141: }
142:
143: /**
144: * Register a com.opensymphony.xwork.interceptor.PreResultListener to be notified after the Action is executed and before the
145: * Result is executed. The ActionInvocation implementation must guarantee that listeners will be called in the order
146: * in which they are registered. Listener registration and execution does not need to be thread-safe.
147: *
148: * @param listener
149: */
150: public void addPreResultListener(PreResultListener listener) {
151: if (preResultListeners == null) {
152: preResultListeners = new ArrayList(1);
153: }
154:
155: preResultListeners.add(listener);
156: }
157:
158: public Result createResult() throws Exception {
159:
160: ActionConfig config = proxy.getConfig();
161: Map results = config.getResults();
162:
163: ResultConfig resultConfig = null;
164:
165: synchronized (config) {
166: try {
167: resultConfig = (ResultConfig) results.get(resultCode);
168: } catch (NullPointerException e) {
169: }
170: if (resultConfig == null) {
171: // If no result is found for the given resultCode, try to get a wildcard '*' match.
172: resultConfig = (ResultConfig) results.get("*");
173: }
174: }
175:
176: if (resultConfig != null) {
177: try {
178: return ObjectFactory.getObjectFactory()
179: .buildResult(resultConfig,
180: invocationContext.getContextMap());
181: } catch (Exception e) {
182: LOG.error(
183: "There was an exception while instantiating the result of type "
184: + resultConfig.getClassName(), e);
185: throw new XworkException(e, resultConfig);
186: }
187: } else {
188: return null;
189: }
190: }
191:
192: public String invoke() throws Exception {
193: if (executed) {
194: throw new IllegalStateException(
195: "Action has already executed");
196: }
197:
198: if (interceptors.hasNext()) {
199: InterceptorMapping interceptor = (InterceptorMapping) interceptors
200: .next();
201: resultCode = interceptor.getInterceptor().intercept(this );
202: } else {
203: resultCode = invokeActionOnly();
204: }
205:
206: // this is needed because the result will be executed, then control will return to the Interceptor, which will
207: // return above and flow through again
208: if (!executed) {
209: if (preResultListeners != null) {
210: for (Iterator iterator = preResultListeners.iterator(); iterator
211: .hasNext();) {
212: PreResultListener listener = (PreResultListener) iterator
213: .next();
214: listener.beforeResult(this , resultCode);
215: }
216: }
217:
218: // now execute the result, if we're supposed to
219: if (proxy.getExecuteResult()) {
220: executeResult();
221: }
222:
223: executed = true;
224: }
225:
226: return resultCode;
227: }
228:
229: public String invokeActionOnly() throws Exception {
230: return invokeAction(getAction(), proxy.getConfig());
231: }
232:
233: protected void createAction(Map contextMap) {
234: // load action
235: try {
236: action = ObjectFactory.getObjectFactory().buildAction(
237: proxy.getActionName(), proxy.getNamespace(),
238: proxy.getConfig(), contextMap);
239: } catch (InstantiationException e) {
240: throw new XworkException("Unable to intantiate Action!", e,
241: proxy.getConfig());
242: } catch (IllegalAccessException e) {
243: throw new XworkException(
244: "Illegal access to constructor, is it public?", e,
245: proxy.getConfig());
246: } catch (Exception e) {
247: String gripe = "";
248:
249: if (proxy == null) {
250: gripe = "Whoa! No ActionProxy instance found in current ActionInvocation. This is bad ... very bad";
251: } else if (proxy.getConfig() == null) {
252: gripe = "Sheesh. Where'd that ActionProxy get to? I can't find it in the current ActionInvocation!?";
253: } else if (proxy.getConfig().getClassName() == null) {
254: gripe = "No Action defined for '"
255: + proxy.getActionName() + "' in namespace '"
256: + proxy.getNamespace() + "'";
257: } else {
258: gripe = "Unable to instantiate Action, "
259: + proxy.getConfig().getClassName()
260: + ", defined for '" + proxy.getActionName()
261: + "' in namespace '" + proxy.getNamespace()
262: + "'";
263: }
264:
265: gripe += (((" -- " + e.getMessage()) != null) ? e
266: .getMessage() : " [no message in exception]");
267: throw new XworkException(gripe, e, proxy.getConfig());
268: }
269:
270: if (ObjectFactory.getContinuationPackage() != null)
271: prepareContinuation();
272: }
273:
274: private void prepareContinuation() {
275: if (action instanceof ContinuableObject) {
276: ContinuationContext ctx = ContinuationContext
277: .createInstance((ContinuableObject) action);
278: if (action instanceof NonCloningContinuableObject) {
279: ctx.setShouldClone(false);
280: }
281: }
282:
283: try {
284: String id = (String) stack.getContext().get(
285: XWorkContinuationConfig.CONTINUE_KEY);
286: stack.getContext().remove(
287: XWorkContinuationConfig.CONTINUE_KEY);
288: if (id != null) {
289: ContinuationContext context = m.getContext(id);
290: if (context != null) {
291: ContinuationContext.setContext(context);
292: // use the original action instead
293: Object original = context.getContinuable();
294: action = original;
295: }
296: }
297: } catch (CloneNotSupportedException e) {
298: e.printStackTrace();
299: }
300: }
301:
302: protected Map createContextMap() {
303: Map contextMap;
304:
305: if ((extraContext != null)
306: && (extraContext.containsKey(ActionContext.VALUE_STACK))) {
307: // In case the ValueStack was passed in
308: stack = (OgnlValueStack) extraContext
309: .get(ActionContext.VALUE_STACK);
310:
311: if (stack == null) {
312: throw new IllegalStateException(
313: "There was a null Stack set into the extra params.");
314: }
315:
316: contextMap = stack.getContext();
317: } else {
318: // create the value stack
319: // this also adds the ValueStack to its context
320: stack = new OgnlValueStack();
321:
322: // create the action context
323: contextMap = stack.getContext();
324: }
325:
326: // put extraContext in
327: if (extraContext != null) {
328: contextMap.putAll(extraContext);
329: }
330:
331: //put this DefaultActionInvocation into the context map
332: contextMap.put(ActionContext.ACTION_INVOCATION, this );
333:
334: return contextMap;
335: }
336:
337: /**
338: * Uses getResult to get the final Result and executes it
339: */
340: private void executeResult() throws Exception {
341: result = createResult();
342:
343: if (result != null) {
344: result.execute(this );
345: } else if (!Action.NONE.equals(resultCode)) {
346: LOG.warn("No result defined for action "
347: + getAction().getClass().getName() + " and result "
348: + getResultCode());
349: }
350: }
351:
352: private void init() throws Exception {
353: Map contextMap = createContextMap();
354:
355: createAction(contextMap);
356:
357: if (pushAction) {
358: stack.push(action);
359: }
360:
361: invocationContext = new ActionContext(contextMap);
362: invocationContext.setName(proxy.getActionName());
363:
364: // get a new List so we don't get problems with the iterator if someone changes the list
365: List interceptorList = new ArrayList(proxy.getConfig()
366: .getInterceptors());
367: interceptors = interceptorList.iterator();
368: }
369:
370: protected String invokeAction(Object action,
371: ActionConfig actionConfig) throws Exception {
372: String methodName = proxy.getMethod();
373:
374: if (LOG.isDebugEnabled()) {
375: LOG.debug("Executing action method = "
376: + actionConfig.getMethodName());
377: }
378:
379: try {
380: Method method;
381: try {
382: method = getAction().getClass().getMethod(methodName,
383: new Class[0]);
384: } catch (NoSuchMethodException e) {
385: // hmm -- OK, try doXxx instead
386: try {
387: String altMethodName = "do"
388: + methodName.substring(0, 1).toUpperCase()
389: + methodName.substring(1);
390: method = getAction().getClass().getMethod(
391: altMethodName, new Class[0]);
392: } catch (NoSuchMethodException e1) {
393: // throw the original one
394: throw e;
395: }
396: }
397:
398: return (String) method.invoke(action, new Object[0]);
399: } catch (NoSuchMethodException e) {
400: throw new IllegalArgumentException("Neither " + methodName
401: + "() nor do"
402: + methodName.substring(0, 1).toUpperCase()
403: + methodName.substring(1)
404: + "() is found in action " + getAction().getClass());
405: } catch (InvocationTargetException e) {
406: // We try to return the source exception.
407: Throwable t = e.getTargetException();
408:
409: if (t instanceof PauseException) {
410: // continuations in effect!
411: PauseException pe = ((PauseException) t);
412: ContinuationContext context = pe.getContext();
413: String result = (String) pe.getParameters();
414: getStack().getContext().put(
415: XWorkContinuationConfig.CONTINUE_KEY,
416: context.getId());
417: m.addContext(context);
418:
419: return result;
420: } else if (t instanceof Exception) {
421: throw (Exception) t;
422: } else {
423: throw e;
424: }
425: }
426: }
427: }
|