001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package com.sun.jsfcl.app;
043:
044: import java.io.IOException;
045: import java.util.ArrayList;
046: import java.util.Iterator;
047: import java.util.List;
048: import java.util.Locale;
049: import java.util.Map;
050:
051: import javax.faces.FacesException;
052: import javax.faces.FactoryFinder;
053: import javax.faces.application.ViewHandler;
054: import javax.faces.component.UIViewRoot;
055: import javax.faces.context.FacesContext;
056: import javax.faces.el.ValueBinding;
057: import javax.faces.event.PhaseEvent;
058: import javax.faces.event.PhaseId;
059: import javax.faces.event.PhaseListener;
060: import javax.faces.lifecycle.LifecycleFactory;
061:
062: /**
063: * <p>ViewHandler implementation that allows events to be triggered upon the
064: * occurrence of specific ViewHandler method calls. This implementation also
065: * posts relevant lifecycle events to initialized page beans, so it also
066: * implements <code>PhaseListener</code>.</p>
067: *
068: * <p>FIXME - may need to provide an implementation of Lifecycle as well,
069: * in order to ensure that afterRenderResponse works even in the face of
070: * exceptions thrown by application code.</p>
071: */
072:
073: public class ViewHandlerImpl extends ViewHandler implements
074: PhaseListener {
075:
076: // ------------------------------------------------------------ Constructors
077:
078: /**
079: * <p>Construct a new {@link ViewHandlerImpl} that delegates to the
080: * specified <code>ViewHandler</code> instance.</p>
081: *
082: * @param handler The ViewHandler instance to which we will delegate
083: */
084: public ViewHandlerImpl(ViewHandler handler) {
085:
086: this .handler = handler;
087: register();
088:
089: }
090:
091: // ------------------------------------------------------ Instance Variables
092:
093: /**
094: * <p>The ViewHandler instance to which we delegate operations.</p>
095: */
096: private ViewHandler handler = null;
097:
098: /**
099: * <p>The {@link PageBeanMapper} used to identify the page bean that
100: * corresponds to a view identifier. This instance is lazily instantiated,
101: * so use <code>getPageBeanMapper()</code> to acquire a reference.</p>
102: */
103: private PageBeanMapper mapper = null;
104:
105: // ------------------------------------------------------ Manifest Constants
106:
107: /**
108: * <p>The request scope attribute under which we store the view id
109: * when <code>createView()</code> is called.</p>
110: */
111: public static final String CREATED_VIEW = "com.sun.jsfcl.app.CREATED_VIEW"; //NOI18N
112:
113: /**
114: * <p>Request attribute key under which the {@link AbstractPageBean}
115: * for the view that will actually be rendered (if any) is stored.</p>
116: */
117: private static final String PAGE_BEAN_RENDERED = "com.sun.jsfcl.app.PAGE_BEAN_RENDERED"; //NOI18N
118:
119: /**
120: * <p>Request attribute key under which a <code>List</code> of the
121: * {@link AbstractPageBean}s that have been initialized for the current
122: * request are stored. Typically, there will be either one or two
123: * page beans on this list, depending on whether page navigation has
124: * taken place or not.</p>
125: */
126: private static final String PAGE_BEANS_INITIALIZED = "com.sun.jsfcl.app.PAGE_BEANS_INITIALIZED"; //NO18N
127:
128: // ----------------------------------------------------- ViewHandler Methods
129:
130: /**
131: * <p>Return an appropriate <code>Locale</code> to use for this
132: * and subsequent requests for the current client.</p>
133: *
134: * @param context <code>FacesContext</code> for the current request
135: *
136: * @exception NullPointerException if <code>context</code>
137: * is <code>null</code>
138: */
139: public Locale calculateLocale(FacesContext context) {
140:
141: Locale locale = handler.calculateLocale(context);
142: return locale;
143:
144: }
145:
146: /**
147: * <p>Return an appropriate <code>RenderKit</code> identifier
148: * for this and subsequent requests from the current
149: * client.
150: *
151: *
152: * @param context <code>FacesContext</code> for the current request
153: *
154: * @exception NullPointerException if <code>context</code>
155: * is <code>null</code>
156: */
157: public String calculateRenderKitId(FacesContext context) {
158:
159: String renderKitId = handler.calculateRenderKitId(context);
160: return renderKitId;
161:
162: }
163:
164: /**
165: * <p>Create and return a new <code>UIViewRoot</code> instance
166: * initialized with information from this <code>FacesContext</code>
167: * for the specified <code>viewId</code>.</p>
168: *
169: * @param context <code>FacesContext</code> for the current request
170: * @param viewId View identifier of the view to be created
171: *
172: * @exception NullPointerException if <code>context</code>
173: * or <code>viewId</code> is <code>null</code>
174: */
175: public UIViewRoot createView(FacesContext context, String viewId) {
176:
177: UIViewRoot viewRoot = handler.createView(context, viewId);
178: context.getExternalContext().getRequestMap().put(CREATED_VIEW,
179: viewId);
180: setupPageBean(context, viewRoot, false);
181: return viewRoot;
182:
183: }
184:
185: /**
186: * <p>Return a URL suitable for rendering that selects the
187: * specified view identifier.</p>
188: *
189: * @param context <code>FacesContext</code> for the current request
190: * @param viewId View identifier of the desired view
191: *
192: * @exception NullPointerException if <code>context</code>
193: * or <code>viewId</code> is <code>null</code>
194: */
195: public String getActionURL(FacesContext context, String viewId) {
196:
197: String url = handler.getActionURL(context, viewId);
198: return url;
199:
200: }
201:
202: /**
203: * <p>Return a URL suitable for rendering that selects the
204: * specified resource.</p>
205: *
206: * @param context <code>FacesContext</code> for the current request
207: * @param path Context-relative resource path to reference
208: *
209: * @exception NullPointerException if <code>context</code>
210: * or <code>path</code> is <code>null</code>
211: */
212: public String getResourceURL(FacesContext context, String path) {
213:
214: String url = handler.getResourceURL(context, path);
215: return url;
216:
217: }
218:
219: /**
220: * <p>Perform the necessary actions to render the specified view
221: * as part of the current response.</p>
222: *
223: * @param context <code>FacesContext</code> for the current request
224: * @param viewRoot View to be rendered
225: *
226: * @exception NullPointerException if <code>context</code>
227: * or <code>viewRoot</code> is <code>null</code>
228: */
229: public void renderView(FacesContext context, UIViewRoot viewRoot)
230: throws IOException, FacesException {
231:
232: handler.renderView(context, viewRoot);
233:
234: }
235:
236: /**
237: * <p>Perform necessary actions to restore the specified view
238: * and return a corresponding <code>UIViewRoot</code>. If there
239: * is no view information to be restored, return <code>null</code>.</p>
240: *
241: * @param context <code>FacesContext</code> for the current request
242: * @param viewId View identifier of the view to be restored
243: *
244: * @exception NullPointerException if <code>context</code>
245: * or <code>viewId</code> is <code>null</code>
246: */
247: public UIViewRoot restoreView(FacesContext context, String viewId) {
248:
249: UIViewRoot viewRoot = handler.restoreView(context, viewId);
250: setupPageBean(context, viewRoot, true);
251: return viewRoot;
252:
253: }
254:
255: /**
256: * <p>Take appropriate action to save the current state information.</p>
257: *
258: * @param context <code>FacesContext</code> for the current request
259: *
260: * @exception IOException if an input/output error occurs
261: * @exception NullPointerException if <code>context</code>
262: * is <code>null</code>
263: */
264: public void writeState(FacesContext context) throws IOException {
265:
266: handler.writeState(context);
267:
268: }
269:
270: // -------------------------------------------------- PhaseListener Methods
271:
272: /**
273: * <p>Return <code>PhaseId.ANY_PHASE</code> because we are interested
274: * in all phase events.</p>
275: */
276: public PhaseId getPhaseId() {
277: return PhaseId.ANY_PHASE;
278: }
279:
280: /**
281: * <p>Process the specified <em>before phase</em> event.</p>
282: *
283: * @param event <code>PhaseEvent</code> to be processed
284: */
285: public void beforePhase(PhaseEvent event) {
286:
287: PhaseId phaseId = event.getPhaseId();
288: FacesContext context = event.getFacesContext();
289: // System.out.println("beforePhase(" + phaseId + "," + context + ")");
290: if (PhaseId.RENDER_RESPONSE.equals(phaseId)) {
291: beforeRenderResponse(context);
292: }
293:
294: }
295:
296: /**
297: * <p>Process the specified <em>after phase</em> event.</p>
298: *
299: * @param event <code>PhaseEvent</code> to be processed
300: */
301: public void afterPhase(PhaseEvent event) {
302:
303: PhaseId phaseId = event.getPhaseId();
304: FacesContext context = event.getFacesContext();
305: // System.out.println("afterPhase(" + phaseId + "," + context + ")");
306: if (PhaseId.RESTORE_VIEW.equals(phaseId)) {
307: afterRestoreView(context);
308: } else if (PhaseId.RENDER_RESPONSE.equals(phaseId)
309: || context.getResponseComplete()) {
310: destroy(context);
311: }
312:
313: }
314:
315: // -------------------------------------------------------- Private Methods
316:
317: /**
318: * <p>Call the <code>preprocess()</code> method on any page bean that
319: * will be performing post back processing.</p>
320: *
321: * @param context <code>FacesContext</code> for the current request
322: */
323: private void afterRestoreView(FacesContext context) {
324:
325: Map map = context.getExternalContext().getRequestMap();
326: List list = (List) map.get(PAGE_BEANS_INITIALIZED);
327: if (list == null) {
328: return;
329: }
330: Iterator beans = list.iterator();
331: while (beans.hasNext()) {
332: AbstractPageBean bean = (AbstractPageBean) beans.next();
333: if (bean.isPostBack()) {
334: // CR 6255669 - Log and swallow thrown RuntimeException
335: try {
336: bean.preprocess();
337: } catch (RuntimeException e) {
338: context.getExternalContext().log(e.getMessage(), e);
339: }
340: }
341: }
342:
343: }
344:
345: /**
346: * <p>Call the <code>prerender()</code> method on any page bean that
347: * will be performing rendering.</p>
348: *
349: * @param context <code>FacesContext</code> for the current request
350: */
351: private void beforeRenderResponse(FacesContext context) {
352:
353: Map map = context.getExternalContext().getRequestMap();
354: AbstractPageBean pageBean = (AbstractPageBean) map
355: .get(PAGE_BEAN_RENDERED);
356: if (pageBean == null) {
357: return;
358: }
359: // CR 6255669 - Log and swallow any thrown RuntimeException
360: try {
361: pageBean.prerender();
362: } catch (RuntimeException e) {
363: context.getExternalContext().log(e.getMessage(), e);
364: }
365: map.remove(PAGE_BEAN_RENDERED);
366:
367: }
368:
369: /**
370: * <p>Call the <code>destroy()</code> method for any page bean that we
371: * have called <code>init()</code> for.</p>
372: *
373: * @param context <code>FacesContext</code> for the current request
374: */
375: private void destroy(FacesContext context) {
376:
377: Map map = context.getExternalContext().getRequestMap();
378: List list = (List) map.get(PAGE_BEANS_INITIALIZED);
379: if (list == null) {
380: return;
381: }
382: Iterator beans = list.iterator();
383: while (beans.hasNext()) {
384: AbstractPageBean bean = (AbstractPageBean) beans.next();
385: // CR 6255669 - Log and swallow any thrown RuntimeException
386: try {
387: bean.destroy();
388: } catch (RuntimeException e) {
389: context.getExternalContext().log(e.getMessage(), e);
390: }
391: }
392: map.remove(PAGE_BEANS_INITIALIZED);
393:
394: }
395:
396: /**
397: * <p>Return the {@link PageBeanMapper} we will use to map view identifiers
398: * to managed bean names of the corresponding page beans, instantiating a
399: * new instance if necessary. <strong>FIXME</strong> - make the actual
400: * implementation class to be used configurable.</p>
401: */
402: private PageBeanMapper getPageBeanMapper() {
403:
404: if (mapper == null) {
405: mapper = new PageBeanMapperImpl();
406: }
407: return mapper;
408:
409: }
410:
411: /**
412: * <p>Register this instance as a <code>PhaseListener</code> with the
413: * <code>Lifecycle</code> instance for this web application.</p>
414: */
415: private void register() {
416:
417: String lifecycleId = LifecycleFactory.DEFAULT_LIFECYCLE; // FIXME - override?
418: LifecycleFactory factory = (LifecycleFactory) FactoryFinder
419: .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
420: factory.getLifecycle(lifecycleId).addPhaseListener(this );
421:
422: }
423:
424: /**
425: * <p>Create and initialize an appropriate {@link AbstractPageBean}
426: * associated with the specified view, which was just created or restored.
427: *
428: * @param context <code>FacesContext</code> for the current request
429: * @param view <code>UIViewRoot</code> just created or restored
430: * (or <code>null</code> if there was no such view)
431: * @param postBack <code>true</code> if this is a post back to
432: * an existing view
433: */
434: private void setupPageBean(FacesContext context, UIViewRoot view,
435: boolean postBack) {
436:
437: // Is there actually a view for us to process?
438: if (view == null) {
439: return;
440: }
441:
442: // Map the view identifier to the corresponding page bean name
443: String viewId = view.getViewId();
444: // System.out.println("setupPageBean(" + viewId + "," + postBack + ")");
445: String viewName = getPageBeanMapper().mapViewId(viewId);
446: if (viewName == null) {
447: // System.out.println(" WARNING: no page bean for " + viewId);
448: return;
449: }
450: // System.out.println(" Mapped to page bean " + viewName);
451:
452: // Retrieve or create a corresponding page bean instance
453: ValueBinding vb = context.getApplication().createValueBinding(
454: "#{" + viewName + "}"); //NOI18N
455: AbstractPageBean pageBean = null;
456: try {
457: pageBean = (AbstractPageBean) vb.getValue(context);
458: } catch (ClassCastException e) {
459: // System.out.println(" WARNING: Bean for " + viewId + " is not a page bean");
460: return;
461: }
462: if (pageBean == null) {
463: // System.out.println(" WARNING: No page bean for " + viewId);
464: return;
465: }
466:
467: // Configure this instance, and schedule it for later event processing
468: // CR 6255669 - Log and swallow any thrown RuntimeException
469: try {
470: pageBean.init();
471: } catch (RuntimeException e) {
472: context.getExternalContext().log(e.getMessage(), e);
473: }
474: Map map = context.getExternalContext().getRequestMap();
475: map.put(PAGE_BEAN_RENDERED, pageBean);
476: List list = (List) map.get(PAGE_BEANS_INITIALIZED);
477: if (list == null) {
478: list = new ArrayList(2);
479: map.put(PAGE_BEANS_INITIALIZED, list);
480: }
481: list.add(pageBean);
482:
483: }
484:
485: }
|