View Javadoc

1   /*
2    * $Id: FacesRequestProcessor.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.RequestProcessor;
46  import org.apache.struts.action.InvalidCancelException;
47  import org.apache.struts.config.FormBeanConfig;
48  import org.apache.struts.config.ForwardConfig;
49  import org.apache.struts.faces.Constants;
50  import org.apache.struts.faces.component.FormComponent;
51  
52  
53  
54  /**
55   * <p>Concrete implementation of <code>RequestProcessor</code> that
56   * implements the standard Struts request processing lifecycle on a
57   * request that was received as an <code>ActionEvent</code> by our
58   * associated <code>ActionListener</code>.  It replaces the request processor
59   * instance normally configured by Struts, so it must support non-Faces
60   * requests as well.</p>
61   *
62   * @version $Rev: 473326 $ $Date: 2006-11-10 06:58:06 -0600 (Fri, 10 Nov 2006) $
63   */
64  
65  public class FacesRequestProcessor extends RequestProcessor {
66  
67  
68      // ------------------------------------------------------ Instance Variables
69  
70  
71      /**
72       * <p>The log instance for this class.</p>
73       */
74      protected static Log log = LogFactory.getLog(FacesRequestProcessor.class);
75  
76  
77      /**
78       * <p>The lifecycle id.</p>
79       */
80      public static final String LIFECYCLE_ID_ATTR = "javax.faces.LIFECYCLE_ID";
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 Action processActionCreate(HttpServletRequest request,
176                                          HttpServletResponse response,
177                                          ActionMapping mapping)
178         throws IOException {
179 
180         if (log.isTraceEnabled()) {
181             log.trace("Performing standard action create");
182         }
183         Action result = super.processActionCreate(request, response, mapping);
184         if (log.isDebugEnabled()) {
185             log.debug("Standard action create returned " +
186                       result.getClass().getName() + " instance");
187         }
188         return (result);
189 
190     }
191 
192 
193     // Override default processing to provide logging
194     protected ActionForm processActionForm(HttpServletRequest request,
195                                            HttpServletResponse response,
196                                            ActionMapping mapping) {
197         if (log.isTraceEnabled()) {
198             log.trace("Performing standard action form processing");
199             String attribute = mapping.getAttribute();
200             if (attribute != null) {
201                 String name = mapping.getName();
202                 FormBeanConfig fbc = moduleConfig.findFormBeanConfig(name);
203                 if (fbc != null) {
204                     if ("request".equals(mapping.getScope())) {
205                         log.trace("  Bean in request scope = " +
206                                   request.getAttribute(attribute));
207                     } else {
208                         log.trace("  Bean in session scope = " +
209                                   request.getSession().getAttribute(attribute));
210                     }
211                 } else {
212                     log.trace("  No FormBeanConfig for '" + name + "'");
213                 }
214             } else {
215                 log.trace("  No form bean for this action");
216             }
217         }
218         ActionForm result =
219             super.processActionForm(request, response, mapping);
220         if (log.isDebugEnabled()) {
221             log.debug("Standard action form returned " +
222                       result);
223         }
224         return (result);
225 
226 
227     }
228 
229 
230     // Override default processing to provide logging
231     protected ActionForward processActionPerform(HttpServletRequest request,
232                                                  HttpServletResponse response,
233                                                  Action action,
234                                                  ActionForm form,
235                                                  ActionMapping mapping)
236         throws IOException, ServletException {
237 
238         if (log.isTraceEnabled()) {
239             log.trace("Performing standard action perform");
240         }
241         ActionForward result =
242             super.processActionPerform(request, response, action,
243                                        form, mapping);
244         if (log.isDebugEnabled()) {
245             log.debug("Standard action perform returned " +
246                       (result == null ? "NULL" :
247                       result.getPath()) + " forward path");
248         }
249         return (result);
250 
251     }
252 
253 
254     // Override default processing to provide logging
255     protected boolean processForward(HttpServletRequest request,
256                                      HttpServletResponse response,
257                                      ActionMapping mapping)
258         throws IOException, ServletException {
259 
260         if (log.isTraceEnabled()) {
261             log.trace("Performing standard forward handling");
262         }
263         boolean result = super.processForward
264             (request, response, mapping);
265         if (log.isDebugEnabled()) {
266             log.debug("Standard forward handling returned " + result);
267         }
268         return (result);
269 
270     }
271 
272 
273     // Override default processing to provide logging
274     protected void processForwardConfig(HttpServletRequest request,
275                                         HttpServletResponse response,
276                                         ForwardConfig forward)
277         throws IOException, ServletException {
278 
279         if (log.isTraceEnabled()) {
280             log.trace("Performing standard forward config handling");
281         }
282         super.processForwardConfig(request, response, forward);
283         if (log.isDebugEnabled()) {
284             log.debug("Standard forward config handling completed");
285         }
286 
287     }
288 
289 
290     // Override default processing to provide logging
291     protected boolean processInclude(HttpServletRequest request,
292                                      HttpServletResponse response,
293                                      ActionMapping mapping)
294         throws IOException, ServletException {
295 
296         if (log.isTraceEnabled()) {
297             log.trace("Performing standard include handling");
298         }
299         boolean result = super.processInclude
300             (request, response, mapping);
301         if (log.isDebugEnabled()) {
302             log.debug("Standard include handling returned " + result);
303         }
304         return (result);
305 
306     }
307 
308 
309     /**
310      * <p>Identify and return the path component (from the request URI for a
311      * non-Faces request, or from the form event for a Faces request)
312      * that we will use to select an ActionMapping to dispatch with.
313      * If no such path can be identified, create an error response and return
314      * <code>null</code>.</p>
315      *
316      * @param request The servlet request we are processing
317      * @param response The servlet response we are creating
318      *
319      * @exception IOException if an input/output error occurs
320      */
321     protected String processPath(HttpServletRequest request,
322                                  HttpServletResponse response)
323         throws IOException {
324 
325         // Are we processing a Faces request?
326         ActionEvent event = (ActionEvent)
327             request.getAttribute(Constants.ACTION_EVENT_KEY);
328 
329         // Handle non-Faces requests in the usual way
330         if (event == null) {
331             if (log.isTraceEnabled()) {
332                 log.trace("Performing standard processPath() processing");
333             }
334             return (super.processPath(request, response));
335         }
336 
337         // Calculate the path from the form name
338         UIComponent component = event.getComponent();
339         if (log.isTraceEnabled()) {
340             log.trace("Locating form parent for command component " +
341                       event.getComponent());
342         }
343         while (!(component instanceof FormComponent)) {
344             component = component.getParent();
345             if (component == null) {
346                 log.warn("Command component was not nested in a Struts form!");
347                 return (null);
348             }
349         }
350         if (log.isDebugEnabled()) {
351             log.debug("Returning selected path of '" +
352                       ((FormComponent) component).getAction() + "'");
353         }
354         return (((FormComponent) component).getAction());
355 
356     }
357 
358 
359     /**
360      * <p>Populate the properties of the specified <code>ActionForm</code>
361      * instance from the request parameters included with this request,
362      * <strong>IF</strong> this is a non-Faces request.  For a Faces request,
363      * this will have already been done by the <em>Update Model Values</em>
364      * phase of the request processing lifecycle, so all we have to do is
365      * recognize whether the request was cancelled or not.</p>
366      *
367      * @param request The servlet request we are processing
368      * @param response The servlet response we are creating
369      * @param form The ActionForm instance we are populating
370      * @param mapping The ActionMapping we are using
371      *
372      * @exception ServletException if thrown by RequestUtils.populate()
373      */
374     protected void processPopulate(HttpServletRequest request,
375                                    HttpServletResponse response,
376                                    ActionForm form,
377                                    ActionMapping mapping)
378         throws ServletException {
379 
380         // Are we processing a Faces request?
381         ActionEvent event = (ActionEvent)
382             request.getAttribute(Constants.ACTION_EVENT_KEY);
383 
384         // Handle non-Faces requests in the usual way
385         if (event == null) {
386             if (log.isTraceEnabled()) {
387                 log.trace("Performing standard processPopulate() processing");
388             }
389             super.processPopulate(request, response, form, mapping);
390             return;
391         }
392 
393         // Faces Requests require no processing for form bean population
394         // so we need only check for the cancellation command name
395         if (log.isTraceEnabled()) {
396             log.trace("Faces request, so no processPopulate() processing");
397         }
398         UIComponent source = event.getComponent();
399         if (source instanceof UICommand) {
400             if ("cancel".equals(((UICommand) source).getId())) {
401                 if (log.isTraceEnabled()) {
402                     log.trace("Faces request with cancel button pressed");
403                 }
404                 request.setAttribute(Globals.CANCEL_KEY, Boolean.TRUE);
405             }
406         }
407 
408     }
409 
410 
411     // Override default processing to provide logging
412     protected boolean processValidate(HttpServletRequest request,
413                                       HttpServletResponse response,
414                                       ActionForm form,
415                                       ActionMapping mapping)
416         throws IOException, ServletException, InvalidCancelException {
417 
418         if (log.isTraceEnabled()) {
419             log.trace("Performing standard validation");
420         }
421         boolean result = super.processValidate
422             (request, response, form, mapping);
423         if (log.isDebugEnabled()) {
424             log.debug("Standard validation processing returned " + result);
425         }
426         return (result);
427 
428     }
429 
430 
431     // --------------------------------------------------------- Private Methods
432 
433 
434     /**
435      * <p>Return the used Lifecycle ID (default or custom).</p>
436      */
437     private String getLifecycleId()
438     {
439         String lifecycleId = this.servlet.getServletContext().getInitParameter(LIFECYCLE_ID_ATTR);
440         return lifecycleId != null ? lifecycleId : LifecycleFactory.DEFAULT_LIFECYCLE;
441     }  
442 
443     /**
444      * <p>Return <code>true</code> if the specified context-relative URI
445      * specifies a request to be processed by the Struts controller servlet.</p>
446      *
447      * @param uri URI to be checked
448      */
449     private boolean isStrutsRequest(String uri) {
450 
451         int question = uri.indexOf("?");
452         if (question >= 0) {
453             uri = uri.substring(0, question);
454         }
455         String mapping = (String)
456             servlet.getServletContext().getAttribute(Globals.SERVLET_KEY);
457         if (mapping == null) {
458             return (false);
459         } else if (mapping.startsWith("*.")) {
460             return (uri.endsWith(mapping.substring(1)));
461         } else if (mapping.endsWith("/*")) {
462             return (uri.startsWith(mapping.substring(0, mapping.length() - 2)));
463         } else {
464             return (false);
465         }
466 
467     }
468 
469 
470 }