View Javadoc

    1   /*
    2    * $Id: FacesTilesRequestProcessor.java 473326 2006-11-10 12:58:06Z 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
    22  package org.apache.struts.faces.application;
    23
    24
    25  import java.io.IOException;
    26  import javax.faces.FactoryFinder;
    27  import javax.faces.application.ViewHandler;
    28  import javax.faces.component.UICommand;
    29  import javax.faces.component.UIComponent;
    30  import javax.faces.context.FacesContext;
    31  import javax.faces.context.FacesContextFactory;
    32  import javax.faces.event.ActionEvent;
    33  import javax.faces.lifecycle.Lifecycle;
    34  import javax.faces.lifecycle.LifecycleFactory;
    35  import javax.servlet.ServletException;
    36  import javax.servlet.http.HttpServletRequest;
    37  import javax.servlet.http.HttpServletResponse;
    38  import org.apache.commons.logging.Log;
    39  import org.apache.commons.logging.LogFactory;
    40  import org.apache.struts.Globals;
    41  import org.apache.struts.action.Action;
    42  import org.apache.struts.action.ActionForm;
    43  import org.apache.struts.action.ActionForward;
    44  import org.apache.struts.action.ActionMapping;
    45  import org.apache.struts.action.InvalidCancelException;
    46  import org.apache.struts.config.FormBeanConfig;
    47  import org.apache.struts.config.ForwardConfig;
    48  import org.apache.struts.faces.Constants;
    49  import org.apache.struts.faces.component.FormComponent;
    50  import org.apache.struts.tiles.TilesRequestProcessor;
    51
    52
    53  /**
    54   * <p>Concrete implementation of <code>RequestProcessor</code> that
    55   * implements the standard Struts request processing lifecycle on a
    56   * request that was received as an <code>ActionEvent</code> by our
    57   * associated <code>ActionListener</code>.  It replaces the request processor
    58   * instance normally configured by Struts+Tiles, so it must support non-Faces
    59   * requests as well.</p>
    60   *
    61   * @version $Rev: 473326 $ $Date: 2006-11-10 06:58:06 -0600 (Fri, 10 Nov 2006) $
    62   */
    63
    64  public class FacesTilesRequestProcessor extends TilesRequestProcessor {
    65
    66
    67      // ------------------------------------------------------ Instance Variables
    68
    69
    70      /**
    71       * <p>The log instance for this class.</p>
    72       */
    73      protected static Log log =
    74          LogFactory.getLog(FacesTilesRequestProcessor.class);
    75
    76      /**
    77       * <p>The lifecycle id.</p>
    78       */
    79      public static final String LIFECYCLE_ID_ATTR = "javax.faces.LIFECYCLE_ID";
    80
    81
    82      // ------------------------------------------------------- Protected Methods
    83
    84
    85      /**
    86       * <p>Set up a Faces Request if we are not already processing one.  Next,
    87       * create a new view if the specified <code>uri</code> is different from
    88       * the current view identifier.  Finally, cause the new view to be
    89       * rendered, and call <code>FacesContext.responseComplete()</code> to
    90       * indicate that this has already been done.</p>
    91       *
    92       * @param uri Context-relative path to forward to
    93       * @param request Current page request
    94       * @param response Current page response
    95       *
    96       * @exception IOException if an input/output error occurs
    97       * @exception ServletException if a servlet error occurs
    98       */
    99      protected void doForward(String uri,
    100                              HttpServletRequest request,
    101                              HttpServletResponse response)
    102         throws IOException, ServletException {
    103
    104         if (log.isDebugEnabled()) {
    105             log.debug("doForward(" + uri + ")");
    106         }
    107
    108         // Remove the current ActionEvent (if any)
    109         request.removeAttribute(Constants.ACTION_EVENT_KEY);
    110
    111         // Process a Struts controller request normally
    112         if (isStrutsRequest(uri)) {
    113             if (response.isCommitted()) {
    114                 if (log.isTraceEnabled()) {
    115                     log.trace("  super.doInclude(" + uri + ")");
    116                 }
    117                 super.doInclude(uri, request, response);
    118             } else {
    119                 if (log.isTraceEnabled()) {
    120                     log.trace("  super.doForward(" + uri + ")");
    121                 }
    122                 super.doForward(uri, request, response);
    123             }
    124             return;
    125         }
    126
    127         // Create a FacesContext for this request if necessary
    128         LifecycleFactory lf = (LifecycleFactory)
    129             FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
    130         Lifecycle lifecycle =
    131             lf.getLifecycle(getLifecycleId());
    132         boolean created = false;
    133         FacesContext context = FacesContext.getCurrentInstance();
    134         if (context == null) {
    135             if (log.isTraceEnabled()) {
    136                 log.trace("  Creating new FacesContext for '" + uri + "'");
    137             }
    138             created = true;
    139             FacesContextFactory fcf = (FacesContextFactory)
    140                 FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
    141             context = fcf.getFacesContext(servlet.getServletContext(),
    142                                           request, response, lifecycle);
    143         }
    144
    145         // Create a new view root
    146         ViewHandler vh = context.getApplication().getViewHandler();
    147         if (log.isTraceEnabled()) {
    148             log.trace("  Creating new view for '" + uri + "'");
    149         }
    150         context.setViewRoot(vh.createView(context, uri));
    151
    152         // Cause the view to be rendered
    153         if (log.isTraceEnabled()) {
    154             log.trace("  Rendering view for '" + uri + "'");
    155         }
    156         try {
    157             lifecycle.render(context);
    158         } finally {
    159             if (created) {
    160                 if (log.isTraceEnabled()) {
    161                     log.trace("  Releasing context for '" + uri + "'");
    162                 }
    163                 context.release();
    164             } else {
    165                 if (log.isTraceEnabled()) {
    166                     log.trace("  Rendering completed");
    167                 }
    168             }
    169         }
    170
    171     }
    172
    173
    174     // Override default processing to provide logging
    175     protected void internalModuleRelativeForward
    176         (String uri, HttpServletRequest request, HttpServletResponse response)
    177         throws IOException, ServletException {
    178
    179         if (log.isTraceEnabled()) {
    180             log.trace("Performing internal module relative forward to '" +
    181                       uri + "'");
    182         }
    183         super.internalModuleRelativeForward(uri, request, response);
    184
    185     }
    186
    187
    188     // Override default processing to provide logging
    189     protected Action processActionCreate(HttpServletRequest request,
    190                                          HttpServletResponse response,
    191                                          ActionMapping mapping)
    192         throws IOException {
    193
    194         if (log.isTraceEnabled()) {
    195             log.trace("Performing standard action create");
    196         }
    197         Action result = super.processActionCreate(request, response, mapping);
    198         if (log.isDebugEnabled()) {
    199             log.debug("Standard action create returned " +
    200                       result.getClass().getName() + " instance");
    201         }
    202         return (result);
    203
    204     }
    205
    206
    207     // Override default processing to provide logging
    208     protected ActionForm processActionForm(HttpServletRequest request,
    209                                            HttpServletResponse response,
    210                                            ActionMapping mapping) {
    211         if (log.isTraceEnabled()) {
    212             log.trace("Performing standard action form processing");
    213             String attribute = mapping.getAttribute();
    214             if (attribute != null) {
    215                 String name = mapping.getName();
    216                 FormBeanConfig fbc = moduleConfig.findFormBeanConfig(name);
    217                 if (fbc != null) {
    218                     if ("request".equals(mapping.getScope())) {
    219                         log.trace("  Bean in request scope = " +
    220                                   request.getAttribute(attribute));
    221                     } else {
    222                         log.trace("  Bean in session scope = " +
    223                                   request.getSession().getAttribute(attribute));
    224                     }
    225                 } else {
    226                     log.trace("  No FormBeanConfig for '" + name + "'");
    227                 }
    228             } else {
    229                 log.trace("  No form bean for this action");
    230             }
    231         }
    232         ActionForm result =
    233             super.processActionForm(request, response, mapping);
    234         if (log.isDebugEnabled()) {
    235             log.debug("Standard action form returned " +
    236                       result);
    237         }
    238         return (result);
    239
    240
    241     }
    242
    243
    244     // Override default processing to provide logging
    245     protected ActionForward processActionPerform(HttpServletRequest request,
    246                                                  HttpServletResponse response,
    247                                                  Action action,
    248                                                  ActionForm form,
    249                                                  ActionMapping mapping)
    250         throws IOException, ServletException {
    251
    252         if (log.isTraceEnabled()) {
    253             log.trace("Performing standard action perform");
    254         }
    255         ActionForward result =
    256             super.processActionPerform(request, response, action,
    257                                        form, mapping);
    258         if (log.isDebugEnabled()) {
    259             log.debug("Standard action perform returned " +
    260                       (result == null ? "NULL" :
    261                       result.getPath()) + " forward path");
    262         }
    263         return (result);
    264
    265     }
    266
    267
    268     // Override default processing to provide logging
    269     protected boolean processForward(HttpServletRequest request,
    270                                      HttpServletResponse response,
    271                                      ActionMapping mapping)
    272         throws IOException, ServletException {
    273
    274         if (log.isTraceEnabled()) {
    275             log.trace("Performing standard forward handling");
    276         }
    277         boolean result = super.processForward
    278             (request, response, mapping);
    279         if (log.isDebugEnabled()) {
    280             log.debug("Standard forward handling returned " + result);
    281         }
    282         return (result);
    283
    284     }
    285
    286
    287     // Override default processing to provide logging
    288     protected void processForwardConfig(HttpServletRequest request,
    289                                         HttpServletResponse response,
    290                                         ForwardConfig forward)
    291         throws IOException, ServletException {
    292
    293         if (log.isTraceEnabled()) {
    294             log.trace("Performing standard forward config handling");
    295         }
    296         super.processForwardConfig(request, response, forward);
    297         if (log.isDebugEnabled()) {
    298             log.debug("Standard forward config handling completed");
    299         }
    300
    301     }
    302
    303
    304     // Override default processing to provide logging
    305     protected boolean processInclude(HttpServletRequest request,
    306                                      HttpServletResponse response,
    307                                      ActionMapping mapping)
    308         throws IOException, ServletException {
    309
    310         if (log.isTraceEnabled()) {
    311             log.trace("Performing standard include handling");
    312         }
    313         boolean result = super.processInclude
    314             (request, response, mapping);
    315         if (log.isDebugEnabled()) {
    316             log.debug("Standard include handling returned " + result);
    317         }
    318         return (result);
    319
    320     }
    321
    322
    323     /**
    324      * <p>Identify and return the path component (from the request URI for a
    325      * non-Faces request, or from the form event for a Faces request)
    326      * that we will use to select an ActionMapping to dispatch with.
    327      * If no such path can be identified, create an error response and return
    328      * <code>null</code>.</p>
    329      *
    330      * @param request The servlet request we are processing
    331      * @param response The servlet response we are creating
    332      *
    333      * @exception IOException if an input/output error occurs
    334      */
    335     protected String processPath(HttpServletRequest request,
    336                                  HttpServletResponse response)
    337         throws IOException {
    338
    339         // Are we processing a Faces request?
    340         ActionEvent event = (ActionEvent)
    341             request.getAttribute(Constants.ACTION_EVENT_KEY);
    342
    343         // Handle non-Faces requests in the usual way
    344         if (event == null) {
    345             if (log.isTraceEnabled()) {
    346                 log.trace("Performing standard processPath() processing");
    347             }
    348             return (super.processPath(request, response));
    349         }
    350
    351         // Calculate the path from the form name
    352         UIComponent component = event.getComponent();
    353         if (log.isTraceEnabled()) {
    354             log.trace("Locating form parent for command component " +
    355                       event.getComponent());
    356         }
    357         while (!(component instanceof FormComponent)) {
    358             component = component.getParent();
    359             if (component == null) {
    360                 log.warn("Command component was not nested in a Struts form!");
    361                 return (null);
    362             }
    363         }
    364         if (log.isTraceEnabled()) {
    365             log.trace("Returning selected path of " +
    366                       ((FormComponent) component).getAction());
    367         }
    368         return (((FormComponent) component).getAction());
    369
    370     }
    371
    372
    373     /**
    374      * <p>Populate the properties of the specified <code>ActionForm</code>
    375      * instance from the request parameters included with this request,
    376      * <strong>IF</strong> this is a non-Faces request.  For a Faces request,
    377      * this will have already been done by the <em>Update Model Values</em>
    378      * phase of the request processing lifecycle, so all we have to do is
    379      * recognize whether the request was cancelled or not.</p>
    380      *
    381      * @param request The servlet request we are processing
    382      * @param response The servlet response we are creating
    383      * @param form The ActionForm instance we are populating
    384      * @param mapping The ActionMapping we are using
    385      *
    386      * @exception ServletException if thrown by RequestUtils.populate()
    387      */
    388     protected void processPopulate(HttpServletRequest request,
    389                                    HttpServletResponse response,
    390                                    ActionForm form,
    391                                    ActionMapping mapping)
    392         throws ServletException {
    393
    394         // Are we processing a Faces request?
    395         ActionEvent event = (ActionEvent)
    396             request.getAttribute(Constants.ACTION_EVENT_KEY);
    397
    398         // Handle non-Faces requests in the usual way
    399         if (event == null) {
    400             if (log.isTraceEnabled()) {
    401                 log.trace("Performing standard processPopulate() processing");
    402             }
    403             super.processPopulate(request, response, form, mapping);
    404             return;
    405         }
    406
    407         // Faces Requests require no processing for form bean population
    408         // so we need only check for the cancellation command name
    409         if (log.isTraceEnabled()) {
    410             log.trace("Faces request, so no processPopulate() processing");
    411         }
    412         UIComponent source = event.getComponent();
    413         if (source instanceof UICommand) {
    414             if ("cancel".equals(((UICommand) source).getId())) {
    415                 if (log.isTraceEnabled()) {
    416                     log.trace("Faces request with cancel button pressed");
    417                 }
    418                 request.setAttribute(Globals.CANCEL_KEY, Boolean.TRUE);
    419             }
    420         }
    421
    422     }
    423
    424
    425     // Override default processing to provide logging
    426     protected boolean processValidate(HttpServletRequest request,
    427                                       HttpServletResponse response,
    428                                       ActionForm form,
    429                                       ActionMapping mapping)
    430         throws IOException, ServletException, InvalidCancelException {
    431
    432         if (log.isTraceEnabled()) {
    433             log.trace("Performing standard validation");
    434         }
    435         boolean result = super.processValidate
    436             (request, response, form, mapping);
    437         if (log.isDebugEnabled()) {
    438             log.debug("Standard validation processing returned " + result);
    439         }
    440         return (result);
    441
    442     }
    443
    444
    445     // --------------------------------------------------------- Private Methods
    446
    447
    448     /**
    449      * <p>Return the used Lifecycle ID (default or custom).</p>
    450      */
    451     private String getLifecycleId()
    452     {
    453         String lifecycleId = this.servlet.getServletContext().getInitParameter(LIFECYCLE_ID_ATTR);
    454         return lifecycleId != null ? lifecycleId : LifecycleFactory.DEFAULT_LIFECYCLE;
    455     }
    456
    457     /**
    458      * <p>Return <code>true</code> if the specified context-relative URI
    459      * specifies a request to be processed by the Struts controller servlet.</p>
    460      *
    461      * @param uri URI to be checked
    462      */
    463     private boolean isStrutsRequest(String uri) {
    464
    465         int question = uri.indexOf("?");
    466         if (question >= 0) {
    467             uri = uri.substring(0, question);
    468         }
    469         String mapping = (String)
    470             servlet.getServletContext().getAttribute(Globals.SERVLET_KEY);
    471         if (mapping == null) {
    472             return (false);
    473         } else if (mapping.startsWith("*.")) {
    474             return (uri.endsWith(mapping.substring(1)));
    475         } else if (mapping.endsWith("/*")) {
    476             return (uri.startsWith(mapping.substring(0, mapping.length() - 2)));
    477         } else {
    478             return (false);
    479         }
    480
    481     }
    482
    483
    484 }