All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.faces.context.PartialViewContextImpl Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package com.sun.faces.context;

import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.PARTIAL_EXECUTE_PARAM;
import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.PARTIAL_RENDER_PARAM;
import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.PARTIAL_RESET_VALUES_PARAM;
import static jakarta.faces.FactoryFinder.VISIT_CONTEXT_FACTORY;
import static jakarta.faces.component.visit.VisitHint.EXECUTE_LIFECYCLE;
import static jakarta.faces.component.visit.VisitHint.SKIP_UNRENDERED;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.WARNING;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sun.faces.RIConstants;
import com.sun.faces.component.visit.PartialVisitContext;
import com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter;
import com.sun.faces.util.FacesLogger;
import com.sun.faces.util.HtmlUtils;
import com.sun.faces.util.Util;

import jakarta.faces.FacesException;
import jakarta.faces.FactoryFinder;
import jakarta.faces.application.ResourceHandler;
import jakarta.faces.component.EditableValueHolder;
import jakarta.faces.component.NamingContainer;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.UIViewRoot;
import jakarta.faces.component.visit.VisitCallback;
import jakarta.faces.component.visit.VisitContext;
import jakarta.faces.component.visit.VisitContextFactory;
import jakarta.faces.component.visit.VisitContextWrapper;
import jakarta.faces.component.visit.VisitHint;
import jakarta.faces.component.visit.VisitResult;
import jakarta.faces.context.ExternalContext;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.PartialResponseWriter;
import jakarta.faces.context.PartialViewContext;
import jakarta.faces.context.ResponseWriter;
import jakarta.faces.event.PhaseId;
import jakarta.faces.lifecycle.ClientWindow;
import jakarta.faces.render.RenderKit;
import jakarta.faces.render.RenderKitFactory;

public class PartialViewContextImpl extends PartialViewContext {

    // Log instance for this class
    private static final Logger LOGGER = FacesLogger.CONTEXT.getLogger();

    private static final Set SKIP_UNRENDERED_HINT = EnumSet.of(SKIP_UNRENDERED);

    private static final Set SKIP_UNRENDERED_AND_EXECUTE_LIFECYCLE_HINTS = EnumSet.of(SKIP_UNRENDERED, EXECUTE_LIFECYCLE);


    private boolean released;

    // BE SURE TO ADD NEW IVARS TO THE RELEASE METHOD
    private PartialResponseWriter partialResponseWriter;
    private List executeIds;
    private Collection renderIds;
    private List evalScripts;
    private Boolean ajaxRequest;
    private Boolean partialRequest;
    private Boolean renderAll;
    private FacesContext ctx;

    private static final String ORIGINAL_WRITER = "com.sun.faces.ORIGINAL_WRITER";

    // ----------------------------------------------------------- Constructors

    public PartialViewContextImpl(FacesContext ctx) {
        this.ctx = ctx;
    }

    // ---------------------------------------------- Methods from PartialViewContext

    /**
     * @see jakarta.faces.context.PartialViewContext#isAjaxRequest()
     */
    @Override
    public boolean isAjaxRequest() {

        assertNotReleased();
        if (ajaxRequest == null) {
            ajaxRequest = "partial/ajax".equals(ctx.getExternalContext().getRequestHeaderMap().get("Faces-Request"));
            if (!ajaxRequest) {
                ajaxRequest = "partial/ajax".equals(ctx.getExternalContext().getRequestParameterMap().get("Faces-Request"));
            }
        }
        return ajaxRequest;

    }

    /**
     * @see jakarta.faces.context.PartialViewContext#isPartialRequest()
     */
    @Override
    public boolean isPartialRequest() {

        assertNotReleased();
        if (partialRequest == null) {
            partialRequest = isAjaxRequest() || "partial/process".equals(ctx.getExternalContext().getRequestHeaderMap().get("Faces-Request"));
        }
        return partialRequest;

    }

    /**
     * @see jakarta.faces.context.PartialViewContext#isExecuteAll()
     */
    @Override
    public boolean isExecuteAll() {

        assertNotReleased();
        String execute = PARTIAL_EXECUTE_PARAM.getValue(ctx);
        return ALL_PARTIAL_PHASE_CLIENT_IDS.equals(execute);

    }

    /**
     * @see jakarta.faces.context.PartialViewContext#isRenderAll()
     */
    @Override
    public boolean isRenderAll() {

        assertNotReleased();
        if (renderAll == null) {
            String render = PARTIAL_RENDER_PARAM.getValue(ctx);
            renderAll = ALL_PARTIAL_PHASE_CLIENT_IDS.equals(render);
        }

        return renderAll;

    }

