dev@javaserverfaces.java.net

Seeking Review: 8-ViewLifecycle

From: Ed Burns <Ed.Burns_at_Sun.COM>
Date: Thu, 11 Nov 2004 08:23:41 -0800

Issue: 8-ViewLifecycle

PENDING(edburns): complete the RI for this.

SECTION: API Changes

M jsf-api/src/javax/faces/component/UIViewRoot.java

- add the following JavaBeans properties

  MethodBinding beforePhaseListener

  MethodBinding afterPhaseListener

  MethodBinding afterPhaseListener

- add the following JavaBeans listener

  PhaseListener

- modify encode{Begin,End}() to invoke the listeners as per spec.

M jsf-api/test/javax/faces/component/UIViewRootTestCase.java

- test the MethodBinding JavaBeans property, and PhaseListener JavaBeans
  listener, individually, and together, and with and without state saving.

M jsf-api/test/javax/faces/mock/MockExternalContext.java

- avoid throwing UnsupportedOperationException() when asked for the
  initParameter for the lifecycleid. Return null instead.

SECTION: API Diffs

Index: jsf-api/src/javax/faces/component/UIViewRoot.java
===================================================================
RCS file: /cvs/javaserverfaces-sources/jsf-api/src/javax/faces/component/UIViewRoot.java,v
retrieving revision 1.28
diff -u -r1.28 UIViewRoot.java
--- jsf-api/src/javax/faces/component/UIViewRoot.java 7 Apr 2004 17:36:58 -0000 1.28
+++ jsf-api/src/javax/faces/component/UIViewRoot.java 11 Nov 2004 16:07:24 -0000
@@ -15,13 +15,21 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import javax.faces.FactoryFinder;
+import javax.faces.FacesException;
+import javax.faces.lifecycle.LifecycleFactory;
+import javax.faces.lifecycle.Lifecycle;
 import javax.faces.context.FacesContext;
 import javax.faces.event.AbortProcessingException;
 import javax.faces.event.FacesEvent;
 import javax.faces.event.PhaseId;
+import javax.faces.event.PhaseEvent;
+import javax.faces.event.PhaseListener;
 import javax.faces.render.RenderKit;
 import javax.faces.render.RenderKitFactory;
 import javax.faces.el.ValueBinding;
+import javax.faces.el.MethodBinding;
+import javax.faces.webapp.FacesServlet;
 
 
 /**
@@ -170,6 +178,63 @@
 
     // ------------------------------------------------ Event Management Methods
 
+ private MethodBinding beforePhase = null;
+ private MethodBinding afterPhase = null;
+
+ /**
+ * @return the {_at_link MethodBinding} that will be invoked before
+ * this view is rendered.
+ *
+ */
+
+ public MethodBinding getBeforePhaseListener() {
+ return beforePhase;
+ }
+
+ /**
+ * @param newBeforePhase the {_at_link MethodBinding} that will be
+ * invoked before this view is rendered.
+ *
+ */
+
+ public void setBeforePhaseListener(MethodBinding newBeforePhase) {
+ beforePhase = newBeforePhase;
+ }
+
+ /**
+ * @return the {_at_link MethodBinding} that will be invoked after
+ * this view is rendered.
+ *
+ */
+
+ public MethodBinding getAfterPhaseListener() {
+ return afterPhase;
+ }
+
+ /**
+ * @param newAfterPhase the {_at_link MethodBinding} that will be
+ * invoked after this view is rendered.
+ *
+ */
+
+ public void setAfterPhaseListener(MethodBinding newAfterPhase) {
+ afterPhase = newAfterPhase;
+ }
+
+ private List phaseListeners = null;
+
+ public void removePhaseListener(PhaseListener toRemove) {
+ if (null != phaseListeners) {
+ phaseListeners.remove(toRemove);
+ }
+ }
+
+ public void addPhaseListener(PhaseListener newPhaseListener) {
+ if (null == phaseListeners) {
+ phaseListeners = new ArrayList();
+ }
+ phaseListeners.add(newPhaseListener);
+ }
 
     /**
      * <p>An array of Lists of events that have been queued for later
@@ -314,16 +379,112 @@
 
     /**
      * <p>Override the default {_at_link UIComponentBase#encodeBegin}
- * behavior to reset the mechanism used in {_at_link #createUniqueId}
- * before falling through to the standard superclass processing.</p>
+ * behavior. Reset the mechanism used in {_at_link #createUniqueId}
+ * before falling through to the standard superclass processing. If
+ * {_at_link #getBeforePhaseListener} returns non-<code>null</code>,
+ * invoke it, passing a {_at_link PhaseEvent} for the {_at_link
+ * PhaseId.RENDER_RESPONSE} phase. If the internal list populated
+ * by calls to {_at_link #addPhaseListener} is non-empty, any listeners
+ * in that list must have their {_at_link PhaseListener#beforePhase}
+ * method called, passing the <code>PhaseEvent</code>. Any errors
+ * that occur during invocation of any of the the beforePhase
+ * listeners must be logged and swallowed.</p>
      *
      */
 
     public void encodeBegin(FacesContext context) throws IOException {
         lastId = 0;
+
+ // avoid creating the PhaseEvent if possible by doing redundant
+ // null checks.
+ if (null != beforePhase || null != phaseListeners) {
+ PhaseEvent event = createPhaseEvent(context);
+ if (null != beforePhase) {
+ try {
+ beforePhase.invoke(context,
+ new Object [] { event });
+ }
+ catch (Exception e) {
+ // PENDING(edburns): log this
+ }
+ }
+ if (null != phaseListeners) {
+ Iterator iter = phaseListeners.iterator();
+ PhaseListener curListener = null;
+ while (iter.hasNext()) {
+ curListener = (PhaseListener) iter.next();
+ try {
+ curListener.beforePhase(event);
+ }
+ catch (Exception e) {
+ // PENDING(edburns): log this
+ }
+ }
+ }
+ }
+
         super.encodeBegin(context);
     }
 
