dev@jsf-extensions.java.net

Seeking Review: Change Dynamic Faces to not override UIViewRoot encodeAll()

From: Ken Paulsen <Ken.Paulsen_at_Sun.COM>
Date: Sat, 23 Dec 2006 11:52:27 -0800

An explanation of the issue is here:

    https://jsf-extensions.dev.java.net/issues/show_bug.cgi?id=35

Bottom line is to change DF to use encodeBegin / encodeEnd vs. encodeAll
to be compatible with JSF 1.1 ViewHandlers and to allow ViewHandlers
more fine-grained control over rendering.

diffs attached.

Thanks!

Ken Paulsen
ken.paulsen_at_sun.com
https://jsftemplating.dev.java.net


Index: src/main/java/com/sun/faces/extensions/avatar/lifecycle/PartialTraversalLifecycle.java
===================================================================
--- src/main/java/com/sun/faces/extensions/avatar/lifecycle/PartialTraversalLifecycle.java (revision 335)
+++ src/main/java/com/sun/faces/extensions/avatar/lifecycle/PartialTraversalLifecycle.java (working copy)
@@ -100,8 +100,10 @@
 
         try {
 
- // Don't allow any content between now and the call
- // to PartialTraversalViewRoot.encodeAll() to be written to the response.
+ // Don't allow any content between now and the call to
+ // PartialTraversalViewRoot.encodeBegin() through
+ // PartialTraversalViewRoot.encodeEnd() to be written to the
+ // response.
             async.setOnOffResponseEnabled(false);
             parent.render(context);
             
Index: src/main/java/com/sun/faces/extensions/avatar/components/PartialTraversalViewRoot.java
===================================================================
--- src/main/java/com/sun/faces/extensions/avatar/components/PartialTraversalViewRoot.java (revision 335)
+++ src/main/java/com/sun/faces/extensions/avatar/components/PartialTraversalViewRoot.java (working copy)
@@ -236,85 +236,126 @@
         this.broadcastEvents(context, PhaseId.UPDATE_MODEL_VALUES);
     }
 