    /**
     * @see jakarta.faces.context.PartialViewContext#setRenderAll(boolean)
     */
    @Override
    public void setRenderAll(boolean renderAll) {

        this.renderAll = renderAll;

    }

    @Override
    public boolean isResetValues() {
        Object value = PARTIAL_RESET_VALUES_PARAM.getValue(ctx);
        return Boolean.TRUE.toString().equals(value);
    }

    @Override
    public void setPartialRequest(boolean isPartialRequest) {
        partialRequest = isPartialRequest;
    }

    /**
     * @see jakarta.faces.context.PartialViewContext#getExecuteIds()
     */
    @Override
    public Collection getExecuteIds() {

        assertNotReleased();
        if (executeIds != null) {
            return executeIds;
        }
        executeIds = populatePhaseClientIds(PARTIAL_EXECUTE_PARAM);

        // include the view parameter facet ID if there are other execute IDs
        // to process
        if (!executeIds.isEmpty()) {
            UIViewRoot root = ctx.getViewRoot();
            if (root.getFacetCount() > 0) {
                if (root.getFacet(UIViewRoot.METADATA_FACET_NAME) != null) {
                    executeIds.add(0, UIViewRoot.METADATA_FACET_NAME);
                }
            }
        }
        return executeIds;

    }

    /**
     * @see jakarta.faces.context.PartialViewContext#getRenderIds()
     */
    @Override
    public Collection getRenderIds() {

        assertNotReleased();
        if (renderIds != null) {
            return renderIds;
        }
        renderIds = populatePhaseClientIds(PARTIAL_RENDER_PARAM);
        return renderIds;

    }

    /**
     * @see jakarta.faces.context.PartialViewContext#getEvalScripts()
     */
    @Override
    public List getEvalScripts() {
        assertNotReleased();

        if (evalScripts == null) {
            evalScripts = new ArrayList<>(1);
        }

        return evalScripts;
    }

    /**
     * @see PartialViewContext#processPartial(jakarta.faces.event.PhaseId)
     */
    @Override
    public void processPartial(PhaseId phaseId) {
        PartialViewContext pvc = ctx.getPartialViewContext();
        Collection myExecuteIds = pvc.getExecuteIds();
        Collection myRenderIds = pvc.getRenderIds();
        UIViewRoot viewRoot = ctx.getViewRoot();

        if (phaseId == PhaseId.APPLY_REQUEST_VALUES || phaseId == PhaseId.PROCESS_VALIDATIONS || phaseId == PhaseId.UPDATE_MODEL_VALUES) {

            // Skip this processing if "none" is specified in the render list,
            // or there were no execute phase client ids.

            if (myExecuteIds == null || myExecuteIds.isEmpty()) {
                if (LOGGER.isLoggable(Level.FINE)) {
                    LOGGER.log(Level.FINE, "No execute and render identifiers specified.  Skipping component processing.");
                }
                return;
            }

            try {
                processComponents(viewRoot, phaseId, myExecuteIds, ctx);
            } catch (Exception e) {
                if (LOGGER.isLoggable(Level.INFO)) {
                    LOGGER.log(Level.INFO, e.toString(), e);
                }
                throw new FacesException(e);
            }

            // If we have just finished APPLY_REQUEST_VALUES phase, install the
            // partial response writer. We want to make sure that any content
            // or errors generated in the other phases are written using the
            // partial response writer.
            //
            if (phaseId == PhaseId.APPLY_REQUEST_VALUES) {
                PartialResponseWriter writer = pvc.getPartialResponseWriter();
                ctx.setResponseWriter(writer);
            }

        } else if (phaseId == PhaseId.RENDER_RESPONSE) {

            try {
                //
                // We re-enable response writing.
                //
                PartialResponseWriter writer = pvc.getPartialResponseWriter();
                ResponseWriter orig = ctx.getResponseWriter();
                ctx.getAttributes().put(ORIGINAL_WRITER, orig);
                ctx.setResponseWriter(writer);

                ExternalContext exContext = ctx.getExternalContext();
                exContext.setResponseContentType(RIConstants.TEXT_XML_CONTENT_TYPE);
                exContext.addResponseHeader("Cache-Control", "no-cache");

                writer.startDocument();

                if (isResetValues()) {
                    resetValues(viewRoot, myRenderIds, ctx);
                }

                if (isRenderAll()) {
                    renderAll(ctx, viewRoot);
                    renderState(ctx);
                    doFlashPostPhaseActions(ctx);
                    writer.endDocument();
                    return;
                }

                renderComponentResources(ctx, viewRoot);

                // Skip this processing if "none" is specified in the render list,
                // or there were no render phase client ids.
                if (myRenderIds != null && !myRenderIds.isEmpty()) {
                    processComponents(viewRoot, phaseId, myRenderIds, ctx);
                }

                renderState(ctx);
                renderEvalScripts(ctx);
                doFlashPostPhaseActions(ctx);

                writer.endDocument();
            } catch (IOException ex) {
                cleanupAfterView();
            } catch (RuntimeException ex) {
                cleanupAfterView();
                // Throw the exception
                throw ex;
            }
        }
    }

