/* * $Id: PartialTraversalViewRoot.java,v 1.4 2005/12/21 22:38:09 edburns Exp $ */ /* * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * https://javaserverfaces.dev.java.net/CDDL.html or * legal/CDDLv1.0.txt. * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at legal/CDDLv1.0.txt. * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * [Name of File] [ver.__] [Date] * * Copyright 2005 Sun Microsystems Inc. All Rights Reserved */ package com.sun.faces.extensions.avatar.components; import com.sun.faces.extensions.avatar.event.EventParser; import com.sun.faces.extensions.avatar.lifecycle.AsyncResponse; import com.sun.faces.extensions.common.util.Util; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import javax.faces.FacesException; import javax.faces.component.ActionSource; import javax.faces.component.ContextCallback; import javax.faces.component.EditableValueHolder; import javax.faces.component.UIComponent; import javax.faces.component.UIViewRoot; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.context.ResponseWriterWrapper; import javax.faces.convert.ConverterException; import javax.faces.event.PhaseId; import javax.servlet.http.HttpServletResponse; /** * *
Request Processing Lifecycle Operation
* * This class contains the implementation details needed for a
* {@link PartialTraversalViewRoot} implementation. On initial request,
* it provides behavior that is the same as a standard
* UIViewRoot, delegating all behavior to the UIViewRoot.
* However, since it is unable to call viewRoot.super.method(), it instead
* returns true from methods that should delegate this. So it is up to
* the caller to correctly invoke super.method() in these cases.
On postback, the {@link #processDecodes}, {@link #processValidators},
* and {@link #processUpdates} methods have special behaviour. If {@link
* com.sun.faces.extensions.avatar.lifecycle.AsyncResponse#isAjaxRequest}
* returns false, the UIViewRoot superclass implementation of
* the respective method is called. Otherwise,
* {@link com.sun.faces.extensions.avatar.lifecycle.AsyncResponse#getExecuteSubtrees}
* is called. This returns a list of client ids suitable for the
* execute portion of the request processing lifecycle. If
* the list is empty, call through to the UIViewRoot superclass
* implementation of the respective method. Otherwise, for each client id
* in the list, using invokeOnComponent, call the respective
* process* method on the component with that clientId.
* After all the client ids in the list have been handled for the current
* phase, reflectively call broadcastEvents() on the
* superclass passing the current PhaseId.
On postback, the {@link #encodeBegin}, {@link #encodeChildren} and
* {@link #encodeEnd} methods have special behavior. If {@link
* com.sun.faces.extensions.avatar.lifecycle.AsyncResponse#isAjaxRequest}
* returns false, the UIViewRoot superclass
* encode* methods are called. Otherwise, {@link
* com.sun.faces.extensions.avatar.lifecycle.AsyncResponse#getRenderSubtrees}
* is called. This returns a list of client ids suitable for the
* {@link javax.faces.lifecycle.Lifecycle#render} portion of the request
* processing lifecycle. If the list is empty, call through to the
* UIViewRoot superclass implementation of encode*.
* Otherwise, for each client id in the list, using {@link
* javax.faces.component.UIComponent#invokeOnComponent}, call the
* encode* methods on the component with that clientId. If
* the list of subtrees to render for this request is non-empty, set the
* response content-type and headers approriately for XML, and wrap the
* rendered output from the components in the list as in the following
* example.
<partial-response>
<components>
<render id="form:table"/>
<markup><![CDATA[
Rendered content from component
]]></markup>
<messages>
<message>The messages element is optional. If present,
it is a list of FacesMessage.getSummary() output
</message>
</messages>
</render>
<!-- repeat for the appropriate number of components -->
</components>
<state><![CDATA[state information for this view ]]></state>
</partial-response>
*
* If the isRenderXML value is false, assume
* the renderers are handling the content-type, header, and component
* encapsulation details and write nothing to the response.
*
* See {@link
* com.sun.faces.extensions.avatar.lifecycle.PartialTraversalLifecycle} for
* additional information about how this class helps
* PartialTraversalViewRoot get its job done.
This class uses a hack to make the
* UIViewRoot.broadcastEvents method public. This is
* necessary to allow easy broadcasting events during the AJAX
* lifecycle.
Get the cooresponding PartialTraversalViewRoot.
*/ protected PartialTraversalViewRoot getUIViewRoot() { return _viewRoot; } /** *Set the cooresponding PartialTraversalViewRoot.
*/ protected void setUIViewRoot(PartialTraversalViewRoot viewRoot) { _viewRoot = viewRoot; } /** * */ public void postExecuteCleanup(FacesContext context) { for (UIComponent comp : modifiedComponents) { if (comp instanceof ActionSource) { ActionSource as = (ActionSource)comp; assert(as.isImmediate()); as.setImmediate(false); } else if (comp instanceof EditableValueHolder) { EditableValueHolder ev = (EditableValueHolder)comp; assert(ev.isImmediate()); ev.setImmediate(false); } } modifiedComponents.clear(); } /** * * @return true If super.processDecodes() should be called. */ public boolean processDecodes(FacesContext context) { boolean invokedCallback = false; UIComponent comp = (UIComponent) getUIViewRoot(); if (!AsyncResponse.isAjaxRequest()) { // Invoke the "super" return true; } AsyncResponse async = AsyncResponse.getInstance(); modifiedComponents.clear(); // If this is an immediate "render all" request and there are // no explicit execute subtrees and the user didn't request // execute: none... if (async.isImmediateAjaxRequest() && async.isRenderAll() && async.getExecuteSubtrees().isEmpty() && !async.isExecuteNone()) { // Traverse the entire view and mark every ActionSource or // EditableValueHolder as immediate. Util.prefixViewTraversal(context, comp, markImmediate); } invokedCallback = invokeContextCallbackOnSubtrees(context, new PhaseAwareContextCallback(PhaseId.APPLY_REQUEST_VALUES)); // Queue any events for this request in a context aware manner ResponseWriter writer = null; try { writer = async.getResponseWriter(); } catch (IOException ex) { throw new FacesException(ex); } // Install the AjaxResponseWriter context.setResponseWriter(writer); EventParser.queueFacesEvents(context); if (!invokedCallback) { comp.processDecodes(context); } this.broadcastEvents(context, PhaseId.APPLY_REQUEST_VALUES); return false; } /** * * @return true If super.processValidators() should be called. */ public boolean processValidators(FacesContext context) { if (!AsyncResponse.isAjaxRequest() || !invokeContextCallbackOnSubtrees(context, new PhaseAwareContextCallback(PhaseId.PROCESS_VALIDATIONS))) { return true; } this.broadcastEvents(context, PhaseId.PROCESS_VALIDATIONS); return false; } /** * * @return true If super.processUpdates() should be called. */ public boolean processUpdates(FacesContext context) { if (!AsyncResponse.isAjaxRequest() || !invokeContextCallbackOnSubtrees(context, new PhaseAwareContextCallback(PhaseId.UPDATE_MODEL_VALUES))) { return true; } this.broadcastEvents(context, PhaseId.UPDATE_MODEL_VALUES); return false; } /** * * @param Value The value if this is not an AjaxRequest. */ public boolean getRendersChildren(boolean value) { // If this is not an ajax request... AsyncResponse async = AsyncResponse.getInstance(); if (async.isAjaxRequest() && !async.isRenderAll()) { // do Dynamic Faces behavior value = true; } return value; } /** *Request scoped key to hold the original ResponseWriter between * encodeBegin and encodeEnd during Ajax requests.
*/ private static final String ORIGINAL_WRITER = AsyncResponse.FACES_PREFIX + "origWriter"; /** * * @return true If super.encodeBegin() should be called. */ public boolean encodeBegin(FacesContext context) throws IOException { AsyncResponse async = AsyncResponse.getInstance(); PartialTraversalViewRoot root = getUIViewRoot(); // If this is not an ajax request... if (!async.isAjaxRequest()) { // do default behavior return true; } boolean renderAll = async.isRenderAll(), renderNone = async.isRenderNone(); ResponseWriter orig = null, writer = null; try { // Turn on the response that has been embedded in the ViewHandler. async.setOnOffResponseEnabled(true); // 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(); // Save the new writer for encodeEnd context.getExternalContext().getRequestMap(). put(ORIGINAL_WRITER, orig); // Install the AjaxResponseWriter context.setResponseWriter(writer); } root.encodePartialResponseBegin(context); if (renderAll) { writer = context.getResponseWriter(); // If this is a "render all via ajax" request, // make sure to wrap the entire page in aThis method returns a publicly usable broadcastEvents method.
*/ private Method getBroadcastEventsMethod() { if (broadcastEvents == null) { try { synchronized (this) { broadcastEvents = UIViewRoot.class.getDeclaredMethod( "broadcastEvents", FacesContext.class, PhaseId.class); broadcastEvents.setAccessible(true); } } catch (NoSuchMethodException ex) { throw new RuntimeException( "broadcastEvents method not found on UIViewRoot!", ex); } } return broadcastEvents; } private class PhaseAwareContextCallback implements ContextCallback { private PhaseId curPhase = null; private PhaseAwareContextCallback(PhaseId curPhase) { this.curPhase = curPhase; } private PhaseId getPhaseId() { return curPhase; } public void invokeContextCallback(FacesContext facesContext, UIComponent comp) { try { ConverterException converterException = null; if (curPhase == PhaseId.APPLY_REQUEST_VALUES) { // If the user requested an immediate request // Make sure to set the immediate flag. if (AsyncResponse.isImmediateAjaxRequest()) { PartialTraversalViewRootHelper.this.markImmediate.takeActionOnNode(facesContext, comp); } comp.processDecodes(facesContext); } else if (curPhase == PhaseId.PROCESS_VALIDATIONS) { comp.processValidators(facesContext); } else if (curPhase == PhaseId.UPDATE_MODEL_VALUES) { comp.processUpdates(facesContext); } else if (curPhase == PhaseId.RENDER_RESPONSE) { if (comp.isRendered()) { ResponseWriter writer = AsyncResponse.getInstance().getResponseWriter(); writer.startElement("render", comp); writer.writeAttribute("id", comp.getClientId(facesContext), "id"); try { writer.startElement("markup", comp); writer.write(""); writer.endElement("markup"); } catch (ConverterException ce) { converterException = ce; } writer.endElement("render"); } } else { throw new IllegalStateException("I18N: Unexpected PhaseId passed to PhaseAwareContextCallback: " + curPhase.toString()); } } catch (IOException ex) { ex.printStackTrace(); } } } private class EscapeCDATAWriter extends ResponseWriterWrapper { private ResponseWriter toWrap = null; public EscapeCDATAWriter(ResponseWriter toWrap) { this.toWrap = toWrap; } protected ResponseWriter getWrapped() { return toWrap; } public void write(String string) throws IOException { super.write(string.replace("]]>", "]]]]>")); } } private PartialTraversalViewRoot _viewRoot = null; // FIXME: The following 2 are initialized in the constructor... this class is // serializable, these are transient, they should NOT be initialized // in the constructor: private transient List