KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > inversoft > verge > mvc > controller > actionflow > ActionFlowExecutor


1 /*
2  * Copyright (c) 2003, Inversoft
3  *
4  * This software is distribuable under the GNU Lesser General Public License.
5  * For more information visit gnu.org.
6  */

7 package com.inversoft.verge.mvc.controller.actionflow;
8
9
10 import java.util.Map JavaDoc;
11
12 import javax.servlet.http.HttpServletRequest JavaDoc;
13 import javax.servlet.http.HttpServletResponse JavaDoc;
14
15 import org.apache.log4j.Logger;
16
17 import com.inversoft.util.StringTools;
18 import com.inversoft.verge.mvc.MVCException;
19 import com.inversoft.verge.mvc.controller.DefaultLongTxnHandler;
20 import com.inversoft.verge.mvc.controller.GenericResult;
21 import com.inversoft.verge.mvc.controller.LongTxnHandler;
22 import com.inversoft.verge.mvc.controller.LongTxnSetup;
23 import com.inversoft.verge.mvc.controller.Result;
24 import com.inversoft.verge.mvc.controller.actionflow.config.ActionFlowConfigRegistry;
25 import com.inversoft.verge.mvc.controller.actionflow.config.Link;
26 import com.inversoft.verge.mvc.controller.actionflow.config.Namespace;
27 import com.inversoft.verge.mvc.controller.actionflow.config.Node;
28
29
30 /**
31  * This class is the main execution engine for the ActionFlow
32  * controller. This handles full execution of an ActionFlow
33  * based on the configuration files for that ActionFlow.
34  *
35  * @author Brian Pontarelli
36  * @since 2.0
37  * @version 2.0
38  */

