KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > beehive > netui > pageflow > FlowController


1 /*
2  * Copyright 2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * $Header:$
17  */

18 package org.apache.beehive.netui.pageflow;
19
20 import org.apache.beehive.netui.core.urls.MutableURI;
21 import org.apache.beehive.netui.pageflow.config.PageFlowActionMapping;
22 import org.apache.beehive.netui.pageflow.handler.ActionForwardHandler;
23 import org.apache.beehive.netui.pageflow.handler.ExceptionsHandler;
24 import org.apache.beehive.netui.pageflow.handler.FlowControllerHandlerContext;
25 import org.apache.beehive.netui.pageflow.handler.Handlers;
26 import org.apache.beehive.netui.pageflow.handler.LoginHandler;
27 import org.apache.beehive.netui.pageflow.internal.AdapterManager;
28 import org.apache.beehive.netui.pageflow.internal.InternalConstants;
29 import org.apache.beehive.netui.pageflow.internal.InternalExpressionUtils;
30 import org.apache.beehive.netui.pageflow.internal.InternalUtils;
31 import org.apache.beehive.netui.pageflow.scoping.ScopedRequest;
32 import org.apache.beehive.netui.util.internal.FileUtils;
33 import org.apache.beehive.netui.util.internal.InternalStringBuilder;
34 import org.apache.beehive.netui.util.internal.cache.ClassLevelCache;
35 import org.apache.beehive.netui.util.logging.Logger;
36 import org.apache.struts.Globals;
37 import org.apache.struts.action.ActionForm;
38 import org.apache.struts.action.ActionForward;
39 import org.apache.struts.action.ActionMapping;
40 import org.apache.struts.action.ActionMessage;
41 import org.apache.struts.action.ActionMessages;
42 import org.apache.struts.action.ActionServlet;
43 import org.apache.struts.action.RequestProcessor;
44 import org.apache.struts.config.ActionConfig;
45 import org.apache.struts.config.ControllerConfig;
46 import org.apache.struts.config.ModuleConfig;
47 import org.apache.struts.util.MessageResources;
48 import org.apache.struts.util.RequestUtils;
49 import org.apache.struts.util.TokenProcessor;
50
51 import javax.security.auth.login.LoginException JavaDoc;
52 import javax.servlet.ServletContext JavaDoc;
53 import javax.servlet.ServletException JavaDoc;
54 import javax.servlet.http.HttpServletRequest JavaDoc;
55 import javax.servlet.http.HttpServletResponse JavaDoc;
56 import javax.servlet.http.HttpSession JavaDoc;
57 import javax.sql.DataSource JavaDoc;
58 import java.io.IOException JavaDoc;
59 import java.lang.reflect.Field JavaDoc;
60 import java.lang.reflect.InvocationTargetException JavaDoc;
61 import java.lang.reflect.Method JavaDoc;
62 import java.net.URISyntaxException JavaDoc;
63 import java.util.ArrayList JavaDoc;
64 import java.util.Iterator JavaDoc;
65 import java.util.Locale JavaDoc;
66 import java.util.Map JavaDoc;
67
68
69
70 /**
71  * Base class for user-written flow controllers - {@link PageFlowController}s and {@link SharedFlowController}s.
72  */

