com.sun.faces.context.PartialViewContextImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jakarta.faces Show documentation
Show all versions of jakarta.faces Show documentation
EE4J Compatible Implementation for Jakarta Faces API
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
}