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