73 public abstract class FlowController extends PageFlowManagedObject
74         implements PageFlowConstants, ActionResolver
75 {
76     private static final Logger _log = Logger.getInstance( FlowController.class );
77     
78     private static final String JavaDoc ONCREATE_EXCEPTION_FORWARD = InternalConstants.ATTR_PREFIX + "onCreateException";
79     private static final String JavaDoc CACHEID_ACTION_METHODS = InternalConstants.ATTR_PREFIX + "actionMethods";
80     private static final int DEFAULT_MAX_CONCURRENT_REQUEST_COUNT = 4;
81     private static final String JavaDoc MAX_CONCURRENT_REQUESTS_PARAM = "pageflow-max-concurrent-requests";
82     private static final int EXCEEDED_MAX_CONCURRENT_REQUESTS_ERRORCODE = 503;
83     private static final Locale JavaDoc DEFAULT_LOCALE = Locale.getDefault();
84     private static final ActionForward NULL_ACTION_FORWARD = new ActionForward();
85     private static final TokenProcessor TOKEN_PROCESSOR = TokenProcessor.getInstance();
86     
87     /**
88      * The system default Locale.
89      *
90      * @deprecated Use {@link #getDefaultLocale}.
91      */

92     protected static Locale JavaDoc defaultLocale = DEFAULT_LOCALE;
93
94
95     /**
96      * Get the current Struts ActionServlet.
97      *
98      * @deprecated This variable will be removed with no replacement. In most cases,
99      * {@link FlowController#getServletContext()} is sufficient; for other cases, the ActionServlet itself
100      * is in the ServletContext attribute {@link Globals#ACTION_SERVLET_KEY}.
101      */

102     protected transient ActionServlet servlet = null;
103     
104     
105     static class PerRequestState
106     {
107         private HttpServletRequest JavaDoc _request;
108         private HttpServletResponse JavaDoc _response;
109         private ActionMapping _actionMapping;
110
111         public PerRequestState( HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response, ActionMapping actionMapping )
112         {
113             _request = request;
114             _response = response;
115             _actionMapping = actionMapping;
116         }
117
118         public HttpServletRequest JavaDoc getRequest()
119         {
120             return _request;
121         }
122
123         public HttpServletResponse JavaDoc getResponse()
124         {
125             return _response;
126         }
127
128         public ActionMapping getActionMapping()
129         {
130             return _actionMapping;
131         }
132     }
133
134     /**
135      * Stores per-request state, which is <i>only valid during calls to {@link FlowController#execute} or {@link FlowController#handleException}</i>.
136      */

137     private transient PerRequestState _perRequestState;
138
139     /**
140      * Cached reference to the associated Struts ModuleConfig.
141      */

142     private transient ModuleConfig _moduleConfig = null;
143
144     /**
145      * @see #incrementRequestCount
146      */

147     private transient int _requestCount = 0;
148     
149     /**
150      * @see #incrementRequestCount
151      */

152     private static int _maxConcurrentRequestCount = -1;
153     
154
155     /**
156      * Default constructor.
157      */

158     protected FlowController()
159     {
160     }
161
162     /**
163      * Reinitialize the object for a new request. Used by the framework; normally should not be called directly.
164      */

165     public void reinitialize( HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response, ServletContext JavaDoc servletContext )
166     {
167         //
168
// Cache the associated ModuleConfig. This is used throughout the code, in places where the request
169
// isn't available to do a lazy initialization.
170
//
171
super.reinitialize( request, response, servletContext );
172         initModuleConfig( servletContext, request );
173         servlet = getServlet();
174     }
175
176     /**
177      * Log in the user, using "weak" username/password authentication.
178      *
179      * @param username the user's login name
180      * @param password the user's password
181      *
182      * @exception LoginException if the authentication failed
183      */

184     public void login( String JavaDoc username, String JavaDoc password )
185         throws LoginException JavaDoc
186     {
187         LoginHandler lh = Handlers.get( getServletContext() ).getLoginHandler();
188         lh.login( getHandlerContext(), username, password );
189     }
190     
191     /**
192      * Log out the current user.
193      *
194      * @param invalidateSessions if true, the session is invalidated (on all single-signon webapps);
195      * otherwise the session and its data are left intact (except for authentication
196      * information used internally by the server). To invalidate the session in only the
197      * current webapp, set this parameter to <code>false</code> and call
198      * {@link FlowController#getSession}.invalidate().
199      */

200     public void logout( boolean invalidateSessions )
201     {
202         LoginHandler lh = Handlers.get( getServletContext() ).getLoginHandler();
203         lh.logout( getHandlerContext(), invalidateSessions );
204     }
205
206     /**
207      * Send a Page Flow error to the browser.
208      *
209      * @deprecated Use {@link FlowController#sendError(String, HttpServletRequest, HttpServletResponse)} instead.
210      * @param errText the error message to display.
211      * @param response the current HttpServletResponse.
212      */

213     protected void sendError( String JavaDoc errText, HttpServletResponse JavaDoc response )
214         throws IOException JavaDoc
215     {
216         sendError( errText, null, response );
217     }
218     
219     /**
220      * Send a Page Flow error to the browser.
221      *
222      * @param errText the error message to display.
223      * @param response the current HttpServletResponse.
224      */

225     protected void sendError( String JavaDoc errText, HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response )
226         throws IOException JavaDoc
227     {
228         InternalUtils.sendError( "PageFlow_Custom_Error", null, request, response,
229                                  new Object JavaDoc[]{ getDisplayName(), errText } );
230     }
231     
232     /**
233      * Handle the given exception - invoke user code if appropriate and return a destination URI.
234      *
235      * @param ex the Exception to handle.
236      * @param mapping the Struts action mapping for current Struts action being processed.
237      * @param form the form-bean (if any) associated with the Struts action being processed. May be null.
238      * @param request the current HttpServletRequest.
239      * @param response the current HttpServletResponse.
240      * @return a Struts ActionForward object that specifies the URI that should be displayed.
241      * @throws ServletException if another Exception is thrown during handling of <code>ex</code>.
242      */

243     public synchronized ActionForward handleException( Throwable JavaDoc ex, ActionMapping mapping,
244                                                        ActionForm form, HttpServletRequest JavaDoc request,
245                                                        HttpServletResponse JavaDoc response )
246         throws IOException JavaDoc, ServletException JavaDoc
247     {
248         PerRequestState prevState = setPerRequestState( new PerRequestState( request, response, mapping ) );
249         
250         try
251         {
252             ExceptionsHandler eh = Handlers.get( getServletContext() ).getExceptionsHandler();
253             FlowControllerHandlerContext context = getHandlerContext();
254             
255             // First, put the exception into the request (or other applicable context).
256
Throwable JavaDoc unwrapped = eh.unwrapException( context, ex );
257             eh.exposeException( context, unwrapped, mapping );
258             return eh.handleException( context, unwrapped, mapping, form );
259         }
260         finally
261         {
262             setPerRequestState( prevState );
263         }
264     }
265     
266     /**
267      * Get the name of the current action being executed. This call is only valid
268      * during {@link FlowController#execute} (where any user action method is invoked), and during the lifecycle
269      * methods {@link FlowController#beforeAction} and {@link FlowController#afterAction}.
270      *
271      * @return the name of the current action being executed.
272      * @throws IllegalStateException if this method is invoked outside of action method
273      * execution (i.e., outside of the call to {@link FlowController#execute}, and outside of
274      * {@link FlowController#onCreate}, {@link FlowController#beforeAction}, {@link FlowController#afterAction}.
275      */

276
277     protected String JavaDoc getCurrentActionName()
278     {
279         return InternalUtils.getActionName( getActionMapping() );
280     }
281     
282     /**
283      * Perform decision logic to determine the next URI to be displayed.
284      *
285      * @param mapping the Struts ActionMapping for the current action being processed.
286      * @param form the form-bean (if any) associated with the Struts action being processed. May be null.
287      * @param request the current HttpServletRequest.
288      * @param response the current HttpServletResponse.
289      * @return a Struts ActionForward object that specifies the next URI to be displayed.
290      * @throws Exception if an Exception was thrown during user action-handling code.
291      */

292     public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest JavaDoc request,
293                                   HttpServletResponse JavaDoc response )
294             throws Exception JavaDoc
295     {
296         //
297
// Don't actually run the action (and perform the associated synchronization) if there are too many
298
// concurrent requests to this instance.
299
//
300
if ( incrementRequestCount( request, response, getServletContext() ) )
301         {
302             try
303             {
304                 synchronized ( this )
305                 {
306                     return internalExecute( mapping, form, request, response );
307                 }
308             }
309             finally
310             {
311                 decrementRequestCount( request );
312             }
313         }
314         else
315         {
316             return null; // error was written to the response by incrementRequestCount()
317
}
318     }
319
320     /**
321      * An internal method for executing an action; should not be invoked directly.
322      */

