Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/apache/struts/action/RequestProcessor.java


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