View Javadoc

    1   /*
    2    * $Id: RequestProcessor.java 680350 2008-07-28 13:32:29Z niallp $
    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: 680350 $ $Date: 2008-07-28 08:32:29 -0500 (Mon, 28 Jul 2008) $
    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             if (instance.getServlet() == null) {
    298                 instance.setServlet(this.servlet);
    299             }
    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         // Set per request the original path for postback forms
    745         if (request.getAttribute(Globals.ORIGINAL_URI_KEY) == null) {
    746             request.setAttribute(Globals.ORIGINAL_URI_KEY, request.getServletPath());
    747         }
    748
    749         // For prefix matching, match on the path info (if any)
    750         path = (String) request.getAttribute(INCLUDE_PATH_INFO);
    751
    752         if (path == null) {
    753             path = request.getPathInfo();
    754         }
    755
    756         if ((path != null) && (path.length() > 0)) {
    757             return (path);
    758         }
    759
    760         // For extension matching, strip the module prefix and extension
    761         path = (String) request.getAttribute(INCLUDE_SERVLET_PATH);
    762
    763         if (path == null) {
    764             path = request.getServletPath();
    765         }
    766
    767         String prefix = moduleConfig.getPrefix();
    768
    769         if (!path.startsWith(prefix)) {
    770             String msg = getInternal().getMessage("processPath");
    771
    772             log.error(msg + " " + request.getRequestURI());
    773             response.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
    774
    775             return null;
    776         }
    777
    778         path = path.substring(prefix.length());
    779
    780         int slash = path.lastIndexOf("/");
    781         int period = path.lastIndexOf(".");
    782
    783         if ((period >= 0) && (period > slash)) {
    784             path = path.substring(0, period);
    785         }
    786
    787         return (path);
    788     }
    789
    790     /**
    791      * <p>Populate the properties of the specified <code>ActionForm</code>
    792      * instance from the request parameters included with this request.  In
    793      * addition, request attribute <code>Globals.CANCEL_KEY</code> will be set
    794      * if the request was submitted with a button created by
    795      * <code>CancelTag</code>.</p>
    796      *
    797      * @param request  The servlet request we are processing
    798      * @param response The servlet response we are creating
    799      * @param form     The ActionForm instance we are populating
    800      * @param mapping  The ActionMapping we are using
    801      * @throws ServletException if thrown by RequestUtils.populate()
    802      */
    803     protected void processPopulate(HttpServletRequest request,
    804         HttpServletResponse response, ActionForm form, ActionMapping mapping)
    805         throws ServletException {
    806         if (form == null) {
    807             return;
    808         }
    809
    810         // Populate the bean properties of this ActionForm instance
    811         if (log.isDebugEnabled()) {
    812             log.debug(" Populating bean properties from this request");
    813         }
    814
    815         form.setServlet(this.servlet);
    816         form.reset(mapping, request);
    817
    818         if (mapping.getMultipartClass() != null) {
    819             request.setAttribute(Globals.MULTIPART_KEY,
    820                 mapping.getMultipartClass());
    821         }
    822
    823         RequestUtils.populate(form, mapping.getPrefix(), mapping.getSuffix(),
    824             request);
    825
    826         // Set the cancellation request attribute if appropriate
    827         if ((request.getParameter(Globals.CANCEL_PROPERTY) != null)
    828             || (request.getParameter(Globals.CANCEL_PROPERTY_X) != null)) {
    829             request.setAttribute(Globals.CANCEL_KEY, Boolean.TRUE);
    830         }
    831     }
    832
    833     /**
    834      * <p>General-purpose preprocessing hook that can be overridden as
    835      * required by subclasses. Return <code>true</code> if you want standard
    836      * processing to continue, or <code>false</code> if the response has
    837      * already been completed. The default implementation does nothing.</p>
    838      *
    839      * @param request  The servlet request we are processing
    840      * @param response The servlet response we are creating
    841      * @return <code>true</code> to continue normal processing;
    842      *         <code>false</code> if a response has been created.
    843      */
    844     protected boolean processPreprocess(HttpServletRequest request,
    845         HttpServletResponse response) {
    846         return (true);
    847     }
    848
    849     /**
    850      * <p>If this action is protected by security roles, make sure that the
    851      * current user possesses at least one of them.  Return <code>true</code>
    852      * to continue normal processing, or <code>false</code> if an appropriate
    853      * response has been created and processing should terminate.</p>
    854      *
    855      * @param request  The servlet request we are processing
    856      * @param response The servlet response we are creating
    857      * @param mapping  The mapping we are using
    858      * @return <code>true</code> to continue normal processing;
    859      *         <code>false</code> if a response has been created.
    860      * @throws IOException      if an input/output error occurs
    861      * @throws ServletException if a servlet exception occurs
    862      */
    863     protected boolean processRoles(HttpServletRequest request,
    864         HttpServletResponse response, ActionMapping mapping)
    865         throws IOException, ServletException {
    866         // Is this action protected by role requirements?
    867         String[] roles = mapping.getRoleNames();
    868
    869         if ((roles == null) || (roles.length < 1)) {
    870             return (true);
    871         }
    872
    873         // Check the current user against the list of required roles
    874         for (int i = 0; i < roles.length; i++) {
    875             if (request.isUserInRole(roles[i])) {
    876                 if (log.isDebugEnabled()) {
    877                     log.debug(" User '" + request.getRemoteUser()
    878                         + "' has role '" + roles[i] + "', granting access");
    879                 }
    880
    881                 return (true);
    882             }
    883         }
    884
    885         // The current user is not authorized for this action
    886         if (log.isDebugEnabled()) {
    887             log.debug(" User '" + request.getRemoteUser()
    888                 + "' does not have any required role, denying access");
    889         }
    890
    891         response.sendError(HttpServletResponse.SC_FORBIDDEN,
    892             getInternal().getMessage("notAuthorized", mapping.getPath()));
    893
    894         return (false);
    895     }
    896
    897     /**
    898      * <p>If this request was not cancelled, and the request's {@link
    899      * ActionMapping} has not disabled validation, call the
    900      * <code>validate</code> method of the specified {@link ActionForm}, and
    901      * forward to the input path if there were any errors. Return
    902      * <code>true</code> if we should continue processing, or
    903      * <code>false</code> if we have already forwarded control back to the
    904      * input form.</p>
    905      *
    906      * @param request  The servlet request we are processing
    907      * @param response The servlet response we are creating
    908      * @param form     The ActionForm instance we are populating
    909      * @param mapping  The ActionMapping we are using
    910      * @return <code>true</code> to continue normal processing;
    911      *         <code>false</code> if a response has been created.
    912      * @throws IOException      if an input/output error occurs
    913      * @throws ServletException if a servlet exception occurs
    914      * @throws InvalidCancelException if a cancellation is attempted
    915      *         without the proper action configuration.
    916      */
    917     protected boolean processValidate(HttpServletRequest request,
    918         HttpServletResponse response, ActionForm form, ActionMapping mapping)
    919         throws IOException, ServletException, InvalidCancelException {
    920         if (form == null) {
    921             return (true);
    922         }
    923
    924         // Has validation been turned off for this mapping?
    925         if (!mapping.getValidate()) {
    926             return (true);
    927         }
    928
    929         // Was this request cancelled? If it has been, the mapping also
    930         // needs to state whether the cancellation is permissable; otherwise
    931         // the cancellation is considered to be a symptom of a programmer
    932         // error or a spoof.
    933         if (request.getAttribute(Globals.CANCEL_KEY) != null) {
    934             if (mapping.getCancellable()) {
    935                 if (log.isDebugEnabled()) {
    936                     log.debug(" Cancelled transaction, skipping validation");
    937                 }
    938                 return (true);
    939             } else {
    940                 request.removeAttribute(Globals.CANCEL_KEY);
    941                 throw new InvalidCancelException();
    942             }
    943         }
    944
    945         // Call the form bean's validation method
    946         if (log.isDebugEnabled()) {
    947             log.debug(" Validating input form properties");
    948         }
    949
    950         ActionMessages errors = form.validate(mapping, request);
    951
    952         if ((errors == null) || errors.isEmpty()) {
    953             if (log.isTraceEnabled()) {
    954                 log.trace("  No errors detected, accepting input");
    955             }
    956
    957             return (true);
    958         }
    959
    960         // Special handling for multipart request
    961         if (form.getMultipartRequestHandler() != null) {
    962             if (log.isTraceEnabled()) {
    963                 log.trace("  Rolling back multipart request");
    964             }
    965
    966             form.getMultipartRequestHandler().rollback();
    967         }
    968
    969         // Was an input path (or forward) specified for this mapping?
    970         String input = mapping.getInput();
    971
    972         if (input == null) {
    973             if (log.isTraceEnabled()) {
    974                 log.trace("  Validation failed but no input form available");
    975             }
    976
    977             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
    978                 getInternal().getMessage("noInput", mapping.getPath()));
    979
    980             return (false);
    981         }
    982
    983         // Save our error messages and return to the input form if possible
    984         if (log.isDebugEnabled()) {
    985             log.debug(" Validation failed, returning to '" + input + "'");
    986         }
    987
    988         request.setAttribute(Globals.ERROR_KEY, errors);
    989
    990         if (moduleConfig.getControllerConfig().getInputForward()) {
    991             ForwardConfig forward = mapping.findForward(input);
    992
    993             processForwardConfig(request, response, forward);
    994         } else {
    995             internalModuleRelativeForward(input, request, response);
    996         }
    997
    998         return (false);
    999     }
    1000
    1001     /**
    1002      * <p>Do a module relative forward to specified URI using request
    1003      * dispatcher. URI is relative to the current module. The real URI is
    1004      * compute by prefixing the module name.</p> <p>This method is used
    1005      * internally and is not part of the public API. It is advised to not use
    1006      * it in subclasses. </p>
    1007      *
    1008      * @param uri      Module-relative URI to forward to
    1009      * @param request  Current page request
    1010      * @param response Current page response
    1011      * @throws IOException      if an input/output error occurs
    1012      * @throws ServletException if a servlet exception occurs
    1013      * @since Struts 1.1
    1014      */
    1015     protected void internalModuleRelativeForward(String uri,
    1016         HttpServletRequest request, HttpServletResponse response)
    1017         throws IOException, ServletException {
    1018         // Construct a request dispatcher for the specified path
    1019         uri = moduleConfig.getPrefix() + uri;
    1020
    1021         // Delegate the processing of this request
    1022         // :FIXME: - exception handling?
    1023         if (log.isDebugEnabled()) {
    1024             log.debug(" Delegating via forward to '" + uri + "'");
    1025         }
    1026
    1027         doForward(uri, request, response);
    1028     }
    1029
    1030     /**
    1031      * <p>Do a module relative include to specified URI using request
    1032      * dispatcher. URI is relative to the current module. The real URI is
    1033      * compute by prefixing the module name.</p> <p>This method is used
    1034      * internally and is not part of the public API. It is advised to not use
    1035      * it in subclasses.</p>
    1036      *
    1037      * @param uri      Module-relative URI to include
    1038      * @param request  Current page request
    1039      * @param response Current page response
    1040      * @throws IOException      if an input/output error occurs
    1041      * @throws ServletException if a servlet exception occurs
    1042      * @since Struts 1.1
    1043      */
    1044     protected void internalModuleRelativeInclude(String uri,
    1045         HttpServletRequest request, HttpServletResponse response)
    1046         throws IOException, ServletException {
    1047         // Construct a request dispatcher for the specified path
    1048         uri = moduleConfig.getPrefix() + uri;
    1049
    1050         // Delegate the processing of this request
    1051         // FIXME - exception handling?
    1052         if (log.isDebugEnabled()) {
    1053             log.debug(" Delegating via include to '" + uri + "'");
    1054         }
    1055
    1056         doInclude(uri, request, response);
    1057     }
    1058
    1059     /**
    1060      * <p>Do a forward to specified URI using a <code>RequestDispatcher</code>.
    1061      * This method is used by all internal method needing to do a
    1062      * forward.</p>
    1063      *
    1064      * @param uri      Context-relative URI to forward to
    1065      * @param request  Current page request
    1066      * @param response Current page response
    1067      * @throws IOException      if an input/output error occurs
    1068      * @throws ServletException if a servlet exception occurs
    1069      * @since Struts 1.1
    1070      */
    1071     protected void doForward(String uri, HttpServletRequest request,
    1072         HttpServletResponse response)
    1073         throws IOException, ServletException {
    1074         RequestDispatcher rd = getServletContext().getRequestDispatcher(uri);
    1075
    1076         if (rd == null) {
    1077             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
    1078                 getInternal().getMessage("requestDispatcher", uri));
    1079
    1080             return;
    1081         }
    1082
    1083         rd.forward(request, response);
    1084     }
    1085
    1086     /**
    1087      * <p>Do an include of specified URI using a <code>RequestDispatcher</code>.
    1088      * This method is used by all internal method needing to do an
    1089      * include.</p>
    1090      *
    1091      * @param uri      Context-relative URI to include
    1092      * @param request  Current page request
    1093      * @param response Current page response
    1094      * @throws IOException      if an input/output error occurs
    1095      * @throws ServletException if a servlet exception occurs
    1096      * @since Struts 1.1
    1097      */
    1098     protected void doInclude(String uri, HttpServletRequest request,
    1099         HttpServletResponse response)
    1100         throws IOException, ServletException {
    1101         RequestDispatcher rd = getServletContext().getRequestDispatcher(uri);
    1102
    1103         if (rd == null) {
    1104             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
    1105                 getInternal().getMessage("requestDispatcher", uri));
    1106
    1107             return;
    1108         }
    1109
    1110         rd.include(request, response);
    1111     }
    1112
    1113     // -------------------------------------------------------- Support Methods
    1114
    1115     /**
    1116      * <p>Return the <code>MessageResources</code> instance containing our
    1117      * internal message strings.</p>
    1118      *
    1119      * @return The <code>MessageResources</code> instance containing our
    1120      *         internal message strings.
    1121      */
    1122     protected MessageResources getInternal() {
    1123         return (servlet.getInternal());
    1124     }
    1125
    1126     /**
    1127      * <p>Return the <code>ServletContext</code> for the web application in
    1128      * which we are running.</p>
    1129      *
    1130      * @return The <code>ServletContext</code> for the web application.
    1131      */
    1132     protected ServletContext getServletContext() {
    1133         return (servlet.getServletContext());
    1134     }
    1135 }