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