+ /**
+ * <p>Override the default {_at_link UIComponentBase#encodeEnd}
+ * behavior. If {_at_link #getAfterPhaseListener} returns
+ * non-<code>null</code>, invoke it, passing a {_at_link PhaseEvent}
+ * for the {_at_link PhaseId.RENDER_RESPONSE} phase. Any errors that
+ * occur during invocation of the afterPhase listener must be
+ * logged and swallowed.</p>
+ */
+
+ public void encodeEnd(FacesContext context) throws IOException {
+ super.encodeEnd(context);
+
+ // avoid creating the PhaseEvent if possible by doing redundant
+ // null checks.
+ if (null != afterPhase || null != phaseListeners) {
+ PhaseEvent event = createPhaseEvent(context);
+ if (null != afterPhase) {
+ try {
+ afterPhase.invoke(context,
+ new Object [] { event });
+ }
+ catch (Exception e) {
+ // PENDING(edburns): log this
+ }
+ }
+ if (null != phaseListeners) {
+ Iterator iter = phaseListeners.iterator();
+ PhaseListener curListener = null;
+ while (iter.hasNext()) {
+ curListener = (PhaseListener) iter.next();
+ try {
+ curListener.afterPhase(event);
+ }
+ catch (Exception e) {
+ // PENDING(edburns): log this
+ }
+ }
+ }
+
+ }
+
+ }
+
+ private PhaseEvent createPhaseEvent(FacesContext context) throws FacesException {
+ Lifecycle lifecycle = null;
+ LifecycleFactory lifecycleFactory = (LifecycleFactory)
+ FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
+ String lifecycleId =
+ context.getExternalContext().getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR);
+ if (lifecycleId == null) {
+ lifecycleId = LifecycleFactory.DEFAULT_LIFECYCLE;
+ }
+ lifecycle = lifecycleFactory.getLifecycle(lifecycleId);
+
+ PhaseEvent result = new PhaseEvent(context, PhaseId.RENDER_RESPONSE,
+ lifecycle);
+ return result;
+ }
+
 
     /**
      * <p>Override the default {_at_link UIComponentBase#processValidators}
@@ -493,11 +654,14 @@
 
     public Object saveState(FacesContext context) {
 
- Object values[] = new Object[4];
+ Object values[] = new Object[7];
         values[0] = super.saveState(context);
         values[1] = renderKitId;
         values[2] = viewId;
         values[3] = locale;
+ values[4] = saveAttachedState(context, beforePhase);
+ values[5] = saveAttachedState(context, afterPhase);
+ values[6] = saveAttachedState(context, phaseListeners);
         return (values);
 
     }
@@ -510,6 +674,9 @@
         renderKitId = (String) values[1];
         viewId = (String) values[2];
         locale = (Locale)values[3];
+ beforePhase = (MethodBinding) restoreAttachedState(context, values[4]);
+ afterPhase = (MethodBinding) restoreAttachedState(context, values[5]);
+ phaseListeners = (List) restoreAttachedState(context, values[6]);
 
     }
 
Index: jsf-api/test/javax/faces/component/UIViewRootTestCase.java
===================================================================
RCS file: /cvs/javaserverfaces-sources/jsf-api/test/javax/faces/component/UIViewRootTestCase.java,v
retrieving revision 1.15
diff -u -r1.15 UIViewRootTestCase.java
--- jsf-api/test/javax/faces/component/UIViewRootTestCase.java 7 Apr 2004 17:39:27 -0000 1.15
+++ jsf-api/test/javax/faces/component/UIViewRootTestCase.java 11 Nov 2004 16:07:25 -0000
@@ -14,14 +14,19 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
+import javax.faces.FactoryFinder;
 import javax.faces.event.AbortProcessingException;
 import javax.faces.event.FacesEvent;
+import javax.faces.event.PhaseEvent;
+import javax.faces.event.PhaseListener;
+import javax.faces.event.PhaseId;
 import javax.faces.validator.Validator;
 import javax.faces.context.FacesContext;
 import javax.faces.component.UIComponent;
 import javax.faces.component.UIViewRoot;
 import javax.faces.event.PhaseId;
 import javax.faces.el.ValueBinding;
+import javax.faces.el.MethodBinding;
 import junit.framework.TestCase;
 import junit.framework.Test;
 import junit.framework.TestSuite;
@@ -67,6 +72,39 @@
 
     }
 
+ public static String FACTORIES[][] = {
+ { FactoryFinder.APPLICATION_FACTORY,
+ "javax.faces.mock.MockApplicationFactory"
+ },
+ { FactoryFinder.FACES_CONTEXT_FACTORY,
+ "javax.faces.mock.MockFacesContextFactory"
+ },
+ { FactoryFinder.LIFECYCLE_FACTORY,
+ "javax.faces.mock.MockLifecycleFactory"
+ },
+ { FactoryFinder.RENDER_KIT_FACTORY,
+ "javax.faces.mock.MockRenderKitFactory"
+ }
+ };
+
+ public void setUp() {
+ super.setUp();
+ for (int i = 0, len = FACTORIES.length; i < len; i++) {
+ System.getProperties().remove(FACTORIES[i][0]);
+ }
+
+ FactoryFinder.releaseFactories();
+ int len, i = 0;
+
+ // simulate the "faces implementation specific" part
+ for (i = 0, len = FACTORIES.length; i < len; i++) {
+ FactoryFinder.setFactory(FACTORIES[i][0],
+ FACTORIES[i][1]);
+ }
+
+
+ }
+
     /**
      * Tear down instance variables required by this test case.
      */
@@ -167,6 +205,103 @@
         
     }
 
+ public void testPhaseMethBinding() throws Exception {
+ UIViewRoot root = facesContext.getApplication().getViewHandler().createView(facesContext, null);
+ doTestPhaseMethodBinding(root);
+ }
+
+ public void testPhaseListener() throws Exception {
+ UIViewRoot root = facesContext.getApplication().getViewHandler().createView(facesContext, null);
+ doTestPhaseListener(root);
+ }
+
+ public void testPhaseMethodBindingAndListener() throws Exception {
+ UIViewRoot root = facesContext.getApplication().getViewHandler().createView(facesContext, null);
+ doTestPhaseMethodBindingAndListener(root);
+ }
+
+
+ public void testPhaseMethBindingState() throws Exception {
+ UIViewRoot root = facesContext.getApplication().getViewHandler().createView(facesContext, null);
+ Object state = root.saveState(facesContext);
+ root = facesContext.getApplication().getViewHandler().createView(facesContext, null);
+ root.restoreState(facesContext, state);
+
+ doTestPhaseMethodBinding(root);
+ }
+
+ public void testPhaseListenerState() throws Exception {
+ UIViewRoot root = facesContext.getApplication().getViewHandler().createView(facesContext, null);
+ Object state = root.saveState(facesContext);
+ root = facesContext.getApplication().getViewHandler().createView(facesContext, null);
+ root.restoreState(facesContext, state);
+
+ doTestPhaseListener(root);
+ }
+
+ public void testPhaseMethodBindingAndListenerState() throws Exception {
+ UIViewRoot root = facesContext.getApplication().getViewHandler().createView(facesContext, null);
+ Object state = root.saveState(facesContext);
+ root = facesContext.getApplication().getViewHandler().createView(facesContext, null);
+ root.restoreState(facesContext, state);
+
+ doTestPhaseMethodBindingAndListener(root);
+ }
+
+
+
+ public void doTestPhaseMethodBinding(UIViewRoot root) throws Exception {
+ PhaseListenerBean phaseListenerBean = new PhaseListenerBean();
+ facesContext.getExternalContext().getRequestMap().put("bean",
+ phaseListenerBean);
+ Class [] args = new Class [] { PhaseEvent.class };
+ MethodBinding
+ beforeBinding = facesContext.getApplication().createMethodBinding("#{bean.beforePhase}", args),
+ afterBinding = facesContext.getApplication().createMethodBinding("#{bean.afterPhase}", args);
+ root.setBeforePhaseListener(beforeBinding);
+ root.setAfterPhaseListener(afterBinding);
+ root.encodeBegin(facesContext);
+ root.encodeEnd(facesContext);
+ assertTrue(phaseListenerBean.isBeforePhaseCalled());
+ assertTrue(phaseListenerBean.isAfterPhaseCalled());
+
+
+ }
+
+ public void doTestPhaseListener(UIViewRoot root) throws Exception {
+ PhaseListenerBean phaseListener = new PhaseListenerBean();
+ root.addPhaseListener(phaseListener);
+ root.encodeBegin(facesContext);
+ root.encodeEnd(facesContext);
+ assertTrue(phaseListener.isBeforePhaseCalled());
+ assertTrue(phaseListener.isAfterPhaseCalled());
+
+
+ }
+
+ public void doTestPhaseMethodBindingAndListener(UIViewRoot root) throws Exception {
+ PhaseListenerBean phaseListener = new PhaseListenerBean();
+ PhaseListenerBean phaseListenerBean = new PhaseListenerBean();
+ facesContext.getExternalContext().getRequestMap().put("bean",
+ phaseListenerBean);
+ Class [] args = new Class [] { PhaseEvent.class };
+ MethodBinding
+ beforeBinding = facesContext.getApplication().createMethodBinding("#{bean.beforePhase}", args),
+ afterBinding = facesContext.getApplication().createMethodBinding("#{bean.afterPhase}", args);
+ root.setBeforePhaseListener(beforeBinding);
+ root.setAfterPhaseListener(afterBinding);
+ root.addPhaseListener(phaseListener);
+ root.encodeBegin(facesContext);
+ root.encodeEnd(facesContext);
+ assertTrue(phaseListenerBean.isBeforePhaseCalled());
+ assertTrue(phaseListenerBean.isAfterPhaseCalled());
+ assertTrue(phaseListener.isBeforePhaseCalled());
+ assertTrue(phaseListener.isAfterPhaseCalled());
+
+ }
+
+
+
 
     // --------------------------------------------------------- Support Methods
 
@@ -258,6 +393,32 @@
         vr.setRenderKitId("foo");
         vr.setViewId("bar");
         vr.setLocale(new Locale("fr", "FR"));
+ }
+
+ public static class PhaseListenerBean extends Object implements PhaseListener {
+ private boolean beforePhaseCalled = false;
+ private boolean afterPhaseCalled = false;
+
+ public PhaseListenerBean() {}
+
+ public boolean isBeforePhaseCalled() {
+ return beforePhaseCalled;
+ }
+
+ public boolean isAfterPhaseCalled() {
+ return afterPhaseCalled;
+ }
+
+ public void beforePhase(PhaseEvent e) {
+ beforePhaseCalled = true;
+ }
+
+ public void afterPhase(PhaseEvent e) {
+ afterPhaseCalled = true;
+ }
+
+ public PhaseId getPhaseId() { return PhaseId.RENDER_RESPONSE; }
+
     }
 
 
Index: jsf-api/test/javax/faces/mock/MockExternalContext.java
===================================================================
RCS file: /cvs/javaserverfaces-sources/jsf-api/test/javax/faces/mock/MockExternalContext.java,v
retrieving revision 1.13
diff -u -r1.13 MockExternalContext.java
--- jsf-api/test/javax/faces/mock/MockExternalContext.java 26 Feb 2004 20:31:51 -0000 1.13
+++ jsf-api/test/javax/faces/mock/MockExternalContext.java 11 Nov 2004 16:07:25 -0000
@@ -160,6 +160,9 @@
         if (name.equals(javax.faces.application.StateManager.STATE_SAVING_METHOD_PARAM_NAME)) {
             return null;
         }
+ if (name.equals(javax.faces.webapp.FacesServlet.LIFECYCLE_ID_ATTR)) {
+ return null;
+ }
         throw new UnsupportedOperationException();
     }
 



  

-- 
| ed.burns_at_sun.com  | {home: 407 294 2468, office: 408 884 9519 OR x31640}
| homepage:         | http://javaweb.sfbay.sun.com/~edburns/
| aim: edburns0sunw | iim: ed.burns_at_sun.com
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe_at_javaserverfaces.dev.java.net
For additional commands, e-mail: dev-help_at_javaserverfaces.dev.java.net