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}