View Javadoc

1   /*
2    * $Id: RequestProcessor.java 421119 2006-07-12 04:49:11Z wsmoak $
3    *
4    * Copyright 2000-2006 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.struts.action;
19  
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  import org.apache.struts.Globals;
23  import org.apache.struts.config.ActionConfig;
24  import org.apache.struts.config.ExceptionConfig;
25  import org.apache.struts.config.ForwardConfig;
26  import org.apache.struts.config.ModuleConfig;
27  import org.apache.struts.upload.MultipartRequestWrapper;
28  import org.apache.struts.util.MessageResources;
29  import org.apache.struts.util.RequestUtils;
30  
31  import javax.servlet.RequestDispatcher;
32  import javax.servlet.ServletContext;
33  import javax.servlet.ServletException;
34  import javax.servlet.http.HttpServletRequest;
35  import javax.servlet.http.HttpServletResponse;
36  import javax.servlet.http.HttpSession;
37  
38  import java.io.IOException;
39  
40  import java.util.HashMap;
41  import java.util.Iterator;
42  import java.util.Locale;
43  
44  /***
45   * <p><strong>RequestProcessor</strong> contains the processing logic that the
46   * {@link ActionServlet} performs as it receives each servlet request from the
47   * container. You can customize the request processing behavior by subclassing
48   * this class and overriding the method(s) whose behavior you are interested
49   * in changing.</p>
50   *
51   * @version $Rev: 421119 $ $Date: 2006-07-11 21:49:11 -0700 (Tue, 11 Jul 2006) $
52   * @since Struts 1.1
53   */
54  public class RequestProcessor {
55      // ----------------------------------------------------- Manifest Constants
56  
57      /***
58       * <p>The request attribute under which the path information is stored for
59       * processing during a <code>RequestDispatcher.include</code> call.</p>
60       */
61      public static final String INCLUDE_PATH_INFO =
62          "javax.servlet.include.path_info";
63  
64      /***
65       * <p>The request attribute under which the servlet path information is
66       * stored for processing during a <code>RequestDispatcher.include</code>
67       * call.</p>
68       */
69      public static final String INCLUDE_SERVLET_PATH =
70          "javax.servlet.include.servlet_path";
71  
72      /***
73       * <p>Commons Logging instance.</p>
74       */
75      protected static Log log = LogFactory.getLog(RequestProcessor.class);
76  
77      // ----------------------------------------------------- Instance Variables
78  
79      /***
80       * <p>The set of <code>Action</code> instances that have been created and
81       * initialized, keyed by the fully qualified Java class name of the
82       * <code>Action</code> class.</p>
83       */
84      protected HashMap actions = new HashMap();
85  
86      /***
87       * <p>The <code>ModuleConfiguration</code> with which we are
88       * associated.</p>
89       */
90      protected ModuleConfig moduleConfig = null;
91  
92      /***
93       * <p>The servlet with which we are associated.</p>
94       */
95      protected ActionServlet servlet = null;
96  
97      // --------------------------------------------------------- Public Methods
98  
99      /***
100      * <p>Clean up in preparation for a shutdown of this application.</p>
101      */
102     public void destroy() {
103         synchronized (this.actions) {
104             Iterator actions = this.actions.values().iterator();
105 
106             while (actions.hasNext()) {
107                 Action action = (Action) actions.next();
108 
109                 action.setServlet(null);
110             }
111 
112             this.actions.clear();
113         }
114 
115         this.servlet = null;
116     }
117 
118     /***
119      * <p>Initialize this request processor instance.</p>
120      *
121      * @param servlet      The ActionServlet we are associated with
122      * @param moduleConfig The ModuleConfig we are associated with.
123      * @throws ServletException If an error occor during initialization
124      */
125     public void init(ActionServlet servlet, ModuleConfig moduleConfig)
126         throws ServletException {
127         synchronized (actions) {
128             actions.clear();
129         }
130 
131         this.servlet = servlet;
132         this.moduleConfig = moduleConfig;
133     }
134 
135     /***
136      * <p>Process an <code>HttpServletRequest</code> and create the
137      * corresponding <code>HttpServletResponse</code> or dispatch to another
138      * resource.</p>
139      *
140      * @param request  The servlet request we are processing
141      * @param response The servlet response we are creating
142      * @throws IOException      if an input/output error occurs
143      * @throws ServletException if a processing exception occurs
144      */
145     public void process(HttpServletRequest request, HttpServletResponse response)
146         throws IOException, ServletException {
147         // Wrap multipart requests with a special wrapper
148         request = processMultipart(request);
149 
150         // Identify the path component we will use to select a mapping
151         String path = processPath(request, response);
152 
153         if (path == null) {
154             return;
155         }
156 
157         if (log.isDebugEnabled()) {
158             log.debug("Processing a '" + request.getMethod() + "' for path '"
159                 + path + "'");
160         }
161 
162         // Select a Locale for the current user if requested
163         processLocale(request, response);
164 
165         // Set the content type and no-caching headers if requested
166         processContent(request, response);
167         processNoCache(request, response);
168 
169         // General purpose preprocessing hook
170         if (!processPreprocess(request, response)) {
171             return;
172         }
173 
174         this.processCachedMessages(request, response);
175 
176         // Identify the mapping for this request
177         ActionMapping mapping = processMapping(request, response, path);
178 
179         if (mapping == null) {
180             return;
181         }
182 
183         // Check for any role required to perform this action
184         if (!processRoles(request, response, mapping)) {
185             return;
186         }
187 
188         // Process any ActionForm bean related to this request
189         ActionForm form = processActionForm(request, response, mapping);
190 
191         processPopulate(request, response, form, mapping);
192 
193         // Validate any fields of the ActionForm bean, if applicable
194         try {
195             if (!processValidate(request, response, form, mapping)) {
196                 return;
197             }
198         } catch (InvalidCancelException e) {
199             ActionForward forward = processException(request, response, e, form, mapping);
200             processForwardConfig(request, response, forward);
201             return;
202         } catch (IOException e) {
203             throw e;
204         } catch (ServletException e) {
205             throw e;
206         }
207 
208         // Process a forward or include specified by this mapping
209         if (!processForward(request, response, mapping)) {
210             return;
211         }
212 
213         if (!processInclude(request, response, mapping)) {
214             return;
215         }
216 
217         // Create or acquire the Action instance to process this request
218         Action action = processActionCreate(request, response, mapping);
219 
220         if (action == null) {
221             return;
222         }
223 
224         // Call the Action instance itself
225         ActionForward forward =
226             processActionPerform(request, response, action, form, mapping);
227 
228         // Process the returned ActionForward instance
229         processForwardConfig(request, response, forward);
230     }
231 
232     // ----------------------------------------------------- Processing Methods
233 
234     /***
235      * <p>Return an <code>Action</code> instance that will be used to process
236      * the current request, creating a new one if necessary.</p>
237      *
238      * @param request  The servlet request we are processing
239      * @param response The servlet response we are creating
240      * @param mapping  The mapping we are using
241      * @return An <code>Action</code> instance that will be used to process
242      *         the current request.
243      * @throws IOException if an input/output error occurs
244      */
245     protected Action processActionCreate(HttpServletRequest request,
246         HttpServletResponse response, ActionMapping mapping)
247         throws IOException {
248         // Acquire the Action instance we will be using (if there is one)
249         String className = mapping.getType();
250 
251         if (log.isDebugEnabled()) {
252             log.debug(" Looking for Action instance for class " + className);
253         }
254 
255         // If there were a mapping property indicating whether
256         // an Action were a singleton or not ([true]),
257         // could we just instantiate and return a new instance here?
258         Action instance;
259 
260         synchronized (actions) {
261             // Return any existing Action instance of this class
262             instance = (Action) actions.get(className);
263 
264             if (instance != null) {
265                 if (log.isTraceEnabled()) {
266                     log.trace("  Returning existing Action instance");
267                 }
268 
269                 return (instance);
270             }
271 
272             // Create and return a new Action instance
273             if (log.isTraceEnabled()) {
274                 log.trace("  Creating new Action instance");
275             }
276 
277             try {
278                 instance = (Action) RequestUtils.applicationInstance(className);
279 
280                 // Maybe we should propagate this exception
281                 // instead of returning null.
282             } catch (Exception e) {
283                 log.error(getInternal().getMessage("actionCreate",
284                         mapping.getPath()), e);
285 
286                 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
287                     getInternal().getMessage("actionCreate", mapping.getPath()));
288 
289                 return (null);
290             }
291 
292             actions.put(className, instance);
293         }
294 
295         if (instance.getServlet() == null) {
296             instance.setServlet(this.servlet);
297         }
298 
299         return (instance);
300     }
301 
302     /***
303      * <p>Retrieve and return the <code>ActionForm</code> associated with this
304      * mapping, creating and retaining one if necessary. If there is no
305      * <code>ActionForm</code> associated with this mapping, return
306      * <code>null</code>.</p>
307      *
308      * @param request  The servlet request we are processing
309      * @param response The servlet response we are creating
310      * @param mapping  The mapping we are using
311      * @return The <code>ActionForm</code> associated with this mapping.
312      */
313     protected ActionForm processActionForm(HttpServletRequest request,
314         HttpServletResponse response, ActionMapping mapping) {
315         // Create (if necessary) a form bean to use
316         ActionForm instance =
317             RequestUtils.createActionForm(request, mapping, moduleConfig,
318                 servlet);
319 
320         if (instance == null) {
321             return (null);
322         }
323 
324         // Store the new instance in the appropriate scope
325         if (log.isDebugEnabled()) {
326             log.debug(" Storing ActionForm bean instance in scope '"
327                 + mapping.getScope() + "' under attribute key '"
328                 + mapping.getAttribute() + "'");
329         }
330 
331         if ("request".equals(mapping.getScope())) {
332             request.setAttribute(mapping.getAttribute(), instance);
333         } else {
334             HttpSession session = request.getSession();
335 
336             session.setAttribute(mapping.getAttribute(), instance);
337         }
338 
339         return (instance);
340     }
341 
342     /***
343      * <p>Forward or redirect to the specified destination, by the specified
344      * mechanism.  This method uses a <code>ForwardConfig</code> object
345      * instead an <code>ActionForward</code>.</p>
346      *
347      * @param request  The servlet request we are processing
348      * @param response The servlet response we are creating
349      * @param forward  The ForwardConfig controlling where we go next
350      * @throws IOException      if an input/output error occurs
351      * @throws ServletException if a servlet exception occurs
352      */
353     protected void processForwardConfig(HttpServletRequest request,
354         HttpServletResponse response, ForwardConfig forward)
355         throws IOException, ServletException {
356         if (forward == null) {
357             return;
358         }
359 
360         if (log.isDebugEnabled()) {
361             log.debug("processForwardConfig(" + forward + ")");
362         }
363 
364         String forwardPath = forward.getPath();
365         String uri;
366 
367         // paths not starting with / should be passed through without any
368         // processing (ie. they're absolute)
369         if (forwardPath.startsWith("/")) {
370             // get module relative uri
371             uri = RequestUtils.forwardURL(request, forward, null);
372         } else {
373             uri = forwardPath;
374         }
375 
376         if (forward.getRedirect()) {
377             // only prepend context path for relative uri
378             if (uri.startsWith("/")) {
379                 uri = request.getContextPath() + uri;
380             }
381 
382             response.sendRedirect(response.encodeRedirectURL(uri));
383         } else {
384             doForward(uri, request, response);
385         }
386     }
387 
388     // :FIXME: if Action.execute throws Exception, and Action.process has been
389     // removed, should the process* methods still throw IOException,
390     // ServletException?
391 
392     /***
393      * <P>Ask the specified <code>Action</code> instance to handle this
394      * request. Return the <code>ActionForward</code> instance (if any)
395      * returned by the called <code>Action</code> for further processing.
396      * </P>
397      *
398      * @param request  The servlet request we are processing
399      * @param response The servlet response we are creating
400      * @param action   The Action instance to be used
401      * @param form     The ActionForm instance to pass to this Action
402      * @param mapping  The ActionMapping instance to pass to this Action
403      * @return The <code>ActionForward</code> instance (if any) returned by
404      *         the called <code>Action</code>.
405      * @throws IOException      if an input/output error occurs
406      * @throws ServletException if a servlet exception occurs
407      */
408     protected ActionForward processActionPerform(HttpServletRequest request,
409         HttpServletResponse response, Action action, ActionForm form,
410         ActionMapping mapping)
411         throws IOException, ServletException {
412         try {
413             return (action.execute(mapping, form, request, response));
414         } catch (Exception e) {
415             return (processException(request, response, e, form, mapping));
416         }
417     }
418 
419     /***
420      * <p>Removes any <code>ActionMessages</code> object stored in the session
421      * under <code>Globals.MESSAGE_KEY</code> and <code>Globals.ERROR_KEY</code>
422      * if the messages' <code>isAccessed</code> method returns true.  This
423      * allows messages to be stored in the session, display one time, and be
424      * released here.</p>
425      *
426      * @param request  The servlet request we are processing.
427      * @param response The servlet response we are creating.
428      * @since Struts 1.2
429      */
430     protected void processCachedMessages(HttpServletRequest request,
431         HttpServletResponse response) {
432         HttpSession session = request.getSession(false);
433 
434         if (session == null) {
435             return;
436         }
437 
438         // Remove messages as needed
439         ActionMessages messages =
440             (ActionMessages) session.getAttribute(Globals.MESSAGE_KEY);
441 
442         if (messages != null) {
443             if (messages.isAccessed()) {
444                 session.removeAttribute(Globals.MESSAGE_KEY);
445             }
446         }
447 
448         // Remove error messages as needed
449         messages = (ActionMessages) session.getAttribute(Globals.ERROR_KEY);
450 
451         if (messages != null) {
452             if (messages.isAccessed()) {
453                 session.removeAttribute(Globals.ERROR_KEY);
454             }
455         }
456     }
457 
458     /***
459      * <p>Set the default content type (with optional character encoding) for
460      * all responses if requested.  <strong>NOTE</strong> - This header will
461      * be overridden automatically if a <code>RequestDispatcher.forward</code>
462      * call is ultimately invoked.</p>
463      *
464      * @param request  The servlet request we are processing
465      * @param response The servlet response we are creating
466      */
467     protected void processContent(HttpServletRequest request,
468         HttpServletResponse response) {
469         String contentType =
470             moduleConfig.getControllerConfig().getContentType();
471 
472         if (contentType != null) {
473             response.setContentType(contentType);
474         }
475     }
476 
477     /***
478      * <p>Ask our exception handler to handle the exception. Return the
479      * <code>ActionForward</code> instance (if any) returned by the called
480      * <code>ExceptionHandler</code>.</p>
481      *
482      * @param request   The servlet request we are processing
483      * @param response  The servlet response we are processing
484      * @param exception The exception being handled
485      * @param form      The ActionForm we are processing
486      * @param mapping   The ActionMapping we are using
487      * @return The <code>ActionForward</code> instance (if any) returned by
488      *         the called <code>ExceptionHandler</code>.
489      * @throws IOException      if an input/output error occurs
490      * @throws ServletException if a servlet exception occurs
491      */
492     protected ActionForward processException(HttpServletRequest request,
493         HttpServletResponse response, Exception exception, ActionForm form,
494         ActionMapping mapping)
495         throws IOException, ServletException {
496         // Is there a defined handler for this exception?
497         ExceptionConfig config = mapping.findException(exception.getClass());
498 
499         if (config == null) {
500             log.warn(getInternal().getMessage("unhandledException",
501                     exception.getClass()));
502 
503             if (exception instanceof IOException) {
504                 throw (IOException) exception;
505             } else if (exception instanceof ServletException) {
506                 throw (ServletException) exception;
507             } else {
508                 throw new ServletException(exception);
509             }
510         }
511 
512         // Use the configured exception handling
513         try {
514             ExceptionHandler handler =
515                 (ExceptionHandler) RequestUtils.applicationInstance(config
516                     .getHandler());
517 
518             return (handler.execute(exception, config, mapping, form, request,
519                 response));
520         } catch (Exception e) {
521             throw new ServletException(e);
522         }
523     }
524 
525     /***
526      * <p>Process a forward requested by this mapping (if any). Return
527      * <code>true</code> if standard processing should continue, or
528      * <code>false</code> if we have already handled this request.</p>
529      *
530      * @param request  The servlet request we are processing
531      * @param response The servlet response we are creating
532      * @param mapping  The ActionMapping we are using
533      * @return <code>true</code> to continue normal processing;
534      *         <code>false</code> if a response has been created.
535      * @throws IOException      if an input/output error occurs
536      * @throws ServletException if a servlet exception occurs
537      */
538     protected boolean processForward(HttpServletRequest request,
539         HttpServletResponse response, ActionMapping mapping)
540         throws IOException, ServletException {
541         // Are we going to processing this request?
542         String forward = mapping.getForward();
543 
544         if (forward == null) {
545             return (true);
546         }
547 
548         internalModuleRelativeForward(forward, request, response);
549 
550         return (false);
551     }
552 
553     /***
554      * <p>Process an include requested by this mapping (if any). Return
555      * <code>true</code> if standard processing should continue, or
556      * <code>false</code> if we have already handled this request.</p>
557      *
558      * @param request  The servlet request we are processing
559      * @param response The servlet response we are creating
560      * @param mapping  The ActionMapping we are using
561      * @return <code>true</code> to continue normal processing;
562      *         <code>false</code> if a response has been created.
563      * @throws IOException      if an input/output error occurs
564      * @throws ServletException if thrown by invoked methods
565      */
566     protected boolean processInclude(HttpServletRequest request,
567         HttpServletResponse response, ActionMapping mapping)
568         throws IOException, ServletException {
569         // Are we going to processing this request?
570         String include = mapping.getInclude();
571 
572         if (include == null) {
573             return (true);
574         }
575 
576         internalModuleRelativeInclude(include, request, response);
577 
578         return (false);
579     }
580 
581     /***
582      * <p>Automatically select a <code>Locale</code> for the current user, if
583      * requested. <strong>NOTE</strong> - configuring Locale selection will
584      * trigger the creation of a new <code>HttpSession</code> if
585      * necessary.</p>
586      *
587      * @param request  The servlet request we are processing
588      * @param response The servlet response we are creating
589      */
590     protected void processLocale(HttpServletRequest request,
591         HttpServletResponse response) {
592         // Are we configured to select the Locale automatically?
593         if (!moduleConfig.getControllerConfig().getLocale()) {
594             return;
595         }
596 
597         // Has a Locale already been selected?
598         HttpSession session = request.getSession();
599 
600         if (session.getAttribute(Globals.LOCALE_KEY) != null) {
601             return;
602         }
603 
604         // Use the Locale returned by the servlet container (if any)
605         Locale locale = request.getLocale();
606 
607         if (locale != null) {
608             if (log.isDebugEnabled()) {
609                 log.debug(" Setting user locale '" + locale + "'");
610             }
611 
612             session.setAttribute(Globals.LOCALE_KEY, locale);
613         }
614     }
615 
616     /***
617      * <p>Select the mapping used to process the selection path for this
618      * request. If no mapping can be identified, create an error response and
619      * return <code>null</code>.</p>
620      *
621      * @param request  The servlet request we are processing
622      * @param response The servlet response we are creating
623      * @param path     The portion of the request URI for selecting a mapping
624      * @return The mapping used to process the selection path for this
625      *         request.
626      * @throws IOException if an input/output error occurs
627      */
628     protected ActionMapping processMapping(HttpServletRequest request,
629         HttpServletResponse response, String path)
630         throws IOException {
631         // Is there a mapping for this path?
632         ActionMapping mapping =
633             (ActionMapping) moduleConfig.findActionConfig(path);
634 
635         // If a mapping is found, put it in the request and return it
636         if (mapping != null) {
637             request.setAttribute(Globals.MAPPING_KEY, mapping);
638 
639             return (mapping);
640         }
641 
642         // Locate the mapping for unknown paths (if any)
643         ActionConfig[] configs = moduleConfig.findActionConfigs();
644 
645         for (int i = 0; i < configs.length; i++) {
646             if (configs[i].getUnknown()) {
647                 mapping = (ActionMapping) configs[i];
648                 request.setAttribute(Globals.MAPPING_KEY, mapping);
649 
650                 return (mapping);
651             }
652         }
653 
654         // No mapping can be found to process this request
655         String msg = getInternal().getMessage("processInvalid");
656 
657         log.error(msg + " " + path);
658         response.sendError(HttpServletResponse.SC_NOT_FOUND, msg);
659 
660         return null;
661     }
662 
663     /***
664      * <p>If this is a multipart request, wrap it with a special wrapper.
665      * Otherwise, return the request unchanged.</p>
666      *
667      * @param request The HttpServletRequest we are processing
668      * @return A wrapped request, if the request is multipart; otherwise the
669      *         original request.
670      */
671     protected HttpServletRequest processMultipart(HttpServletRequest request) {
672         if (!"POST".equalsIgnoreCase(request.getMethod())) {
673             return (request);
674         }
675 
676         String contentType = request.getContentType();
677 
678         if ((contentType != null)
679             && contentType.startsWith("multipart/form-data")) {
680             return (new MultipartRequestWrapper(request));
681         } else {
682             return (request);
683         }
684     }
685 
686     /***
687      * <p>Set the no-cache headers for all responses, if requested.
688      * <strong>NOTE</strong> - This header will be overridden automatically if
689      * a <code>RequestDispatcher.forward</code> call is ultimately
690      * invoked.</p>
691      *
692      * @param request  The servlet request we are processing
693      * @param response The servlet response we are creating
694      */
695     protected void processNoCache(HttpServletRequest request,
696         HttpServletResponse response) {
697         if (moduleConfig.getControllerConfig().getNocache()) {
698             response.setHeader("Pragma", "No-cache");
699             response.setHeader("Cache-Control", "no-cache,no-store,max-age=0");
700             response.setDateHeader("Expires", 1);
701         }
702     }
703 
704     /***
705      * <p>Identify and return the path component (from the request URI) that
706      * we will use to select an <code>ActionMapping</code> with which to
707      * dispatch. If no such path can be identified, create an error response
708      * and return <code>null</code>.</p>
709      *
710      * @param request  The servlet request we are processing
711      * @param response The servlet response we are creating
712      * @return The path that will be used to select an action mapping.
713      * @throws IOException if an input/output error occurs
714      */
715     protected String processPath(HttpServletRequest request,
716         HttpServletResponse response)
717         throws IOException {
718         String path;
719 
720         // For prefix matching, match on the path info (if any)
721         path = (String) request.getAttribute(INCLUDE_PATH_INFO);
722 
723         if (path == null) {
724             path = request.getPathInfo();
725         }
726 
727         if ((path != null) && (path.length() > 0)) {
728             return (path);
729         }
730 
731         // For extension matching, strip the module prefix and extension
732         path = (String) request.getAttribute(INCLUDE_SERVLET_PATH);
733 
734         if (path == null) {
735             path = request.getServletPath();
736         }
737 
738         String prefix = moduleConfig.getPrefix();
739 
740         if (!path.startsWith(prefix)) {
741             String msg = getInternal().getMessage("processPath");
742 
743             log.error(msg + " " + request.getRequestURI());
744             response.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
745 
746             return null;
747         }
748 
749         path = path.substring(prefix.length());
750 
751         int slash = path.lastIndexOf("/");
752         int period = path.lastIndexOf(".");
753 
754         if ((period >= 0) && (period > slash)) {
755             path = path.substring(0, period);
756         }
757 
758         return (path);
759     }
760 
761     /***
762      * <p>Populate the properties of the specified <code>ActionForm</code>
763      * instance from the request parameters included with this request.  In
764      * addition, request attribute <code>Globals.CANCEL_KEY</code> will be set
765      * if the request was submitted with a button created by
766      * <code>CancelTag</code>.</p>
767      *
768      * @param request  The servlet request we are processing
769      * @param response The servlet response we are creating
770      * @param form     The ActionForm instance we are populating
771      * @param mapping  The ActionMapping we are using
772      * @throws ServletException if thrown by RequestUtils.populate()
773      */
774     protected void processPopulate(HttpServletRequest request,
775         HttpServletResponse response, ActionForm form, ActionMapping mapping)
776         throws ServletException {
777         if (form == null) {
778             return;
779         }
780 
781         // Populate the bean properties of this ActionForm instance
782         if (log.isDebugEnabled()) {
783             log.debug(" Populating bean properties from this request");
784         }
785 
786         form.setServlet(this.servlet);
787         form.reset(mapping, request);
788 
789         if (mapping.getMultipartClass() != null) {
790             request.setAttribute(Globals.MULTIPART_KEY,
791                 mapping.getMultipartClass());
792         }
793 
794         RequestUtils.populate(form, mapping.getPrefix(), mapping.getSuffix(),
795             request);
796 
797         // Set the cancellation request attribute if appropriate
798         if ((request.getParameter(Globals.CANCEL_PROPERTY) != null)
799             || (request.getParameter(Globals.CANCEL_PROPERTY_X) != null)) {
800             request.setAttribute(Globals.CANCEL_KEY, Boolean.TRUE);
801         }
802     }
803 
804     /***
805      * <p>General-purpose preprocessing hook that can be overridden as
806      * required by subclasses. Return <code>true</code> if you want standard
807      * processing to continue, or <code>false</code> if the response has
808      * already been completed. The default implementation does nothing.</p>
809      *
810      * @param request  The servlet request we are processing
811      * @param response The servlet response we are creating
812      * @return <code>true</code> to continue normal processing;
813      *         <code>false</code> if a response has been created.
814      */
815     protected boolean processPreprocess(HttpServletRequest request,
816         HttpServletResponse response) {
817         return (true);
818     }
819 
820     /***
821      * <p>If this action is protected by security roles, make sure that the
822      * current user possesses at least one of them.  Return <code>true</code>
823      * to continue normal processing, or <code>false</code> if an appropriate
824      * response has been created and processing should terminate.</p>
825      *
826      * @param request  The servlet request we are processing
827      * @param response The servlet response we are creating
828      * @param mapping  The mapping we are using
829      * @return <code>true</code> to continue normal processing;
830      *         <code>false</code> if a response has been created.
831      * @throws IOException      if an input/output error occurs
832      * @throws ServletException if a servlet exception occurs
833      */
834     protected boolean processRoles(HttpServletRequest request,
835         HttpServletResponse response, ActionMapping mapping)
836         throws IOException, ServletException {
837         // Is this action protected by role requirements?
838         String[] roles = mapping.getRoleNames();
839 
840         if ((roles == null) || (roles.length < 1)) {
841             return (true);
842         }
843 
844         // Check the current user against the list of required roles
845         for (int i = 0; i < roles.length; i++) {
846             if (request.isUserInRole(roles[i])) {
847                 if (log.isDebugEnabled()) {
848                     log.debug(" User '" + request.getRemoteUser()
849                         + "' has role '" + roles[i] + "', granting access");
850                 }
851 
852                 return (true);
853             }
854         }
855 
856         // The current user is not authorized for this action
857         if (log.isDebugEnabled()) {
858             log.debug(" User '" + request.getRemoteUser()
859                 + "' does not have any required role, denying access");
860         }
861 
862         response.sendError(HttpServletResponse.SC_FORBIDDEN,
863             getInternal().getMessage("notAuthorized", mapping.getPath()));
864 
865         return (false);
866     }
867 
868     /***
869      * <p>If this request was not cancelled, and the request's {@link
870      * ActionMapping} has not disabled validation, call the
871      * <code>validate</code> method of the specified {@link ActionForm}, and
872      * forward to the input path if there were any errors. Return
873      * <code>true</code> if we should continue processing, or
874      * <code>false</code> if we have already forwarded control back to the
875      * input form.</p>
876      *
877      * @param request  The servlet request we are processing
878      * @param response The servlet response we are creating
879      * @param form     The ActionForm instance we are populating
880      * @param mapping  The ActionMapping we are using
881      * @return <code>true</code> to continue normal processing;
882      *         <code>false</code> if a response has been created.
883      * @throws IOException      if an input/output error occurs
884      * @throws ServletException if a servlet exception occurs
885      * @throws InvalidCancelException if a cancellation is attempted
886      *         without the proper action configuration.
887      */
888     protected boolean processValidate(HttpServletRequest request,
889         HttpServletResponse response, ActionForm form, ActionMapping mapping)
890         throws IOException, ServletException, InvalidCancelException {
891         if (form == null) {
892             return (true);
893         }
894 
895         // Has validation been turned off for this mapping?
896         if (!mapping.getValidate()) {
897             return (true);
898         }
899 
900         // Was this request cancelled? If it has been, the mapping also
901         // needs to state whether the cancellation is permissable; otherwise
902         // the cancellation is considered to be a symptom of a programmer
903         // error or a spoof.
904         if (request.getAttribute(Globals.CANCEL_KEY) != null) {
905             if (mapping.getCancellable()) {
906                 if (log.isDebugEnabled()) {
907                     log.debug(" Cancelled transaction, skipping validation");
908                 }
909                 return (true);
910             } else {
911                 request.removeAttribute(Globals.CANCEL_KEY);
912                 throw new InvalidCancelException();
913             }
914         }
915 
916         // Call the form bean's validation method
917         if (log.isDebugEnabled()) {
918             log.debug(" Validating input form properties");
919         }
920 
921         ActionMessages errors = form.validate(mapping, request);
922 
923         if ((errors == null) || errors.isEmpty()) {
924             if (log.isTraceEnabled()) {
925                 log.trace("  No errors detected, accepting input");
926             }
927 
928             return (true);
929         }
930 
931         // Special handling for multipart request
932         if (form.getMultipartRequestHandler() != null) {
933             if (log.isTraceEnabled()) {
934                 log.trace("  Rolling back multipart request");
935             }
936 
937             form.getMultipartRequestHandler().rollback();
938         }
939 
940         // Was an input path (or forward) specified for this mapping?
941         String input = mapping.getInput();
942 
943         if (input == null) {
944             if (log.isTraceEnabled()) {
945                 log.trace("  Validation failed but no input form available");
946             }
947 
948             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
949                 getInternal().getMessage("noInput", mapping.getPath()));
950 
951             return (false);
952         }
953 
954         // Save our error messages and return to the input form if possible
955         if (log.isDebugEnabled()) {
956             log.debug(" Validation failed, returning to '" + input + "'");
957         }
958 
959         request.setAttribute(Globals.ERROR_KEY, errors);
960 
961         if (moduleConfig.getControllerConfig().getInputForward()) {
962             ForwardConfig forward = mapping.findForward(input);
963 
964             processForwardConfig(request, response, forward);
965         } else {
966             internalModuleRelativeForward(input, request, response);
967         }
968 
969         return (false);
970     }
971 
972     /***
973      * <p>Do a module relative forward to specified URI using request
974      * dispatcher. URI is relative to the current module. The real URI is
975      * compute by prefixing the module name.</p> <p>This method is used
976      * internally and is not part of the public API. It is advised to not use
977      * it in subclasses. </p>
978      *
979      * @param uri      Module-relative URI to forward to
980      * @param request  Current page request
981      * @param response Current page response
982      * @throws IOException      if an input/output error occurs
983      * @throws ServletException if a servlet exception occurs
984      * @since Struts 1.1
985      */
986     protected void internalModuleRelativeForward(String uri,
987         HttpServletRequest request, HttpServletResponse response)
988         throws IOException, ServletException {
989         // Construct a request dispatcher for the specified path
990         uri = moduleConfig.getPrefix() + uri;
991 
992         // Delegate the processing of this request
993         // :FIXME: - exception handling?
994         if (log.isDebugEnabled()) {
995             log.debug(" Delegating via forward to '" + uri + "'");
996         }
997 
998         doForward(uri, request, response);
999     }
1000 
1001     /***
1002      * <p>Do a module relative include to specified URI using request
1003      * dispatcher. URI is relative to the current module. The real URI is
1004      * compute by prefixing the module name.</p> <p>This method is used
1005      * internally and is not part of the public API. It is advised to not use
1006      * it in subclasses.</p>
1007      *
1008      * @param uri      Module-relative URI to include
1009      * @param request  Current page request
1010      * @param response Current page response
1011      * @throws IOException      if an input/output error occurs
1012      * @throws ServletException if a servlet exception occurs
1013      * @since Struts 1.1
1014      */
1015     protected void internalModuleRelativeInclude(String uri,
1016         HttpServletRequest request, HttpServletResponse response)
1017         throws IOException, ServletException {
1018         // Construct a request dispatcher for the specified path
1019         uri = moduleConfig.getPrefix() + uri;
1020 
1021         // Delegate the processing of this request
1022         // FIXME - exception handling?
1023         if (log.isDebugEnabled()) {
1024             log.debug(" Delegating via include to '" + uri + "'");
1025         }
1026 
1027         doInclude(uri, request, response);
1028     }
1029 
1030     /***
1031      * <p>Do a forward to specified URI using a <code>RequestDispatcher</code>.
1032      * This method is used by all internal method needing to do a
1033      * forward.</p>
1034      *
1035      * @param uri      Context-relative URI to forward to
1036      * @param request  Current page request
1037      * @param response Current page response
1038      * @throws IOException      if an input/output error occurs
1039      * @throws ServletException if a servlet exception occurs
1040      * @since Struts 1.1
1041      */
1042     protected void doForward(String uri, HttpServletRequest request,
1043         HttpServletResponse response)
1044         throws IOException, ServletException {
1045         RequestDispatcher rd = getServletContext().getRequestDispatcher(uri);
1046 
1047         if (rd == null) {
1048             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1049                 getInternal().getMessage("requestDispatcher", uri));
1050 
1051             return;
1052         }
1053 
1054         rd.forward(request, response);
1055     }
1056 
1057     /***
1058      * <p>Do an include of specified URI using a <code>RequestDispatcher</code>.
1059      * This method is used by all internal method needing to do an
1060      * include.</p>
1061      *
1062      * @param uri      Context-relative URI to include
1063      * @param request  Current page request
1064      * @param response Current page response
1065      * @throws IOException      if an input/output error occurs
1066      * @throws ServletException if a servlet exception occurs
1067      * @since Struts 1.1
1068      */
1069     protected void doInclude(String uri, HttpServletRequest request,
1070         HttpServletResponse response)
1071         throws IOException, ServletException {
1072         RequestDispatcher rd = getServletContext().getRequestDispatcher(uri);
1073 
1074         if (rd == null) {
1075             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1076                 getInternal().getMessage("requestDispatcher", uri));
1077 
1078             return;
1079         }
1080 
1081         rd.include(request, response);
1082     }
1083 
1084     // -------------------------------------------------------- Support Methods
1085 
1086     /***
1087      * <p>Return the <code>MessageResources</code> instance containing our
1088      * internal message strings.</p>
1089      *
1090      * @return The <code>MessageResources</code> instance containing our
1091      *         internal message strings.
1092      */
1093     protected MessageResources getInternal() {
1094         return (servlet.getInternal());
1095     }
1096 
1097     /***
1098      * <p>Return the <code>ServletContext</code> for the web application in
1099      * which we are running.</p>
1100      *
1101      * @return The <code>ServletContext</code> for the web application.
1102      */
1103     protected ServletContext getServletContext() {
1104         return (servlet.getServletContext());
1105     }
1106 }