    private void doFlashPostPhaseActions(FacesContext ctx) {
        try {
            ctx.getExternalContext().getFlash().doPostPhaseActions(ctx);
        } catch (UnsupportedOperationException uoe) {
            if (LOGGER.isLoggable(FINE)) {
                LOGGER.fine("ExternalContext.getFlash() throw UnsupportedOperationException -> Flash unavailable");
            }
        }
    }

    /**
     * @see jakarta.faces.context.PartialViewContext#getPartialResponseWriter()
     */
    @Override
    public PartialResponseWriter getPartialResponseWriter() {
        assertNotReleased();
        if (partialResponseWriter == null) {
            partialResponseWriter = new DelayedInitPartialResponseWriter(this);
        }
        return partialResponseWriter;
    }

    /**
     * @see jakarta.faces.context.PartialViewContext#release()
     */
    @Override
    public void release() {

        released = true;
        ajaxRequest = null;
        renderAll = null;
        partialResponseWriter = null;
        executeIds = null;
        renderIds = null;
        evalScripts = null;
        ctx = null;
        partialRequest = null;

    }

    // -------------------------------------------------------- Private Methods

    private List populatePhaseClientIds(PredefinedPostbackParameter parameterName) {

        String param = parameterName.getValue(ctx);
        if (param == null) {
            return new ArrayList<>();
        } else {
            Map appMap = FacesContext.getCurrentInstance().getExternalContext().getApplicationMap();
            String[] pcs = Util.split(appMap, param, "[ \t]+");
            return pcs != null && pcs.length != 0 ? new ArrayList<>(Arrays.asList(pcs)) : new ArrayList<>();
        }

    }

    // Process the components specified in the phaseClientIds list
    private void processComponents(UIComponent component, PhaseId phaseId, Collection phaseClientIds, FacesContext context) throws IOException {

        // We use the tree visitor mechanism to locate the components to
        // process. Create our (partial) VisitContext and the
        // VisitCallback that will be invoked for each component that
        // is visited.
        VisitContext visitContext = createPartialVisitContext(context, phaseClientIds, true);
        PhaseAwareVisitCallback visitCallback = new PhaseAwareVisitCallback(ctx, phaseId);
        component.visitTree(visitContext, visitCallback);

        PartialVisitContext partialVisitContext = unwrapPartialVisitContext(visitContext);
        if (partialVisitContext != null) {
            if (LOGGER.isLoggable(Level.FINER) && !partialVisitContext.getUnvisitedClientIds().isEmpty()) {
                Collection unvisitedClientIds = partialVisitContext.getUnvisitedClientIds();
                StringBuilder builder = new StringBuilder();
                for (String cur : unvisitedClientIds) {
                    builder.append(cur).append(" ");
                }
                LOGGER.log(Level.FINER, "faces.context.partial_visit_context_unvisited_children", new Object[] { builder.toString() });
            }
        }
    }

    private static VisitContext createPartialVisitContext(FacesContext context, Collection clientIds, boolean executeLifecycle) {

        // Note that we use the SKIP_UNRENDERED hint as
        // we only want to visit the rendered subtree.
        Set hints = executeLifecycle ? SKIP_UNRENDERED_AND_EXECUTE_LIFECYCLE_HINTS : SKIP_UNRENDERED_HINT;
        VisitContextFactory visitContextFactory = (VisitContextFactory) FactoryFinder.getFactory(VISIT_CONTEXT_FACTORY);
        return visitContextFactory.getVisitContext(context, clientIds, hints);
    }

    private static void resetValues(UIComponent component, Collection clientIds, FacesContext context) {

        // NOTE: this is indeed a copy of the one in UIViewRoot#resetValues().
        // The difference is that we want to be able to control the visit hints.
        // This isn't possible via the UIViewRoot#resetValues() API in its current form.
        component.visitTree(createPartialVisitContext(context, clientIds, false), new DoResetValues());
    }

    private static class DoResetValues implements VisitCallback {