39 public class ActionFlowExecutor {
40
41     /**
42      * This classes logger
43      */

44     private static final Logger logger = Logger.getLogger(ActionFlowExecutor.class);
45
46     /**
47      * Long transaction information for session
48      */

49     private static final String JavaDoc LONG_TXN_KEY =
50         "com.inversoft.verge.mvc.controller.actionflow.LongTxn";
51     private static final Object JavaDoc LONG_TXN_VALUE = new Object JavaDoc();
52
53     /**
54      * The long transaction handler
55      */

56     static volatile LongTxnHandler handler = new DefaultLongTxnHandler();
57
58
59     /**
60      * This is a static class
61      */

62     private ActionFlowExecutor() {
63         // Empty
64
}
65
66
67     /**
68      * <p>
69      * Registers a new long transaction handler with this controller handler. This
70      * long transaction handler is responsible for handling any actions that are
71      * marked as long transactions in the configuration files. Only one long
72      * transaction handler can be registered at a time. Each susquent call to this
73      * method, overrides the previous handler.
74      * </p>
75      *
76      * <p>
77      * By default the {@link DefaultLongTxnHandler DefaultLongTxnHandler} class
78      * is the registered handler.
79      * </p>
80      *
81      * @param handler The new handler
82      */

83     public static void registerHandler(LongTxnHandler handler) {
84         ActionFlowExecutor.handler = handler;
85     }
86
87     /**
88      * Unregisters the single long transaction handler if one was ever registered.
89      */

90     public static void unregisterHandler() {
91         ActionFlowExecutor.handler = null;
92     }
93
94     /**
95      * This executes the ActionFlow described within the given namespace, from
96      * the given entry point with the given action. The Map is used to store
97      * any additional name/value pairs that individual NodeExecuters might
98      * need.
99      *
100      * @param request The HttpServletRequest that is stored in the {@link
101      * ActionFlowAction ActionFlowAction} that is passed through the
102      * ActionFlow as it executes
103      * @param response The HttpServletResponse that is set into the {@link
104      * ActionFlowAction ActionFlowAction} object that is passed through
105      * as the ActionFlow executes
106      * @param namespace The namespace to execute within. The namespace of an
107      * ActionFlow is setup inside the configuration file.
108      * @param entry The name of the entry point from which the execution
109      * should start.
110      * @param action The action from the entry point that has been taken.
111      * @param extraParams A Map containing any additional information needed to
112      * execute the ActionFlow
113      * @throws ActionFlowException If the execution failed for any reason. This
114      * is not the same as a Node throwing an exception. However, if a
115      * Node does throw an exception and the executer can not locate
116      * a handler for that exception, this will throw an
117      * ActionFlowException.
118      * @asserts If request, or response are null or if namespace, entry or action
119      * are null or empty
120      */

121     public static Node execute(HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response,
122             String JavaDoc namespace, String JavaDoc entry, String JavaDoc action, Map JavaDoc extraParams)
123     throws ActionFlowException {
124         ActionFlowAction afAction = new ActionFlowAction(request, response);
125         afAction.setAction(action);
126         afAction.setExtraParams(extraParams);
127
128         return execute(request, response, namespace, entry, afAction);
129     }
130
131     /**
132      * The correct MVC version that takes a pre-made Action instance and no extra
133      * parameters.
134      */

135     protected static Node execute(HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response,
136             String JavaDoc namespace, String JavaDoc entry, ActionFlowAction action)
137     throws ActionFlowException {
138         boolean debug = logger.isDebugEnabled();
139         assert (request != null) : "request == null";
140         assert (response != null) : "response == null";
141         assert (action != null) : "action == null";
142         assert (!StringTools.isEmpty(namespace)) : "namespace is null or empty";
143
144         ActionFlowState state = new ActionFlowState(request);
145
146         Namespace namespaceObj =
147             ActionFlowConfigRegistry.getInstance(request).lookup(namespace);
148         if (namespaceObj == null) {
149             handleException(new ActionFlowException(namespace +
150                 " is not a valid namespace"), action);
151             return null; // The exception was handled, bail!!!
152
} else if (logger.isDebugEnabled()) {
153             logger.debug("Resolved namespace: " + namespace);
154         }
155
156         // If no entry specified, get the one from the state
157
Node entryNode = null;
158         boolean entryEmpty = StringTools.isEmpty(entry);
159         if (entryEmpty) {
160             entryNode = state.getCurrentNodeForNamespace(namespaceObj);
161             if (debug && entryNode != null) {
162                 logger.debug("No node for entry specified, using current node of: " +
163                     entryNode.getName());
164             }
165         }
166
167         // If still no entry, get the entry for the name of if the name is empty,
168
// the default
169
if (entryNode == null) {
170             entryNode = namespaceObj.findEntry(entry);
171             if (entryNode == null) {
172                 handleException(new ActionFlowException("Could not find node" +
173                     " named: " + entry + " for entry and namespace: " + namespace +
174                     " does not have a current node or default node"),
175                     action);
176                 return null; // The exception was handled, bail!!!
177
} else {
178                 if (debug && entryEmpty) {
179                     logger.debug("No node for entry, using default node of: " +
180                         entryNode.getName());
181                 } else if (debug) {
182                     logger.debug("Found node for entry or using default. Node" +
183                         " resolved: " + entryNode.getName());
184                 }
185             }
186         }
187
188         state.setCurrentNodeForNamespace(namespaceObj, entryNode);
189         action.setNode(entryNode);
190         Node result = null;
191
192         try {
193             result = executeActionFlow(request, response, action, state);
194         } finally {
195             // Last ditch effort to stop any outstanding transactions
196
resetLongTransaction(request);
197         }
198
199         // Store the action in the last state it was in for later use
200
action.detachHttpServletRequest();
201         request.setAttribute(ActionFlowConstants.ACTION_REQUEST_KEY, action);
202
203         return result;
204     }
205
206     /**
207      * Does the loop based execution of the action flow
208      */

209     protected static Node executeActionFlow(HttpServletRequest JavaDoc request,
210             HttpServletResponse JavaDoc response, ActionFlowAction action,
211             ActionFlowState state)
212     throws ActionFlowException {
213
214         assert (action != null) : "action == null";
215
216         boolean finished = false;
217         while (!finished) {
218             if (logger.isDebugEnabled()) {
219                 logger.debug("Looking up link for action: " +
220                     action.getAction().toString());
221             }
222
223             Node entry = action.getNode();
224             Link link = entry.findLink(action.getAction());
225             Node dest = null;
226             if (link == null) {
227                 dest = findReEntryNode(action, entry.getNamespace());
228                 if (dest == null) {
229                     finished = true;
230                     continue;
231                 }
232             } else {
233                 if (logger.isDebugEnabled()) {
234                     logger.debug("Found link named: " + link.getValue());
235                 }
236
237                 dest = link.getDestination();
238             }
239
240             // Handle the end long transcation support if the next node is off
241
// by writing out the redirect and ending the processing by setting
242
// finished and exiting the loop
243
if (entry.isLongTxnEnabled() && !dest.isLongTxnEnabled() &&
244                     isLongTransaction(request)) {
245                 endLongTransaction(request, response, entry, action, finished);
246                 finished = true;
247                 continue;
248             }
249
250             // Set the new node here since we know that the long transaction is
251
// not ending
252
action.setNode(dest);
253
254             // Handle the start of the long transaction support
255
if (!entry.isLongTxnEnabled() && dest.isLongTxnEnabled()) {
256                 startLongTransaction(request, response, dest, action);
257             }
258
259             NodeExecutor executor = dest.getExecutor();
260             Object JavaDoc result = null;
261             Namespace namespace = dest.getNamespace();
262             try {
263                 if (logger.isDebugEnabled()) {
264                     logger.debug("Executing destination node: " + dest.getName());
265                 }
266
267                 result = executor.execute(namespace, dest, action,
268                     action.getExtraParams());
269                 state.setCurrentNodeForNamespace(namespace, dest);
270
271                 if (dest.isExitPoint()) {
272                     logger.debug("Destination is an exit node, finished");
273                     finished = true;
274                 }
275             } catch (NodeExecutorException ne) {
276                 // A legitimate configuration exception or execution exception
277
// has occured and this needs to be handled
278
handleException(new ActionFlowException(ne), action);
279                 finished = true;
280                 continue;
281             } catch (Exception JavaDoc e) {
282                 // Exit nodes can not throw exceptions because their actions
283
// are ignored
284
if (dest.isExitPoint()) {
285                     handleException(new ActionFlowException("Exit nodes can not" +
286                         " throw exceptions", e), action);
287                     finished = true;
288                     continue;
289                 }
290
291                 // Set the result to the Exception that was thrown and loop to
292
// see if there is a Node that can handle it
293
result = e;
294             }
295
296             if (!finished && result == null) {
297                handleException(new ActionFlowException(dest.getName() +
298                    " returned a null result Object and is not an exit point"),
299                    action);
300                finished = true;
301                continue;
302             }
303
304             // If we are done and the last node was on
305
if (finished && dest.isLongTxnEnabled()) {
306                 endLongTransaction(request, response, dest, action, finished);
307                 finished = true;
308             }
309
310             action.setAction(result);
311         }
312
313         return action.getNode();
314     }
315
316     /**
317      * Handles an exception by first checking with the NodeExecutorRegistry to
318      * determine if an {@link ExceptionHandler ExceptionHandler} is registered
319      * and if so, it calls that to handle the exception. Otheriwse the exception
320      * is rethrown.
321      */

322     protected static void handleException(ActionFlowException exception,
323             ActionFlowAction action)
324     throws ActionFlowException {
325         // Log the exception before we do the handling process
326
logger.error(exception.getMessage());
327
328         // Look for the ExceptionHandler to handle the Exception given. If one is not
329
// found then throw the exception
330
ExceptionHandler handler = NodeExecutorRegistry.lookupExceptionHandler();
331         if (handler == null) {
332             throw exception;
333         }
334
335         // See if the handler wants to handle it
336
if (!handler.handleException(exception, action)) {
337             throw exception;
338         }
339     }
340
341     protected static Node findReEntryNode(ActionFlowAction action,
342             Namespace namespace)
343     throws ActionFlowException {
344         // Get the appropriate name of the action depending on the class
345
// type
346
String JavaDoc actionName = "";
347         if (action.getAction() instanceof Exception JavaDoc) {
348             actionName = action.getAction().getClass().getName();
349         } else {
350             actionName = action.getAction().toString();
351         }
352
353         // Attempt to re-enter the namespace. This will cover the
354
// posibility of wildcard nodes.
355
// TODO This is incorrect, make a new reEntry method for wildcard nodes
356
Node dest = namespace.findEntry(actionName);
357         if (dest == null) {
358             handleException(new ActionFlowException("Could not find node to " +
359                 "re-enter the namespace named: " + namespace.getName() +
360                 " for the action named: " + actionName), action);
361         }
362
363         if (logger.isDebugEnabled() && dest != null) {
364             logger.debug("Link not found but new destination found: " + dest);
365         }
366
367         return dest;
368     }
369
370     /**
371      * Starts the long transaction by finding the URL if any and calling the
372      * {@link LongTxnHandler LongTxnHandler} registered above. This also saves a
373      * flag into the session to denote that the long transaction is in progress.
374      *
375      * @param request The HttpServletRequest to get the session from
376      */

377     private static void startLongTransaction(HttpServletRequest JavaDoc request,
378             HttpServletResponse JavaDoc response, Node dest, ActionFlowAction action)
379     throws ActionFlowException {
380         LongTxnSetup setup = dest.getNamespace().getLongTxnSetup();
381         String JavaDoc url = dest.getLongTxnStartURL();
382         if (url == null && setup != null) {
383             url = setup.getLongTxnStartURL(action);
384         }
385
386         try {
387             handler.handleStartLongTxn(request, response, url);
388         } catch (MVCException mvce) {
389             throw new ActionFlowException(mvce.getMessage(), mvce);
390         }
391
392         request.setAttribute(LONG_TXN_KEY, LONG_TXN_VALUE);
393     }
394
395     /**
396      * Ends the long transaction by finding the URL, the category and then
397      * generating a result that can be passed to the {@link LongTxnHandler
398      * LongTxnHandler}. This also erases the flag in the session.
399      *
400      * @param request The HttpServletRequest to get the session from
401      */

402     private static void endLongTransaction(HttpServletRequest JavaDoc request,
403             HttpServletResponse JavaDoc response, Node node, ActionFlowAction action,
404             boolean finished)
405     throws ActionFlowException {
406         LongTxnSetup setup = node.getNamespace().getLongTxnSetup();
407         String JavaDoc url = node.getLongTxnEndURL();
408         if (url == null && setup != null) {
409             url = setup.getLongTxnEndURL(action);
410         }
411
412         String JavaDoc category = node.getLongTxnCategory();
413         if (category == null) {
414             category = setup.getLongTxnURLCategory(action);
415         }
416
417         Result ret = null;
418         if (finished && node instanceof Result) {
419             ret = (Result) node;
420             ret = new GenericResult(ret.getURL(), category, false);
421         } else if (!finished) {
422             ret = new ActionFlowResult(action, node, category);
423         } else {
424             throw new ActionFlowException("Problem ending long " +
425                 "transaction support. Last node was not a " +
426                 "presentation or Renderable node.");
427         }
428
429         // Output the end, flag that we are finished, and set the Node
430
// to null so that the MVC code does not try to do a forward
431
try {
432             handler.handleEndLongTxn(request, response, url, ret);
433             action.setNode(null);
434         } catch (MVCException mvce) {
435             throw new ActionFlowException(mvce.getMessage(), mvce);
436         } finally {
437             request.removeAttribute(LONG_TXN_KEY);
438         }
439     }
440
441     /**
442      * Determines if the current request is in a long transaction
443      *
444      * @param request The HttpServletRequest to get the session from
445      * @return True if the current request is in a long transaction, false
446      * otherwise
447      */

448     private static boolean isLongTransaction(HttpServletRequest JavaDoc request) {
449         return (request.getAttribute(LONG_TXN_KEY) != null);
450     }
451
452     /**
453      * Resets any previously start long transactions for this users session.
454      *
455      * @param request The HttpServletRequest to clear the attribute from
456      */

457     private static void resetLongTransaction(HttpServletRequest JavaDoc request) {
458         request.removeAttribute(LONG_TXN_KEY);
459     }
460 }
Popular Tags