323     protected ActionForward internalExecute( ActionMapping mapping, ActionForm form, HttpServletRequest JavaDoc request,
324                                              HttpServletResponse JavaDoc response )
325         throws Exception JavaDoc
326     {
327         ServletContainerAdapter sca = AdapterManager.getServletContainerAdapter( getServletContext() );
328         PageFlowEventReporter eventReporter = sca.getEventReporter();
329         eventReporter.actionRaised( this, mapping, form, request, response );
330         long startTime = System.currentTimeMillis();
331         
332         //
333
// If we handled an exception in onCreate, just forward to the result of that.
334
//
335
ActionForward onCreateFwd = ( ActionForward ) request.getAttribute( ONCREATE_EXCEPTION_FORWARD );
336         
337         if ( onCreateFwd != null )
338         {
339             return onCreateFwd == NULL_ACTION_FORWARD ? null : onCreateFwd;
340         }
341         
342         
343         PageFlowUtils.setActionURI( request );
344         
345         //
346
// First change the actionPath (path) so that it lines up with our naming convention
347
// for action methods.
348
//
349
boolean gotPastBeforeAction = false;
350         ServletContext JavaDoc servletContext = getServletContext();
351         PerRequestState prevState = setPerRequestState( new PerRequestState( request, response, mapping ) );
352
353         try
354         {
355             //
356
// beforeAction callback
357
//
358
beforeAction();
359             gotPastBeforeAction = true;
360             
361             PageFlowActionMapping pfActionMapping =
362                     mapping instanceof PageFlowActionMapping ? ( PageFlowActionMapping ) mapping : null;
363             Object JavaDoc unwrappedForm = InternalUtils.unwrapFormBean( form );
364             
365             //
366
// mapping.isOverloaded() means it's the base mapping for a set of overloaded mappings.
367
// Find the one appropriate to the passed-in form.
368
//
369
if ( unwrappedForm != null && pfActionMapping != null )
370             {
371                 if ( pfActionMapping.isOverloaded() )
372                 {
373                     String JavaDoc mappingPath = pfActionMapping.getPath();
374
375                     //
376
// Try the form class and all superclasses to get an overloaded action path.
377
//
378
for ( Class JavaDoc i = unwrappedForm.getClass(); i != null; i = i.getSuperclass() )
379                     {
380                         String JavaDoc formQualifiedActionPath = getFormQualifiedActionPath( i, mappingPath );
381                         ActionConfig cf = pfActionMapping.getModuleConfig().findActionConfig( formQualifiedActionPath );
382                             
383                         if ( cf != null )
384                         {
385                             assert cf instanceof PageFlowActionMapping : cf.getClass().getName();
386                                 
387                             if ( _log.isDebugEnabled() )
388                             {
389                                 _log.debug( "Found form-specific mapping " + cf.getPath() +
390                                            " -- choosing this one over current mapping " + mappingPath );
391                             }
392                                 
393                             pfActionMapping = ( PageFlowActionMapping ) cf;
394                             mapping = pfActionMapping;
395                             break;
396                         }
397                     }
398                 }
399             }
400             
401             String JavaDoc actionName = InternalUtils.getActionName( mapping );
402             
403             //
404
// Check whether isLoginRequired=true for this action.
405
//
406
LoginHandler loginHandler = Handlers.get( getServletContext() ).getLoginHandler();
407             
408             if ( pfActionMapping != null && pfActionMapping.isLoginRequired()
409                  && loginHandler.getUserPrincipal( getHandlerContext() ) == null )
410             {
411                 NotLoggedInException ex = createNotLoggedInException( actionName, request );
412                 return handleException( ex, mapping, form, request, response );
413             }
414             
415             //
416
// Now delegate to the appropriate action method, or if it's a simple action, handle it that way.
417
//
418
ActionForward retVal;
419             if ( pfActionMapping != null && pfActionMapping.isSimpleAction() )
420             {
421                 retVal = handleSimpleAction( pfActionMapping, form, request, servletContext );
422             }
423             else
424             {
425                 retVal = getActionMethodForward( actionName, unwrappedForm, request, response, mapping );
426             }
427             
428             ActionForward ret = forwardTo( retVal, mapping, request, response, actionName, null, form, servletContext );
429             long timeTaken = System.currentTimeMillis() - startTime;
430             eventReporter.actionSuccess( this, mapping, form, request, response, ret, timeTaken );
431             return ret;
432         }
433         catch ( Exception JavaDoc e )
434         {
435             //
436
// Even though we handle any Throwable thrown by the user's action method, we don't need
437
// to catch Throwable here, because anything thrown by the action method will be wrapped
438
// in an InvocationTargetException. Any Error (or other Throwable) that appears here
439
// should not be handled by handleException() -- it's probably a framework problem and
440
// should bubble out to the container.
441
//
442
return handleException( e, mapping, form, request, response );
443         }
444         finally
445         {
446             try
447             {
448                 ActionForward overrideReturn = null;
449                 
450                 if ( gotPastBeforeAction )
451                 {
452                     //
453
// afterAction callback
454
//
455
try
456                     {
457                         afterAction();
458                     }
459                     catch ( Throwable JavaDoc th )
460                     {
461                         overrideReturn = handleException( th, mapping, form, request, response );
462                     }
463                 }
464                 
465                 //
466
// Store information on this action for use with navigateTo=Jpf.NavigateTo.previousAction.
467
//
468
savePreviousActionInfo( form, request, mapping, getServletContext() );
469                 
470                 if ( overrideReturn != null )
471                 {
472                     return overrideReturn;
473                 }
474             }
475             finally
476             {
477                 setPerRequestState( prevState );
478             }
479         }
480     }
481
482     ActionForward forwardTo( ActionForward fwd, ActionMapping mapping, HttpServletRequest JavaDoc request,
483                              HttpServletResponse JavaDoc response, String JavaDoc actionName, ModuleConfig altModuleConfig,
484                              ActionForm form, ServletContext JavaDoc servletContext )
485     {
486         //
487
// This method is overridden in PageFlowController. Even though we're just delegating here, we can't remove it.
488
//
489
ActionForwardHandler handler = Handlers.get( servletContext ).getActionForwardHandler();
490         FlowControllerHandlerContext context = new FlowControllerHandlerContext( request, response, this );
491         return handler.doForward( context, fwd, mapping, actionName, altModuleConfig, form );
492     }
493     
494     NotLoggedInException createNotLoggedInException( String JavaDoc actionName, HttpServletRequest JavaDoc request )
495     {
496         if ( InternalUtils.sessionExpired( request ) )
497         {
498             return new LoginExpiredException( actionName, this );
499         }
500         else
501         {
502             return new NotLoggedInException( actionName, this );
503         }
504     }
505     
506     /**
507      * Initialize after object creation. This is a framework-invoked method; it should not normally be called directly.
508      */