        @Override
        public VisitResult visit(VisitContext context, UIComponent target) {
            if (target instanceof EditableValueHolder) {
                ((EditableValueHolder) target).resetValue();
            }
            return VisitResult.ACCEPT;
        }
    }

    /**
     * Unwraps {@link PartialVisitContext} from a chain of {@link VisitContextWrapper}s.
     *
     * If no {@link PartialVisitContext} is found in the chain, null is returned instead.
     *
     * @param visitContext the visit context.
     * @return the (unwrapped) partial visit context.
     */
    private static PartialVisitContext unwrapPartialVisitContext(VisitContext visitContext) {
        if (visitContext == null) {
            return null;
        }
        if (visitContext instanceof PartialVisitContext) {
            return (PartialVisitContext) visitContext;
        }
        if (visitContext instanceof VisitContextWrapper) {
            return unwrapPartialVisitContext(((VisitContextWrapper) visitContext).getWrapped());
        }
        return null;
    }

    private void renderAll(FacesContext context, UIViewRoot viewRoot) throws IOException {
        // If this is a "render all via ajax" request,
        // make sure to wrap the entire page in a  elemnt
        // with the special viewStateId of VIEW_ROOT_ID. This is how the client
        // JavaScript knows how to replace the entire document with
        // this response.
        PartialViewContext pvc = context.getPartialViewContext();
        PartialResponseWriter writer = pvc.getPartialResponseWriter();

        if (!(viewRoot instanceof NamingContainer)) {
            writer.startUpdate(PartialResponseWriter.RENDER_ALL_MARKER);
            if (viewRoot.getChildCount() > 0) {
                for (UIComponent uiComponent : viewRoot.getChildren()) {
                    uiComponent.encodeAll(context);
                }
            }
            writer.endUpdate();
        } else {
            /*
             * If we have a portlet request, start rendering at the view root.
             */
            writer.startUpdate(viewRoot.getClientId(context));
            viewRoot.encodeBegin(context);
            if (viewRoot.getChildCount() > 0) {
                for (UIComponent uiComponent : viewRoot.getChildren()) {
                    uiComponent.encodeAll(context);
                }
            }
            viewRoot.encodeEnd(context);
            writer.endUpdate();
        }
    }

    private void renderComponentResources(FacesContext context, UIViewRoot viewRoot) throws IOException {
        ResourceHandler resourceHandler = context.getApplication().getResourceHandler();
        PartialResponseWriter writer = context.getPartialViewContext().getPartialResponseWriter();
        boolean updateStarted = false;

        for (UIComponent resource : viewRoot.getComponentResources(context)) {
            String name = (String) resource.getAttributes().get("name");
            String library = (String) resource.getAttributes().get("library");

            if (resource.getChildCount() == 0 && resourceHandler.getRendererTypeForResourceName(name) != null
                    && !resourceHandler.isResourceRendered(context, name, library)) {
                if (!updateStarted) {
                    writer.startUpdate("jakarta.faces.Resource");
                    updateStarted = true;
                }

                resource.encodeAll(context);
            }
        }

        if (updateStarted) {
            writer.endUpdate();
        }
    }

    private void renderState(FacesContext context) throws IOException {
        // Get the view state and write it to the response..
        PartialViewContext pvc = context.getPartialViewContext();
        PartialResponseWriter writer = pvc.getPartialResponseWriter();
        String viewStateId = Util.getViewStateId(context);

        writer.startUpdate(viewStateId);
        String state = context.getApplication().getStateManager().getViewState(context);
        writer.write(state);
        writer.endUpdate();

        ClientWindow window = context.getExternalContext().getClientWindow();
        if (null != window) {
            String clientWindowId = Util.getClientWindowId(context);
            writer.startUpdate(clientWindowId);
            writer.writeText(window.getId(), null);
            writer.endUpdate();
        }
    }

    private void renderEvalScripts(FacesContext context) throws IOException {
        PartialViewContext pvc = context.getPartialViewContext();
        PartialResponseWriter writer = pvc.getPartialResponseWriter();

        for (String evalScript : pvc.getEvalScripts()) {
            writer.startEval();
            writer.write(evalScript);
            writer.endEval();
        }
    }

