dev@javaserverfaces.java.net

Re: Seeking Review: 8-ViewLifecycle

From: Adam Winer <adam.winer_at_oracle.com>
Date: Thu, 11 Nov 2004 08:50:30 -0800

Ed,

Shouldn't we be calling all the UIViewRoot phase listener
on all phases, not just encodeBegin() and encodeEnd()?

Also, it would greatly simplify the resulting code if
we pulled this requirement out of UIViewRoot and into
LifecycleImpl.

-- Adam


Ed Burns wrote:
> 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();
> }
>
>
>
>
>
>


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe_at_javaserverfaces.dev.java.net
For additional commands, e-mail: dev-help_at_javaserverfaces.dev.java.net