View Javadoc

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