    private PartialResponseWriter createPartialResponseWriter() {

        ExternalContext extContext = ctx.getExternalContext();
        String encoding = extContext.getRequestCharacterEncoding();
        extContext.setResponseCharacterEncoding(encoding);
        ResponseWriter responseWriter = null;
        Writer out = null;
        try {
            out = extContext.getResponseOutputWriter();
        } catch (IOException ioe) {
            if (LOGGER.isLoggable(Level.SEVERE)) {
                LOGGER.log(Level.SEVERE, ioe.toString(), ioe);
            }
        }

        if (out != null) {
            UIViewRoot viewRoot = ctx.getViewRoot();
            if (viewRoot != null) {
                responseWriter = ctx.getRenderKit().createResponseWriter(out, RIConstants.TEXT_XML_CONTENT_TYPE, encoding);
            } else {
                RenderKitFactory factory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
                RenderKit renderKit = factory.getRenderKit(ctx, RenderKitFactory.HTML_BASIC_RENDER_KIT);
                responseWriter = renderKit.createResponseWriter(out, RIConstants.TEXT_XML_CONTENT_TYPE, encoding);
            }
        }
        if (responseWriter instanceof PartialResponseWriter) {
            return (PartialResponseWriter) responseWriter;
        } else {
            return new PartialResponseWriter(responseWriter);
        }

    }

    private void cleanupAfterView() {
        ResponseWriter orig = (ResponseWriter) ctx.getAttributes().get(ORIGINAL_WRITER);
        assert null != orig;
        // move aside the PartialResponseWriter
        ctx.setResponseWriter(orig);
    }

    private void assertNotReleased() {
        if (released) {
            throw new IllegalStateException();
        }
    }

    // ----------------------------------------------------------- Inner Classes

    private static class PhaseAwareVisitCallback implements VisitCallback {

        private final PhaseId curPhase;
        private final FacesContext ctx;

        private PhaseAwareVisitCallback(FacesContext ctx, PhaseId curPhase) {
            this.ctx = ctx;
            this.curPhase = curPhase;
        }

        @Override
        public VisitResult visit(VisitContext context, UIComponent comp) {
            try {

                if (curPhase == PhaseId.APPLY_REQUEST_VALUES) {

                    // RELEASE_PENDING handle immediate request(s)
                    // If the user requested an immediate request
                    // Make sure to set the immediate flag here.

                    comp.processDecodes(ctx);
                } else if (curPhase == PhaseId.PROCESS_VALIDATIONS) {
                    comp.processValidators(ctx);
                } else if (curPhase == PhaseId.UPDATE_MODEL_VALUES) {
                    comp.processUpdates(ctx);
                } else if (curPhase == PhaseId.RENDER_RESPONSE) {
                    PartialResponseWriter writer = ctx.getPartialViewContext().getPartialResponseWriter();
                    writer.startUpdate(comp.getClientId(ctx));
                    // do the default behavior...
                    comp.encodeAll(ctx);
                    writer.endUpdate();
                } else {
                    throw new IllegalStateException("I18N: Unexpected " + "PhaseId passed to " + " PhaseAwareContextCallback: " + curPhase.toString());
                }
            } catch (IOException ex) {
                if (LOGGER.isLoggable(Level.SEVERE)) {
                    LOGGER.severe(ex.toString());
                }
                if (LOGGER.isLoggable(Level.FINE)) {
                    LOGGER.log(Level.FINE, ex.toString(), ex);
                }
                throw new FacesException(ex);
            }

            // Once we visit a component, there is no need to visit
            // its children, since processDecodes/Validators/Updates and
            // encodeAll() already traverse the subtree. We return
            // VisitResult.REJECT to supress the subtree visit.
            return VisitResult.REJECT;
        }
    }

    /**
     * Delays the actual construction of the PartialResponseWriter until content is going to actually be written.
     */
    private static final class DelayedInitPartialResponseWriter extends PartialResponseWriter {

        private ResponseWriter writer;
        private final PartialViewContextImpl ctx;

        // -------------------------------------------------------- Constructors

        public DelayedInitPartialResponseWriter(PartialViewContextImpl ctx) {

            super(null);
            this.ctx = ctx;
            ExternalContext extCtx = ctx.ctx.getExternalContext();

            if (extCtx.isResponseCommitted()) {
                LOGGER.log(WARNING, "Response is already committed - cannot reconfigure it anymore");
            }
            else {
                extCtx.setResponseContentType(RIConstants.TEXT_XML_CONTENT_TYPE);
                extCtx.setResponseCharacterEncoding(extCtx.getRequestCharacterEncoding());
                extCtx.setResponseBufferSize(extCtx.getResponseBufferSize());
            }
        }

        // ---------------------------------- Methods from PartialResponseWriter

        @Override
        public void write(String text) throws IOException {
            HtmlUtils.writeUnescapedTextForXML(getWrapped(), text);
        }

        @Override
        public ResponseWriter getWrapped() {

            if (writer == null) {
                writer = ctx.createPartialResponseWriter();
            }
            return writer;

        }

    } // END DelayedInitPartialResponseWriter

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy