001: /*
002: * $Id: FacesRequestProcessor.java 473326 2006-11-10 12:58:06Z niallp $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: package org.apache.struts.faces.application;
023:
024: import java.io.IOException;
025: import javax.faces.FactoryFinder;
026: import javax.faces.application.ViewHandler;
027: import javax.faces.component.UICommand;
028: import javax.faces.component.UIComponent;
029: import javax.faces.context.FacesContext;
030: import javax.faces.context.FacesContextFactory;
031: import javax.faces.event.ActionEvent;
032: import javax.faces.lifecycle.Lifecycle;
033: import javax.faces.lifecycle.LifecycleFactory;
034: import javax.servlet.ServletException;
035: import javax.servlet.http.HttpServletRequest;
036: import javax.servlet.http.HttpServletResponse;
037: import org.apache.commons.logging.Log;
038: import org.apache.commons.logging.LogFactory;
039: import org.apache.struts.Globals;
040: import org.apache.struts.action.Action;
041: import org.apache.struts.action.ActionForm;
042: import org.apache.struts.action.ActionForward;
043: import org.apache.struts.action.ActionMapping;
044: import org.apache.struts.action.RequestProcessor;
045: import org.apache.struts.action.InvalidCancelException;
046: import org.apache.struts.config.FormBeanConfig;
047: import org.apache.struts.config.ForwardConfig;
048: import org.apache.struts.faces.Constants;
049: import org.apache.struts.faces.component.FormComponent;
050:
051: /**
052: * <p>Concrete implementation of <code>RequestProcessor</code> that
053: * implements the standard Struts request processing lifecycle on a
054: * request that was received as an <code>ActionEvent</code> by our
055: * associated <code>ActionListener</code>. It replaces the request processor
056: * instance normally configured by Struts, so it must support non-Faces
057: * requests as well.</p>
058: *
059: * @version $Rev: 473326 $ $Date: 2006-11-10 06:58:06 -0600 (Fri, 10 Nov 2006) $
060: */
061:
062: public class FacesRequestProcessor extends RequestProcessor {
063:
064: // ------------------------------------------------------ Instance Variables
065:
066: /**
067: * <p>The log instance for this class.</p>
068: */
069: protected static Log log = LogFactory
070: .getLog(FacesRequestProcessor.class);
071:
072: /**
073: * <p>The lifecycle id.</p>
074: */
075: public static final String LIFECYCLE_ID_ATTR = "javax.faces.LIFECYCLE_ID";
076:
077: // ------------------------------------------------------- Protected Methods
078:
079: /**
080: * <p>Set up a Faces Request if we are not already processing one. Next,
081: * create a new view if the specified <code>uri</code> is different from
082: * the current view identifier. Finally, cause the new view to be
083: * rendered, and call <code>FacesContext.responseComplete()</code> to
084: * indicate that this has already been done.</p>
085: *
086: * @param uri Context-relative path to forward to
087: * @param request Current page request
088: * @param response Current page response
089: *
090: * @exception IOException if an input/output error occurs
091: * @exception ServletException if a servlet error occurs
092: */
093: protected void doForward(String uri, HttpServletRequest request,
094: HttpServletResponse response) throws IOException,
095: ServletException {
096:
097: if (log.isDebugEnabled()) {
098: log.debug("doForward(" + uri + ")");
099: }
100:
101: // Remove the current ActionEvent (if any)
102: request.removeAttribute(Constants.ACTION_EVENT_KEY);
103:
104: // Process a Struts controller request normally
105: if (isStrutsRequest(uri)) {
106: if (response.isCommitted()) {
107: if (log.isTraceEnabled()) {
108: log.trace(" super.doInclude(" + uri + ")");
109: }
110: super .doInclude(uri, request, response);
111: } else {
112: if (log.isTraceEnabled()) {
113: log.trace(" super.doForward(" + uri + ")");
114: }
115: super .doForward(uri, request, response);
116: }
117: return;
118: }
119:
120: // Create a FacesContext for this request if necessary
121: LifecycleFactory lf = (LifecycleFactory) FactoryFinder
122: .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
123: Lifecycle lifecycle = lf.getLifecycle(getLifecycleId());
124: boolean created = false;
125: FacesContext context = FacesContext.getCurrentInstance();
126: if (context == null) {
127: if (log.isTraceEnabled()) {
128: log.trace(" Creating new FacesContext for '" + uri
129: + "'");
130: }
131: created = true;
132: FacesContextFactory fcf = (FacesContextFactory) FactoryFinder
133: .getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
134: context = fcf.getFacesContext(servlet.getServletContext(),
135: request, response, lifecycle);
136: }
137:
138: // Create a new view root
139: ViewHandler vh = context.getApplication().getViewHandler();
140: if (log.isTraceEnabled()) {
141: log.trace(" Creating new view for '" + uri + "'");
142: }
143: context.setViewRoot(vh.createView(context, uri));
144:
145: // Cause the view to be rendered
146: if (log.isTraceEnabled()) {
147: log.trace(" Rendering view for '" + uri + "'");
148: }
149: try {
150: lifecycle.render(context);
151: } finally {
152: if (created) {
153: if (log.isTraceEnabled()) {
154: log.trace(" Releasing context for '" + uri + "'");
155: }
156: context.release();
157: } else {
158: if (log.isTraceEnabled()) {
159: log.trace(" Rendering completed");
160: }
161: }
162: }
163:
164: }
165:
166: // Override default processing to provide logging
167: protected Action processActionCreate(HttpServletRequest request,
168: HttpServletResponse response, ActionMapping mapping)
169: throws IOException {
170:
171: if (log.isTraceEnabled()) {
172: log.trace("Performing standard action create");
173: }
174: Action result = super .processActionCreate(request, response,
175: mapping);
176: if (log.isDebugEnabled()) {
177: log.debug("Standard action create returned "
178: + result.getClass().getName() + " instance");
179: }
180: return (result);
181:
182: }
183:
184: // Override default processing to provide logging
185: protected ActionForm processActionForm(HttpServletRequest request,
186: HttpServletResponse response, ActionMapping mapping) {
187: if (log.isTraceEnabled()) {
188: log.trace("Performing standard action form processing");
189: String attribute = mapping.getAttribute();
190: if (attribute != null) {
191: String name = mapping.getName();
192: FormBeanConfig fbc = moduleConfig
193: .findFormBeanConfig(name);
194: if (fbc != null) {
195: if ("request".equals(mapping.getScope())) {
196: log.trace(" Bean in request scope = "
197: + request.getAttribute(attribute));
198: } else {
199: log.trace(" Bean in session scope = "
200: + request.getSession().getAttribute(
201: attribute));
202: }
203: } else {
204: log.trace(" No FormBeanConfig for '" + name + "'");
205: }
206: } else {
207: log.trace(" No form bean for this action");
208: }
209: }
210: ActionForm result = super .processActionForm(request, response,
211: mapping);
212: if (log.isDebugEnabled()) {
213: log.debug("Standard action form returned " + result);
214: }
215: return (result);
216:
217: }
218:
219: // Override default processing to provide logging
220: protected ActionForward processActionPerform(
221: HttpServletRequest request, HttpServletResponse response,
222: Action action, ActionForm form, ActionMapping mapping)
223: throws IOException, ServletException {
224:
225: if (log.isTraceEnabled()) {
226: log.trace("Performing standard action perform");
227: }
228: ActionForward result = super .processActionPerform(request,
229: response, action, form, mapping);
230: if (log.isDebugEnabled()) {
231: log.debug("Standard action perform returned "
232: + (result == null ? "NULL" : result.getPath())
233: + " forward path");
234: }
235: return (result);
236:
237: }
238:
239: // Override default processing to provide logging
240: protected boolean processForward(HttpServletRequest request,
241: HttpServletResponse response, ActionMapping mapping)
242: throws IOException, ServletException {
243:
244: if (log.isTraceEnabled()) {
245: log.trace("Performing standard forward handling");
246: }
247: boolean result = super .processForward(request, response,
248: mapping);
249: if (log.isDebugEnabled()) {
250: log.debug("Standard forward handling returned " + result);
251: }
252: return (result);
253:
254: }
255:
256: // Override default processing to provide logging
257: protected void processForwardConfig(HttpServletRequest request,
258: HttpServletResponse response, ForwardConfig forward)
259: throws IOException, ServletException {
260:
261: if (log.isTraceEnabled()) {
262: log.trace("Performing standard forward config handling");
263: }
264: super .processForwardConfig(request, response, forward);
265: if (log.isDebugEnabled()) {
266: log.debug("Standard forward config handling completed");
267: }
268:
269: }
270:
271: // Override default processing to provide logging
272: protected boolean processInclude(HttpServletRequest request,
273: HttpServletResponse response, ActionMapping mapping)
274: throws IOException, ServletException {
275:
276: if (log.isTraceEnabled()) {
277: log.trace("Performing standard include handling");
278: }
279: boolean result = super .processInclude(request, response,
280: mapping);
281: if (log.isDebugEnabled()) {
282: log.debug("Standard include handling returned " + result);
283: }
284: return (result);
285:
286: }
287:
288: /**
289: * <p>Identify and return the path component (from the request URI for a
290: * non-Faces request, or from the form event for a Faces request)
291: * that we will use to select an ActionMapping to dispatch with.
292: * If no such path can be identified, create an error response and return
293: * <code>null</code>.</p>
294: *
295: * @param request The servlet request we are processing
296: * @param response The servlet response we are creating
297: *
298: * @exception IOException if an input/output error occurs
299: */
300: protected String processPath(HttpServletRequest request,
301: HttpServletResponse response) throws IOException {
302:
303: // Are we processing a Faces request?
304: ActionEvent event = (ActionEvent) request
305: .getAttribute(Constants.ACTION_EVENT_KEY);
306:
307: // Handle non-Faces requests in the usual way
308: if (event == null) {
309: if (log.isTraceEnabled()) {
310: log
311: .trace("Performing standard processPath() processing");
312: }
313: return (super .processPath(request, response));
314: }
315:
316: // Calculate the path from the form name
317: UIComponent component = event.getComponent();
318: if (log.isTraceEnabled()) {
319: log.trace("Locating form parent for command component "
320: + event.getComponent());
321: }
322: while (!(component instanceof FormComponent)) {
323: component = component.getParent();
324: if (component == null) {
325: log
326: .warn("Command component was not nested in a Struts form!");
327: return (null);
328: }
329: }
330: if (log.isDebugEnabled()) {
331: log.debug("Returning selected path of '"
332: + ((FormComponent) component).getAction() + "'");
333: }
334: return (((FormComponent) component).getAction());
335:
336: }
337:
338: /**
339: * <p>Populate the properties of the specified <code>ActionForm</code>
340: * instance from the request parameters included with this request,
341: * <strong>IF</strong> this is a non-Faces request. For a Faces request,
342: * this will have already been done by the <em>Update Model Values</em>
343: * phase of the request processing lifecycle, so all we have to do is
344: * recognize whether the request was cancelled or not.</p>
345: *
346: * @param request The servlet request we are processing
347: * @param response The servlet response we are creating
348: * @param form The ActionForm instance we are populating
349: * @param mapping The ActionMapping we are using
350: *
351: * @exception ServletException if thrown by RequestUtils.populate()
352: */
353: protected void processPopulate(HttpServletRequest request,
354: HttpServletResponse response, ActionForm form,
355: ActionMapping mapping) throws ServletException {
356:
357: // Are we processing a Faces request?
358: ActionEvent event = (ActionEvent) request
359: .getAttribute(Constants.ACTION_EVENT_KEY);
360:
361: // Handle non-Faces requests in the usual way
362: if (event == null) {
363: if (log.isTraceEnabled()) {
364: log
365: .trace("Performing standard processPopulate() processing");
366: }
367: super .processPopulate(request, response, form, mapping);
368: return;
369: }
370:
371: // Faces Requests require no processing for form bean population
372: // so we need only check for the cancellation command name
373: if (log.isTraceEnabled()) {
374: log
375: .trace("Faces request, so no processPopulate() processing");
376: }
377: UIComponent source = event.getComponent();
378: if (source instanceof UICommand) {
379: if ("cancel".equals(((UICommand) source).getId())) {
380: if (log.isTraceEnabled()) {
381: log
382: .trace("Faces request with cancel button pressed");
383: }
384: request.setAttribute(Globals.CANCEL_KEY, Boolean.TRUE);
385: }
386: }
387:
388: }
389:
390: // Override default processing to provide logging
391: protected boolean processValidate(HttpServletRequest request,
392: HttpServletResponse response, ActionForm form,
393: ActionMapping mapping) throws IOException,
394: ServletException, InvalidCancelException {
395:
396: if (log.isTraceEnabled()) {
397: log.trace("Performing standard validation");
398: }
399: boolean result = super .processValidate(request, response, form,
400: mapping);
401: if (log.isDebugEnabled()) {
402: log.debug("Standard validation processing returned "
403: + result);
404: }
405: return (result);
406:
407: }
408:
409: // --------------------------------------------------------- Private Methods
410:
411: /**
412: * <p>Return the used Lifecycle ID (default or custom).</p>
413: */
414: private String getLifecycleId() {
415: String lifecycleId = this .servlet.getServletContext()
416: .getInitParameter(LIFECYCLE_ID_ATTR);
417: return lifecycleId != null ? lifecycleId
418: : LifecycleFactory.DEFAULT_LIFECYCLE;
419: }
420:
421: /**
422: * <p>Return <code>true</code> if the specified context-relative URI
423: * specifies a request to be processed by the Struts controller servlet.</p>
424: *
425: * @param uri URI to be checked
426: */
427: private boolean isStrutsRequest(String uri) {
428:
429: int question = uri.indexOf("?");
430: if (question >= 0) {
431: uri = uri.substring(0, question);
432: }
433: String mapping = (String) servlet.getServletContext()
434: .getAttribute(Globals.SERVLET_KEY);
435: if (mapping == null) {
436: return (false);
437: } else if (mapping.startsWith("*.")) {
438: return (uri.endsWith(mapping.substring(1)));
439: } else if (mapping.endsWith("/*")) {
440: return (uri.startsWith(mapping.substring(0, mapping
441: .length() - 2)));
442: } else {
443: return (false);
444: }
445:
446: }
447:
448: }
|