001: /*
002: * $Id: LifecycleImpl.java,v 1.10 2006/06/13 09:45:55 dg154973 Exp $
003: */
004:
005: /*
006: * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
007: * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
008: */
009:
010: package com.sun.faces.portlet;
011:
012: import java.io.IOException;
013: import java.util.ArrayList;
014: import java.util.Collections;
015: import java.util.HashMap;
016: import java.util.Iterator;
017: import java.util.List;
018: import java.util.Map;
019:
020: import javax.faces.FacesException;
021: import javax.faces.application.FacesMessage;
022: import javax.faces.application.ViewHandler;
023: import javax.faces.component.UIComponent;
024: import javax.faces.component.UIViewRoot;
025: import javax.faces.context.FacesContext;
026: import javax.faces.el.ValueBinding;
027: import javax.faces.event.PhaseEvent;
028: import javax.faces.event.PhaseId;
029: import javax.faces.event.PhaseListener;
030: import javax.faces.lifecycle.Lifecycle;
031: import javax.faces.render.RenderKitFactory;
032:
033: import javax.portlet.PortletSession;
034:
035: import org.apache.commons.logging.Log;
036: import org.apache.commons.logging.LogFactory;
037:
038: /**
039: * <p>Custom implementation of <code>Lifecycle</code> that implements a
040: * portlet-specific <code>Lifecycle</code>.</p>
041: */
042:
043: public final class LifecycleImpl extends Lifecycle {
044:
045: // ------------------------------------------------------------ Constructors
046:
047: /**
048: * <p>Construct a new <code>LifecycleImpl</code> instance.</p>
049: */
050: public LifecycleImpl() {
051: super ();
052: if (log.isTraceEnabled()) {
053: log.trace("Created Lifecycle " + this );
054: }
055: }
056:
057: // -------------------------------------------------------- Static Variables
058:
059: /**
060: * <p><code>Log</code> instance for this class.</p>
061: */
062: private static final Log log = LogFactory
063: .getLog(LifecycleImpl.class);
064:
065: /**
066: * <p>Portlet initialization parameter which holds the value of one of
067: * initial mode identifier. i.e either INIT_VIEW_PARAMETER or
068: * INIT_EDIT_PARAMETER or INIT_HELP_PARAMETER</p>
069: */
070: public static final String INIT_PARAMETER = "com.sun.faces.portlet.INIT";
071:
072: /**
073: * <p>Portlet initialization parameter under which the application
074: * may specify an initial view mode identifier to be displayed.</p>
075: */
076: public static final String INIT_VIEW_PARAMETER = "com.sun.faces.portlet.INIT_VIEW";
077:
078: /**
079: * <p>Portlet initialization parameter under which the application
080: * may specify an initial edit mode identifier to be displayed.</p>
081: */
082: public static final String INIT_EDIT_PARAMETER = "com.sun.faces.portlet.INIT_EDIT";
083:
084: /**
085: * <p>Portlet initialization parameter under which the application
086: * may specify an initial help mode identifier to be displayed.</p>
087: */
088: public static final String INIT_HELP_PARAMETER = "com.sun.faces.portlet.INIT_HELP";
089:
090: /**
091: * <p>Portlet session attribute (in portlet scope) under which we will
092: * save the window state identifer. i.e one of the WINDOW_STATE_ATTR_* values.</p>
093: */
094: private static final String PREVIOUS_WINDOW_STATE = "com.sun.faces.portlet.PREVIOUS_WINDOW_STATE";
095:
096: /**
097: * <p>Portlet session attribute (in portlet scope) under which we will
098: * save state information for VIEW for the current window.</p>
099: */
100: private static final String WINDOW_STATE_ATTR_VIEW = "com.sun.faces.portlet.WINDOW_STATE_VIEW";
101:
102: /**
103: * <p>Portlet session attribute (in portlet scope) under which we will
104: * save state information for EDIT for the current window.</p>
105: */
106: private static final String WINDOW_STATE_ATTR_EDIT = "com.sun.faces.portlet.WINDOW_STATE_EDIT";
107:
108: /**
109: * <p>Portlet session attribute (in portlet scope) under which we will
110: * save state information for HELP for the current window.</p>
111: */
112: private static final String WINDOW_STATE_ATTR_HELP = "com.sun.faces.portlet.WINDOW_STATE_HELP";
113:
114: /**
115: * <p>Portlet session attribute (in portlet scope) under which we will
116: * save a Boolean TRUE or FALSE for the current window.</p>
117: */
118: public static final String ERROR_FLAG = "com.sun.faces.portlet.ERROR_FLAG";
119:
120: // ------------------------------------------------------ Instance Variables
121:
122: /**
123: * <p>The set of <code>PhaseListener</code>s registered with this
124: * <code>Lifecycle</code> instance, in order of registration.</p>
125: */
126: private List listeners = new ArrayList();
127:
128: /**
129: * <p>The set of {@link Phase} instances that are to be
130: * executed by the <code>execute()</code> method, in order by the
131: * ordinal property of each phase.</p>
132: */
133: private Phase phases[] = {
134: null, // ANY_PHASE holder
135: new RestoreViewPhase(), new ApplyRequestValuesPhase(),
136: new ProcessValidationsPhase(),
137: new UpdateModelValuesPhase(), new InvokeApplicationPhase() };
138:
139: /**
140: * <p>The {@link Phase} instance to process Render Response phase.</p>
141: */
142: private Phase response = new RenderResponsePhase();
143:
144: // ------------------------------------------------------- Lifecycle Methods
145:
146: // Add a new PhaseListener to the set of registered listeners
147: public void addPhaseListener(PhaseListener listener) {
148:
149: if (listener == null) {
150: throw new NullPointerException();
151: }
152: if (log.isDebugEnabled()) {
153: log
154: .debug("addPhaseListener("
155: + listener.getPhaseId().toString() + ","
156: + listener);
157: }
158: synchronized (listeners) {
159: listeners.add(listener);
160: }
161:
162: }
163:
164: // Execute the phases up to but not including Render Response
165: public void execute(FacesContext context) throws FacesException {
166:
167: if (context == null) {
168: throw new NullPointerException();
169: }
170:
171: if (log.isDebugEnabled()) {
172: log.debug("execute(" + context + ")");
173: }
174:
175: restore(context, true);
176:
177: setErrorFlag(context, Boolean.FALSE);
178:
179: for (int i = 1; i < phases.length; i++) {
180: PhaseId phaseId = (PhaseId) PhaseId.VALUES.get(i);
181: if (context.getRenderResponse()
182: || context.getResponseComplete()) {
183: /*
184: (a) If there are any validation/conversion errors, then its an error.
185: (b) If there are no validation/conversion errors, it means either
186: immediate=true is specified or FacesContext.renderResponse() has been
187: called.
188: */
189: Iterator itr = context.getMessages();
190: if (itr.hasNext()) {
191: setErrorFlag(context, Boolean.TRUE);
192: }
193: break;
194: }
195: phase(phaseId, phases[i], context);
196: }
197:
198: save(context, true);
199:
200: }
201:
202: // Return the set of PhaseListeners that have been registered
203: public PhaseListener[] getPhaseListeners() {
204:
205: synchronized (listeners) {
206: PhaseListener results[] = new PhaseListener[listeners
207: .size()];
208: return ((PhaseListener[]) listeners.toArray(results));
209: }
210:
211: }
212:
213: // Execute the Render Response phase
214: public void render(FacesContext context) throws FacesException {
215: if (context == null) {
216: throw new NullPointerException();
217: }
218:
219: if (log.isDebugEnabled()) {
220: log.debug("render(" + context + ")");
221: }
222:
223: restore(context, false);
224:
225: setErrorFlag(context, Boolean.FALSE);
226:
227: if (!context.getResponseComplete()) {
228: phase(PhaseId.RENDER_RESPONSE, response, context);
229: }
230:
231: save(context, false);
232: }
233:
234: // Remove a registered PhaseListener from the set of registered listeners
235: public void removePhaseListener(PhaseListener listener) {
236:
237: if (listener == null) {
238: throw new NullPointerException();
239: }
240: if (log.isDebugEnabled()) {
241: log
242: .debug("removePhaseListener("
243: + listener.getPhaseId().toString() + ","
244: + listener);
245: }
246: synchronized (listeners) {
247: listeners.remove(listener);
248: }
249:
250: }
251:
252: // --------------------------------------------------------- Private Methods
253:
254: /**
255: * <p>Execute the specified phase instance for the current request.</p>
256: *
257: * @param phaseId Phase identifier of the current phase
258: * @param phase {@link Phase} instance to execute
259: * @param context <code>FacesContext</code> for the current request
260: *
261: * @exception FacesException if thrown by the phase execution
262: */
263: private void phase(PhaseId phaseId, Phase phase,
264: FacesContext context) throws FacesException {
265:
266: boolean exceptionThrownInRender = false;
267: Exception tempException = null;
268:
269: if (log.isTraceEnabled()) {
270: log.trace("phase(" + phaseId.toString() + "," + context
271: + ")");
272: }
273:
274: // Acquire (once) the list of interested phase listeners
275: // Clone it so that we are independent of changes during the
276: // execution of this phase
277: PhaseListener listeners[] = getPhaseListeners();
278:
279: try {
280:
281: // Notify the "beforePhase" method of interested listeners
282: if (listeners.length > 0) {
283: PhaseEvent event = new PhaseEvent(context, phaseId,
284: this );
285: for (int i = 0; i < listeners.length; i++) {
286: if (phaseId.equals(listeners[i].getPhaseId())
287: || PhaseId.ANY_PHASE.equals(listeners[i]
288: .getPhaseId())) {
289: listeners[i].beforePhase(event);
290: }
291: }
292: }
293:
294: } catch (Exception e) {
295:
296: // Log the problem, but continue
297: if (log.isDebugEnabled()) {
298: log.debug("beforePhase(" + phaseId.toString() + ","
299: + context + ") threw exception", e);
300: }
301:
302: }
303:
304: try {
305:
306: try {
307:
308: // Execute this phase itself
309: phase.execute(context);
310:
311: } catch (Exception e) {
312:
313: // Log the problem, but continue
314: if (log.isDebugEnabled()) {
315: log.debug("executePhase(" + phaseId.toString()
316: + "," + context + ") threw exception", e);
317: }
318:
319: // Don't rethrow as it will break the jsf lifecycle execution
320: // If exception is thrown in the render phase, hold a reference to the exception
321: if (phaseId.compareTo(PhaseId.RENDER_RESPONSE) == 0) {
322: exceptionThrownInRender = true;
323: tempException = e;
324: }
325: }
326:
327: // Notify the "afterPhase" method of interested listeners
328: if (listeners.length > 0) {
329: PhaseEvent event = new PhaseEvent(context, phaseId,
330: this );
331: for (int i = listeners.length - 1; i >= 0; i--) {
332: if (phaseId.equals(listeners[i].getPhaseId())
333: || PhaseId.ANY_PHASE.equals(listeners[i]
334: .getPhaseId())) {
335: listeners[i].afterPhase(event);
336: }
337: }
338: }
339:
340: } catch (Exception e) {
341:
342: // Log the problem, but continue
343: if (log.isDebugEnabled()) {
344: log.debug("afterPhase(" + phaseId.toString() + ","
345: + context + ") threw exception", e);
346: }
347:
348: }
349:
350: if (exceptionThrownInRender)
351: throw new FacesException(tempException);
352: }
353:
354: /**
355: * <p>Restore state information for the current window into the
356: * specified <code>FacesContext</code> instance.</p>
357: *
358: * @param context <code>FacesContext</code> for this request
359: * @param action Flag indicating this is action mode (true)
360: * or render mode (false)
361: */
362: private void restore(FacesContext context, boolean action) {
363: if (log.isTraceEnabled()) {
364: log.trace("restore(" + context + "," + action + ")");
365: }
366:
367: // Retrieve the cached state information (if any)
368: String windowStateIdentifier = getWindowStateIdentifier((String) context
369: .getExternalContext().getRequestMap().get(
370: INIT_PARAMETER));
371:
372: PortletSession session = (PortletSession) context
373: .getExternalContext().getSession(true);
374:
375: String previousWindowStateIndentifier = (String) session
376: .getAttribute(PREVIOUS_WINDOW_STATE);
377:
378: FacesPortletState state = null;
379: // This will ensure that the state information saved for INIT_VIEW will be restored
380: // only for INIT_VIEW and so on..
381: if (previousWindowStateIndentifier != null
382: && previousWindowStateIndentifier
383: .equals(windowStateIdentifier)) {
384: state = (FacesPortletState) session
385: .getAttribute(windowStateIdentifier);
386:
387: }
388:
389: if (state == null) {
390: setViewId(context);
391: return;
392: }
393:
394: // Restore the cached state information
395: if (!action) {
396: Iterator messages;
397: Iterator clientIds = state.getClientIds();
398: while (clientIds.hasNext()) {
399: String clientId = (String) clientIds.next();
400: messages = state.getMessages(clientId);
401: while (messages.hasNext()) {
402: context.addMessage(clientId,
403: (FacesMessage) messages.next());
404: }
405: }
406: }
407: context.setViewRoot(state.getViewRoot());
408:
409: // Remove the cached state information
410: session.removeAttribute(windowStateIdentifier);
411:
412: if (log.isTraceEnabled()) {
413: log.trace("End restore()");
414: }
415:
416: }
417:
418: /**
419: * <p>Save state information for the current window from the
420: * specified <code>FacesContext</code> instance.</p>
421: *
422: * @param context <code>FacesContext</code> for this request
423: * @param action Flag indicating this is action mode (true)
424: * or render mode (false)
425: */
426: private void save(FacesContext context, boolean action) {
427: if (log.isTraceEnabled()) {
428: log.trace("save(" + context + "," + action + ")");
429: }
430: // Save state information from this FacesContext
431: FacesPortletState state = new FacesPortletState();
432: Iterator messages;
433: Iterator clientIds = context.getClientIdsWithMessages();
434: while (clientIds.hasNext()) {
435: String clientId = (String) clientIds.next();
436: messages = context.getMessages(clientId);
437: while (messages.hasNext()) {
438: state.addMessage(clientId, (FacesMessage) messages
439: .next());
440: }
441: // Log the message
442: if (log.isErrorEnabled()) {
443: log.error(clientId + state.getMessagesBuffer(clientId));
444: }
445: }
446: state.setViewRoot(context.getViewRoot());
447:
448: // Cache the state information in a session in portlet scope
449: String windowStateIdentifier = getWindowStateIdentifier((String) context
450: .getExternalContext().getRequestMap().get(
451: INIT_PARAMETER));
452:
453: PortletSession session = (PortletSession) context
454: .getExternalContext().getSession(true);
455: // Save the window state identifier in a session in portlet scope
456: // This information is used during restore to restore the cached information for the
457: // appropriate window state. This will ensure that the state information saved for
458: // INIT_VIEW will be restored only for INIT_VIEW and so on..
459: session.setAttribute(PREVIOUS_WINDOW_STATE,
460: windowStateIdentifier);
461:
462: session.setAttribute(windowStateIdentifier, state);
463: }
464:
465: /**
466: * <p>Get the view identifier to a default page specified in a
467: * portlet init parameter.</p>
468: *
469: * @param context <code>FacesContext</code> for the current request
470: */
471: private String getInitViewId(FacesContext context) {
472: Map requestMap = context.getExternalContext().getRequestMap();
473: String initParameterIdentifier = (String) requestMap
474: .get(INIT_PARAMETER);
475: String viewId = (String) requestMap
476: .get(initParameterIdentifier);
477: return viewId;
478: }
479:
480: /**
481: * <p>Set the view identifier to a default page specified in a
482: * portlet init parameter.</p>
483: *
484: * @param context <code>FacesContext</code> for the current request
485: */
486: private void setViewId(FacesContext context) {
487: String viewId = getInitViewId(context);
488:
489: if (context.getViewRoot() == null) {
490: context.setViewRoot(context.getApplication()
491: .getViewHandler().createView(context, viewId));
492: if (log.isDebugEnabled()) {
493: log.debug("Created new ViewRoot with View Id"
494: + context.getViewRoot().getViewId());
495: }
496: } else {
497: context.getViewRoot().setViewId(viewId);
498: if (log.isDebugEnabled()) {
499: log.debug("set viewId to " + viewId);
500: }
501: }
502:
503: context.getViewRoot().setRenderKitId(
504: RenderKitFactory.HTML_BASIC_RENDER_KIT);
505: }
506:
507: /**
508: * Returns the window state attribute identifier based on the
509: * init parameter identifier.
510: *
511: * @param initParameterIdentifier the initialization parameter identifier
512: * @return window state attribute identifier
513: */
514: private String getWindowStateIdentifier(
515: String initParameterIdentifier) {
516: if (initParameterIdentifier.equals(INIT_VIEW_PARAMETER))
517: return WINDOW_STATE_ATTR_VIEW;
518: else if (initParameterIdentifier.equals(INIT_EDIT_PARAMETER))
519: return WINDOW_STATE_ATTR_EDIT;
520: else if (initParameterIdentifier.equals(INIT_HELP_PARAMETER))
521: return WINDOW_STATE_ATTR_HELP;
522: else
523: return null;
524:
525: }
526:
527: private void setErrorFlag(FacesContext context, Boolean value) {
528: PortletSession session = (PortletSession) context
529: .getExternalContext().getSession(true);
530: session.setAttribute(ERROR_FLAG, value);
531: }
532:
533: // ------------------------------------------------------- Phase Implementations
534:
535: interface Phase {
536:
537: public void execute(FacesContext context) throws FacesException;
538:
539: }
540:
541: final class RestoreViewPhase implements Phase {
542:
543: public void execute(FacesContext context) throws FacesException {
544: if (log.isDebugEnabled()) {
545: log.debug("Begin RestoreViewPhase");
546: }
547: // get the viewId from the context
548: String viewId = context.getViewRoot().getViewId();
549: if (viewId == null) {
550: if (log.isTraceEnabled()) {
551: log.trace("No view identifier found");
552: }
553: throw new FacesException // PENDING - i18n
554: ("No view identifier in this request");
555: }
556:
557: // Try to restore the view
558: ViewHandler vh = context.getApplication().getViewHandler();
559: UIViewRoot viewRoot = vh.restoreView(context, viewId);
560: if (viewRoot == null) {
561: if (log.isTraceEnabled()) {
562: log.trace("Creating new view '" + viewId + "'");
563: }
564: viewRoot = vh.createView(context, viewId);
565: context.renderResponse();
566: } else {
567: if (log.isTraceEnabled()) {
568: log.trace("Restoring old view '" + viewId + "'");
569: }
570: }
571: context.setViewRoot(viewRoot);
572: doPerComponentActions(context, viewRoot);
573: if (log.isDebugEnabled()) {
574: log.debug("End RestoreViewPhase");
575: }
576:
577: }
578:
579: // Do the per-component actions needed during restore
580: private void doPerComponentActions(FacesContext context,
581: UIComponent component) {
582:
583: Iterator kids = component.getFacetsAndChildren();
584: while (kids.hasNext()) {
585: doPerComponentActions(context, (UIComponent) kids
586: .next());
587: }
588: ValueBinding vb = component.getValueBinding("binding");
589: if (vb != null) {
590: vb.setValue(context, component);
591: }
592: }
593:
594: }
595:
596: final class ApplyRequestValuesPhase implements Phase {
597:
598: public void execute(FacesContext context) throws FacesException {
599: if (log.isDebugEnabled()) {
600: log.debug("Begin ApplyRequestValuesPhase");
601: }
602: UIViewRoot viewRoot = context.getViewRoot();
603: try {
604: viewRoot.processDecodes(context);
605: } catch (FacesException e) {
606: log.error("" + e, e);
607: throw e;
608: } catch (RuntimeException e) {
609: log.error("" + e, e);
610: throw new FacesException(e);
611: }
612: if (log.isDebugEnabled()) {
613: log.debug("End ApplyRequestValuesPhase");
614: }
615: }
616:
617: }
618:
619: final class ProcessValidationsPhase implements Phase {
620:
621: public void execute(FacesContext context) throws FacesException {
622: if (log.isDebugEnabled()) {
623: log.debug("Begin ProcessValidationsPhase");
624: }
625: UIViewRoot viewRoot = context.getViewRoot();
626: try {
627: viewRoot.processValidators(context);
628: } catch (FacesException e) {
629: log.error("" + e, e);
630: throw e;
631: } catch (RuntimeException e) {
632: log.error("" + e, e);
633: throw new FacesException(e);
634: }
635: if (log.isDebugEnabled()) {
636: log.debug("End ProcessValidationsPhase");
637: }
638: }
639:
640: }
641:
642: final class UpdateModelValuesPhase implements Phase {
643:
644: public void execute(FacesContext context) throws FacesException {
645: if (log.isDebugEnabled()) {
646: log.debug("Begin UpdateModelValuesPhase");
647: }
648: UIViewRoot viewRoot = context.getViewRoot();
649: try {
650: viewRoot.processUpdates(context);
651: } catch (FacesException e) {
652: log.error("" + e, e);
653: throw e;
654: } catch (RuntimeException e) {
655: log.error("" + e, e);
656: throw new FacesException(e);
657: }
658: if (log.isDebugEnabled()) {
659: log.debug("End UpdateModelValuesPhase");
660: }
661: }
662:
663: }
664:
665: final class InvokeApplicationPhase implements Phase {
666:
667: public void execute(FacesContext context) throws FacesException {
668: if (log.isDebugEnabled()) {
669: log.debug("Begin InvokeApplicationPhase");
670: }
671: UIViewRoot viewRoot = context.getViewRoot();
672: try {
673: viewRoot.processApplication(context);
674: } catch (FacesException e) {
675: log.error("" + e, e);
676: throw e;
677: } catch (RuntimeException e) {
678: log.error("" + e, e);
679: throw new FacesException(e);
680: }
681: if (log.isDebugEnabled()) {
682: log.debug("End InvokeApplicationPhase");
683: }
684: }
685:
686: }
687:
688: final class RenderResponsePhase implements Phase {
689:
690: public void execute(FacesContext context) throws FacesException {
691: if (log.isDebugEnabled()) {
692: log.debug("Begin RenderResponsePhase");
693: }
694: String requestURI = context.getViewRoot().getViewId();
695: if (log.isDebugEnabled()) {
696: log.debug("About to render view " + requestURI);
697: }
698: try {
699: context.getApplication().getViewHandler().renderView(
700: context, context.getViewRoot());
701: } catch (FacesException e) {
702: log.error("" + e, e);
703: throw e;
704: } catch (IOException e) {
705: throw new FacesException(e);
706: }
707: if (log.isDebugEnabled()) {
708: log.debug("End RenderResponsePhase");
709: }
710: }
711:
712: }
713:
714: }
715:
716: // ------------------------------------------------------------- Private Classes
717:
718: /**
719: * <p>Private class to represent the JavaServer Faces specific information
720: * that is saved and restored for a particular window.
721: */
722:
723: final class FacesPortletState {
724:
725: // Methods Saving and Restoring Messages
726: private Map messages = new HashMap(); // key=clientId, value=List of FacesMessage
727:
728: public void addMessage(String clientId, FacesMessage message) {
729: List list = (List) messages.get(clientId);
730: if (list == null) {
731: list = new ArrayList();
732: messages.put(clientId, list);
733: }
734: list.add(message);
735: }
736:
737: public Iterator getMessages(String clientId) {
738: List list = (List) messages.get(clientId);
739: if (list != null) {
740: return (list.iterator());
741: } else {
742: return (Collections.EMPTY_LIST.iterator());
743: }
744: }
745:
746: public StringBuffer getMessagesBuffer(String clientId) {
747: List list = (List) messages.get(clientId);
748: StringBuffer buffer = new StringBuffer();
749: if (list != null) {
750: Iterator messages = list.iterator();
751: FacesMessage message;
752: while (messages.hasNext()) {
753: message = (FacesMessage) messages.next();
754: buffer.append(" ");
755: buffer.append(message.getDetail());
756: }
757: }
758: return buffer;
759: }
760:
761: // Iterate over the client ids in this view
762: public Iterator getClientIds() {
763: return (messages.keySet().iterator());
764: }
765:
766: // The UIViewRoot that is the root of our component tree
767: private UIViewRoot viewRoot;
768:
769: public UIViewRoot getViewRoot() {
770: return this .viewRoot;
771: }
772:
773: public void setViewRoot(UIViewRoot viewRoot) {
774: this.viewRoot = viewRoot;
775: }
776:
777: }
|