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