509     public synchronized void create( HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response,
510                                      ServletContext JavaDoc servletContext )
511     {
512         PerRequestState prevState = setPerRequestState( new PerRequestState( request, response, null ) );
513         
514         try
515         {
516             try
517             {
518                 super.create( request, response, servletContext );
519             }
520             catch ( Throwable JavaDoc th )
521             {
522                 try
523                 {
524                     _log.info( "Handling exception in onCreate(), FlowController " + this, th );
525                     ActionForward fwd = handleException( th, null, null, request, response );
526                     if ( fwd == null ) fwd = NULL_ACTION_FORWARD;
527                     request.setAttribute( ONCREATE_EXCEPTION_FORWARD, fwd );
528                 }
529                 catch ( Exception JavaDoc e )
530                 {
531                     _log.error( "Exception thrown while handling exception in onCreate(): " + e.getMessage(), th );
532                 }
533             }
534         }
535         finally
536         {
537             setPerRequestState( prevState );
538         }
539         
540         PageFlowEventReporter er = AdapterManager.getServletContainerAdapter( servletContext ).getEventReporter();
541         er.flowControllerCreated( this, request, response );
542     }
543     
544     /**
545      * Internal destroy method that is invoked when this object is being removed from the session. This is a
546      * framework-invoked method; it should not normally be called directly.
547      */

548     void destroy( HttpSession JavaDoc session )
549     {
550         onDestroy(); // for backwards compatiblity
551
super.destroy( session );
552         
553         //
554
// We may have lost our transient ServletContext reference. Try to get the ServletContext reference from the
555
// HttpSession object.
556
//
557
ServletContext JavaDoc servletContext = getServletContext();
558         if ( servletContext == null && session != null ) servletContext = session.getServletContext();
559
560         if ( servletContext != null )
561         {
562             PageFlowEventReporter er = AdapterManager.getServletContainerAdapter( servletContext ).getEventReporter();
563             er.flowControllerDestroyed( this );
564         }
565     }
566     
567     /**
568      * Get the Struts module path for this controller.
569      *
570      * @return a String that is the Struts module path for this controller.
571      */

572     public abstract String JavaDoc getModulePath();
573
574     /**
575      * Callback that occurs before any user action method is invoked. {@link FlowController#getRequest},
576      * {@link FlowController#getResponse}, {@link FlowController#getSession}, and
577      * {@link FlowController#getActionMapping} may all be used during this method. The action to be run can be
578      * discovered by calling {@link ActionMapping#getPath} on the value returned from
579      * {@link FlowController#getActionMapping}.
580      */

581     protected synchronized void beforeAction()
582         throws Exception JavaDoc
583     {
584     }
585     
586     /**
587      * Callback that occurs after any user action method is invoked. {@link FlowController#getRequest},
588      * {@link FlowController#getResponse}, {@link FlowController#getSession}, and
589      * {@link FlowController#getActionMapping} may all be used during this method. The action that was run can be
590      * discovered by calling {@link ActionMapping#getPath} on the value returned from
591      * {@link FlowController#getActionMapping}.
592      */

