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