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

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

Go to download

Jakarta Faces defines an MVC framework for building user interfaces for web applications, including UI components, state management, event handing, input validation, page navigation, and support for internationalization and accessibility.

There is a newer version: 4.1.2
Show 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 - 2025 Weber Informatics LLC | Privacy Policy