- public void encodeAll(FacesContext context) throws IOException {
+ /**
+ *
+ */
+ public void encodeBegin(FacesContext context) throws IOException {
         AsyncResponse async = AsyncResponse.getInstance();
         // If this is not an ajax request...
         if (!async.isAjaxRequest()) {
             // do default behavior
- super.encodeAll(context);
+ super.encodeBegin(context);
             return;
         }
- boolean renderAll = async.isRenderAll(),
- renderNone = async.isRenderNone();
- ResponseWriter orig = null, writer = null;
+
+ boolean renderAll = async.isRenderAll();
+ ResponseWriter orig = null;
         
         try {
             // Turn on the response that has been embedded in the ViewHandler.
             async.setOnOffResponseEnabled(true);
+
+ // Save the original writer for encodeEnd
+ orig = context.getResponseWriter();
+ context.getExternalContext().getRequestMap().
+ put(ORIGINAL_WRITER, orig);
+
             // If this is an ajax request, and it is a partial render request...
-
             if (!renderAll) {
                 // replace the context's responseWriter with the AjaxResponseWriter
                 // Get (and maybe create) the AjaxResponseWriter
- writer = async.getResponseWriter();
- orig = context.getResponseWriter();
- // Install the AjaxResponseWriter
- context.setResponseWriter(writer);
+ context.setResponseWriter(async.getResponseWriter());
             }
             
             this.encodePartialResponseBegin(context);
 
             if (renderAll) {
- writer = context.getResponseWriter();
                 // If this is a "render all via ajax" request,
                 // make sure to wrap the entire page in a <render> elemnt
                 // with the special id of VIEW_ROOT_ID. This is how the client
                 // JavaScript knows how to replace the entire document with
                 // this response.
- writer.startElement("render", this);
- writer.writeAttribute("id", AsyncResponse.VIEW_ROOT_ID, "id");
- writer.startElement("markup", this);
- writer.write("<![CDATA[");
+ orig.startElement("render", this);
+ orig.writeAttribute("id", AsyncResponse.VIEW_ROOT_ID, "id");
+ orig.startElement("markup", this);
+ orig.write("<![CDATA[");
                 
                 // setup up a writer which will escape any CDATA sections
- context.setResponseWriter(new EscapeCDATAWriter(writer));
+ context.setResponseWriter(new EscapeCDATAWriter(orig));
                 
                 // do the default behavior...
- super.encodeAll(context);
+ super.encodeBegin(context);
+ }
+ } catch (IOException ex) {
+// FIXME: The old code silently ignored the IOException, is this desirable?
+ cleanupAfterView(context, orig);
+ } catch (RuntimeException ex) {
+ cleanupAfterView(context, orig);
+
+ // Throw the exception
+ throw ex;
+ }
+ }
+
+ /**
+ *
+ */
+ public void encodeChildren(FacesContext context) throws IOException {
+ AsyncResponse async = AsyncResponse.getInstance(false);
+ // If this is not an ajax request...
+ if ((async == null) || !async.isAjaxRequest() || async.isRenderAll()) {
+ // Full request, operate normally
+ super.encodeChildren(context);
+ }
+ }
+
+ /**
+ *
+ */
+ public void encodeEnd(FacesContext context) throws IOException {
+ AsyncResponse async = AsyncResponse.getInstance(false);
+ // If this is not an ajax request...
+ if ((async == null) || !async.isAjaxRequest()) {
+ // do default behavior
+ super.encodeEnd(context);
+ return;
+ }
+
+ // Get the original response writer...
+ ResponseWriter orig = (ResponseWriter) context.getExternalContext().
+ getRequestMap().get(ORIGINAL_WRITER);
+ if (orig == null) {
+ // This shouldn't happen... but just in case...
+ orig = context.getResponseWriter();
+ }
+
+ try {
+ if (async.isRenderAll()) {
+ // do the default behavior...
+ super.encodeEnd(context);
                 
                 // revert the write and finish up
- context.setResponseWriter(writer);
- writer.write("]]>");
- writer.endElement("markup");
- writer.endElement("render");
- // then bail out.
- return;
- }
-
- // If the context callback was not invoked on any subtrees
- // and the client did not explicitly request that no subtrees be rendered...
- if (!invokeContextCallbackOnSubtrees(context,
- new PhaseAwareContextCallback(PhaseId.RENDER_RESPONSE)) &&
- !renderNone) {
- assert(false);
- }
-
- this.encodePartialResponseEnd(context);
-
+ context.setResponseWriter(orig);
+ orig.write("]]>");
+ orig.endElement("markup");
+ orig.endElement("render");
+ orig = null;
+ } else {
+
+ // If the context callback was not invoked on any subtrees
+ // and the client did not explicitly request that no subtrees be rendered...
+ if (!invokeContextCallbackOnSubtrees(context,
+ new PhaseAwareContextCallback(PhaseId.RENDER_RESPONSE)) &&
+ !async.isRenderNone()) {
+ assert(false);
+ }
+
+ this.encodePartialResponseEnd(context);
+ }
         } catch (IOException ioe) {
-
+// FIXME: Should this be ignored as it currently is doing?
         } finally {
- // PENDING(edburns): this is a big hack to get around the
- // way the JSP based faces implementation handles the
- // after view content.
- // We will have to do something different for other implementations.
- // This is not a problem for Facelets.
- context.getExternalContext().getRequestMap().remove("com.sun.faces.AFTER_VIEW_CONTENT");
-
- // move aside the AjaxResponseWriter
- if (null != orig) {
- context.setResponseWriter(orig);
- }
+ cleanupAfterView(context, orig);
         }
     }
     
@@ -377,6 +418,23 @@
         
     }
     
+ /**
+ *
+ */
+ private void cleanupAfterView(FacesContext context, ResponseWriter orig) {
+ // PENDING(edburns): this is a big hack to get around the
+ // way the JSP based faces implementation handles the
+ // after view content.
+ // We will have to do something different for other implementations.
+ // This is not a problem for Facelets.
+ context.getExternalContext().getRequestMap().remove("com.sun.faces.AFTER_VIEW_CONTENT");
+
+ // move aside the AjaxResponseWriter
+ if (null != orig) {
+ context.setResponseWriter(orig);
+ }
+ }
+
     private boolean invokeContextCallbackOnSubtrees(FacesContext context,
             PhaseAwareContextCallback cb) {
         AsyncResponse async = AsyncResponse.getInstance();
@@ -488,4 +546,9 @@
         }
     }
     
+ /**
+ * <p> Request scoped key to hold the original ResponseWriter between
+ * encodeBegin and encodeEnd during Ajax requests.</p>
+ */
+ public static final String ORIGINAL_WRITER = AsyncResponse.FACES_PREFIX + "origWriter";
 }