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