593     protected synchronized void afterAction()
594         throws Exception JavaDoc
595     {
596     }
597
598     /**
599      * Callback that is invoked when this controller instance is created. {@link FlowController#getRequest},
600      * {@link FlowController#getResponse}, {@link FlowController#getSession} may all be used during this method.
601      */

602     protected void onCreate()
603         throws Exception JavaDoc
604     {
605     }
606
607     /**
608      * Callback that is invoked when this controller instance is "destroyed", i.e., removed from the user session.
609      * {@link FlowController#getRequest}, {@link FlowController#getResponse}, and {@link FlowController#getActionMapping}
610      * may <i>not</i> be used during this method, since it may be called due to session termination outside of a
611      * request. {@link FlowController#getSession} also may not be used, but the session is passed as an argument
612      * to {@link FlowController#onDestroy(HttpSession)}, which should be used in place of this method.
613      * <br>
614      * Note that this method is <strong>not synchronized</strong>. It is dangerous to synchronize your override of
615      * this method because it is invoked during a callback from the Servlet container. Depending on the container,
616      * synchronization here can cause deadlocks.
617      *
618      * @deprecated {@link FlowController#onDestroy(HttpSession)} should be used instead.
619      */

620     protected void onDestroy()
621     {
622     }
623     
624     /**
625      * Callback that is invoked when this controller instance is "destroyed", i.e., removed from the user session.
626      * {@link FlowController#getRequest}, {@link FlowController#getResponse}, and {@link FlowController#getActionMapping}
627      * may <i>not</i> be used during this method, since it may be called due to session termination outside of a
628      * request. {@link FlowController#getSession} also may not be used, but the session is passed as an argument.
629      * <br>
630      * Note that this method is <strong>not synchronized</strong>. It is dangerous to synchronize your override of
631      * this method because it is invoked during a callback from the Servlet container. Depending on the container,
632      * synchronization here can cause deadlocks.
633      */

634     protected void onDestroy( HttpSession JavaDoc session )
635     {
636     }
637     
638     /**
639      * Get a legacy PreviousPageInfo.
640      * @deprecated This method will be removed without replacement in a future release.
641      */

642     public abstract PreviousPageInfo getPreviousPageInfoLegacy( PageFlowController curJpf, HttpServletRequest JavaDoc request );
643     
644     /**
645      * Get an action handler method of the given name/signature.
646      *
647      * @param methodName the name of the action handler method to query.
648      * @param argType the type of the argument to the action handler method; if <code>null</code>,
649      * the method takes no arguments.
650      * @return the desired Method, or <code>null</code> if it doesn't exist.
651      */

652     protected Method JavaDoc getActionMethod( String JavaDoc methodName, Class JavaDoc argType )
653     {
654         String JavaDoc cacheKey = argType != null ? methodName + '/' + argType.getName() : methodName;
655         Class JavaDoc thisClass = getClass();
656         ClassLevelCache cache = ClassLevelCache.getCache( thisClass );
657         Method JavaDoc actionMethod = ( Method JavaDoc ) cache.get( CACHEID_ACTION_METHODS, cacheKey );
658         
659         if ( actionMethod != null )
660         {
661             return actionMethod;
662         }
663         else
664         {
665             //
666
// We didn't find it in the cache. Look for it reflectively.
667
//
668
if ( argType == null )
669             {
670                 //
671
// No form -- look for a method with no arguments.
672
//
673
actionMethod = InternalUtils.lookupMethod( thisClass, methodName, null );
674             }
675             else
676             {
677                 //
678
// Has a form. Look for a method with a single argument -- either the given type
679
// or any superclass.
680
//
681
while ( argType != null )
682                 {
683                     actionMethod = InternalUtils.lookupMethod( thisClass, methodName, new Class JavaDoc[]{ argType } );
684                     
685                     if ( actionMethod != null )
686                     {
687                         break;
688                     }
689                     
690                     argType = argType.getSuperclass();
691                 }
692             }
693                 
694             if ( actionMethod != null && actionMethod.getReturnType().equals( Forward.class ) )
695             {
696                 actionMethod.setAccessible( true );
697                 cache.put( CACHEID_ACTION_METHODS, cacheKey, actionMethod );
698                 return actionMethod;
699             }
700         }
701         
702         return null;
703     }
704
705     private Class JavaDoc getFormClass( Object JavaDoc form, ActionMapping mapping, HttpServletRequest JavaDoc request )
706         throws ClassNotFoundException JavaDoc
707     {
708         if ( mapping instanceof PageFlowActionMapping )
709         {
710             String JavaDoc formClassName = ( ( PageFlowActionMapping ) mapping ).getFormClass();
711             
712             if ( formClassName != null )
713             {
714                 return InternalUtils.getReloadableClass( formClassName, getServletContext() );
715             }
716         }
717         
718         return form != null ? form.getClass() : null;
719     }
720     
721     /**
722      * Get the ActionForward returned by the action handler method that corresponds to the
723      * given action name and form-bean, or send an error to the browser if there is no
724      * matching method.
725      *
726      * @param actionName the name of the Struts action to handle.
727      * @param inputForm the form-bean associated with the action. May be <code>null</code>.
728      * @param response the current HttpServletResponse.
729      * @return the ActionForward returned by the action handler method, or <code>null</code> if
730      * there was no matching method (in which case an error was written to the
731      * browser.
732      * @throws Exception if an Exception was raised in user code.
733      */

734     ActionForward getActionMethodForward( String JavaDoc actionName, Object JavaDoc inputForm,
735                                           HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response,
736                                           ActionMapping mapping )
737         throws Exception