com.sun.faces.application.view.FaceletViewHandlingStrategy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jakarta.faces-api Show documentation
Show all versions of jakarta.faces-api Show documentation
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.
/*
* 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.application.view;
import static com.sun.faces.RIConstants.DYNAMIC_COMPONENT;
import static com.sun.faces.RIConstants.FACELETS_ENCODING_KEY;
import static com.sun.faces.RIConstants.FLOW_DEFINITION_ID_SUFFIX;
import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.FaceletsBufferSize;
import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.FaceletsViewMappings;
import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.StateSavingMethod;
import static com.sun.faces.context.StateContext.getStateContext;
import static com.sun.faces.util.ComponentStruct.ADD;
import static com.sun.faces.util.ComponentStruct.REMOVE;
import static com.sun.faces.util.RequestStateManager.FACELET_FACTORY;
import static com.sun.faces.util.Util.getDOCTYPEFromFacesContextAttributes;
import static com.sun.faces.util.Util.getXMLDECLFromFacesContextAttributes;
import static com.sun.faces.util.Util.isEmpty;
import static com.sun.faces.util.Util.isViewIdExactMappedToFacesServlet;
import static com.sun.faces.util.Util.isViewPopulated;
import static com.sun.faces.util.Util.notNull;
import static com.sun.faces.util.Util.saveDOCTYPEToFacesContextAttributes;
import static com.sun.faces.util.Util.saveXMLDECLToFacesContextAttributes;
import static com.sun.faces.util.Util.setViewPopulated;
import static com.sun.faces.util.Util.split;
import static jakarta.faces.FactoryFinder.VIEW_DECLARATION_LANGUAGE_FACTORY;
import static jakarta.faces.application.ProjectStage.Development;
import static jakarta.faces.application.Resource.COMPONENT_RESOURCE_KEY;
import static jakarta.faces.application.StateManager.IS_BUILDING_INITIAL_STATE;
import static jakarta.faces.application.StateManager.STATE_SAVING_METHOD_SERVER;
import static jakarta.faces.application.ViewHandler.CHARACTER_ENCODING_KEY;
import static jakarta.faces.application.ViewHandler.DEFAULT_FACELETS_SUFFIX;
import static jakarta.faces.application.ViewVisitOption.RETURN_AS_MINIMAL_IMPLICIT_OUTCOME;
import static jakarta.faces.component.UIComponent.BEANINFO_KEY;
import static jakarta.faces.component.UIComponent.COMPOSITE_FACET_NAME;
import static jakarta.faces.component.UIComponent.VIEW_LOCATION_KEY;
import static jakarta.faces.component.UIViewRoot.COMPONENT_TYPE;
import static jakarta.faces.view.AttachedObjectTarget.ATTACHED_OBJECT_TARGETS_KEY;
import static jakarta.faces.view.facelets.FaceletContext.FACELET_CONTEXT_KEY;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static java.lang.Boolean.TRUE;
import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINEST;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;
import java.beans.BeanDescriptor;
import java.beans.BeanInfo;
import java.beans.PropertyDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import com.sun.faces.application.ApplicationAssociate;
import com.sun.faces.config.WebConfiguration;
import com.sun.faces.context.StateContext;
import com.sun.faces.facelets.el.ContextualCompositeMethodExpression;
import com.sun.faces.facelets.el.VariableMapperWrapper;
import com.sun.faces.facelets.impl.DefaultFaceletFactory;
import com.sun.faces.facelets.impl.XMLFrontMatterSaver;
import com.sun.faces.facelets.tag.composite.CompositeComponentBeanInfo;
import com.sun.faces.facelets.tag.jsf.CompositeComponentTagHandler;
import com.sun.faces.facelets.tag.ui.UIDebug;
import com.sun.faces.renderkit.RenderKitUtils;
import com.sun.faces.util.Cache;
import com.sun.faces.util.ComponentStruct;
import com.sun.faces.util.FacesLogger;
import com.sun.faces.util.HtmlUtils;
import com.sun.faces.util.RequestStateManager;
import com.sun.faces.util.Util;
import jakarta.el.ELContext;
import jakarta.el.ExpressionFactory;
import jakarta.el.MethodExpression;
import jakarta.el.ValueExpression;
import jakarta.el.VariableMapper;
import jakarta.faces.FacesException;
import jakarta.faces.FactoryFinder;
import jakarta.faces.application.Resource;
import jakarta.faces.application.StateManager;
import jakarta.faces.application.ViewHandler;
import jakarta.faces.application.ViewVisitOption;
import jakarta.faces.component.ActionSource2;
import jakarta.faces.component.EditableValueHolder;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.UIPanel;
import jakarta.faces.component.UIViewRoot;
import jakarta.faces.component.visit.VisitContext;
import jakarta.faces.component.visit.VisitResult;
import jakarta.faces.context.ExternalContext;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.ResponseWriter;
import jakarta.faces.event.ActionEvent;
import jakarta.faces.event.MethodExpressionActionListener;
import jakarta.faces.event.MethodExpressionValueChangeListener;
import jakarta.faces.event.PostAddToViewEvent;
import jakarta.faces.event.ValueChangeEvent;
import jakarta.faces.render.RenderKit;
import jakarta.faces.render.ResponseStateManager;
import jakarta.faces.validator.MethodExpressionValidator;
import jakarta.faces.view.ActionSource2AttachedObjectHandler;
import jakarta.faces.view.ActionSource2AttachedObjectTarget;
import jakarta.faces.view.AttachedObjectHandler;
import jakarta.faces.view.AttachedObjectTarget;
import jakarta.faces.view.BehaviorHolderAttachedObjectHandler;
import jakarta.faces.view.BehaviorHolderAttachedObjectTarget;
import jakarta.faces.view.EditableValueHolderAttachedObjectHandler;
import jakarta.faces.view.EditableValueHolderAttachedObjectTarget;
import jakarta.faces.view.StateManagementStrategy;
import jakarta.faces.view.ValueHolderAttachedObjectHandler;
import jakarta.faces.view.ValueHolderAttachedObjectTarget;
import jakarta.faces.view.ViewDeclarationLanguage;
import jakarta.faces.view.ViewDeclarationLanguageFactory;
import jakarta.faces.view.ViewMetadata;
import jakarta.faces.view.facelets.Facelet;
import jakarta.faces.view.facelets.FaceletContext;
import jakarta.servlet.http.HttpSession;
/**
* This {@link ViewHandlingStrategy} handles Facelets/PDL-based views.
*/
public class FaceletViewHandlingStrategy extends ViewHandlingStrategy {
private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger();
private ViewDeclarationLanguageFactory vdlFactory;
private DefaultFaceletFactory faceletFactory;
// Array of viewId extensions that should be handled by Facelets
private String[] extensionsArray;
// Array of viewId prefixes that should be handled by Facelets
private String[] prefixesArray;
public static final String IS_BUILDING_METADATA = FaceletViewHandlingStrategy.class.getName() + ".IS_BUILDING_METADATA";
public static final String RESOURCE_LIBRARY_CONTRACT_DATA_STRUCTURE_KEY = FaceletViewHandlingStrategy.class.getName()
+ ".RESOURCE_LIBRARY_CONTRACT_DATA_STRUCTURE";
private MethodRetargetHandlerManager retargetHandlerManager = new MethodRetargetHandlerManager();
private int responseBufferSize;
private boolean responseBufferSizeSet;
private boolean isTrinidadStateManager;
private Cache metadataCache;
private Map> contractMappings;
// ------------------------------------------------------------ Constructors
public FaceletViewHandlingStrategy() {
initialize();
}
// ------------------------------------------------------------ Constructors
public static boolean isBuildingMetadata(FacesContext context) {
return context.getAttributes().containsKey(IS_BUILDING_METADATA);
}
// ------------------------------------ Methods from ViewDeclarationLanguage
/**
*
* If {@link UIDebug#debugRequest(jakarta.faces.context.FacesContext)}} is true
, simply return a new
* UIViewRoot(), otherwise, call the default logic.
*
*
* @see ViewDeclarationLanguage#restoreView(jakarta.faces.context.FacesContext, java.lang.String)
*/
@Override
public UIViewRoot restoreView(FacesContext context, String viewId) {
notNull("context", context);
notNull("viewId", viewId);
if (UIDebug.debugRequest(context)) {
context.getApplication().createComponent(COMPONENT_TYPE);
}
UIViewRoot viewRoot;
/*
* Check if we are stateless.
*/
ViewHandler outerViewHandler = context.getApplication().getViewHandler();
String renderKitId = outerViewHandler.calculateRenderKitId(context);
ResponseStateManager rsm = RenderKitUtils.getResponseStateManager(context, renderKitId);
if (rsm.isStateless(context, viewId)) {
try {
context.setProcessingEvents(true);
ViewDeclarationLanguage vdl = vdlFactory.getViewDeclarationLanguage(viewId);
viewRoot = vdl.createView(context, viewId);
context.setViewRoot(viewRoot);
vdl.buildView(context, viewRoot);
if (!viewRoot.isTransient()) {
throw new FacesException("Unable to restore view " + viewId);
}
return viewRoot;
} catch (IOException ioe) {
throw new FacesException(ioe);
}
}
if (getStateContext(context).isPartialStateSaving(context, viewId)) {
try {
context.setProcessingEvents(false);
ViewDeclarationLanguage vdl = vdlFactory.getViewDeclarationLanguage(viewId);
viewRoot = vdl.getViewMetadata(context, viewId).createMetadataView(context);
context.setViewRoot(viewRoot);
outerViewHandler = context.getApplication().getViewHandler();
renderKitId = outerViewHandler.calculateRenderKitId(context);
rsm = RenderKitUtils.getResponseStateManager(context, renderKitId);
Object[] rawState = (Object[]) rsm.getState(context, viewId);
if (rawState != null) {
@SuppressWarnings("unchecked")
Map state = (Map) rawState[1];
if (state != null) {
String clientId = viewRoot.getClientId(context);
Object stateObj = state.get(clientId);
if (stateObj != null) {
context.getAttributes().put("com.sun.faces.application.view.restoreViewScopeOnly", true);
viewRoot.restoreState(context, stateObj);
context.getAttributes().remove("com.sun.faces.application.view.restoreViewScopeOnly");
}
}
}
context.setProcessingEvents(true);
vdl.buildView(context, viewRoot);
} catch (IOException ioe) {
throw new FacesException(ioe);
}
}
UIViewRoot root = super.restoreView(context, viewId);
ViewHandler viewHandler = context.getApplication().getViewHandler();
ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(context, viewId);
context.setResourceLibraryContracts(vdl.calculateResourceLibraryContracts(context, viewId));
StateContext stateCtx = StateContext.getStateContext(context);
stateCtx.startTrackViewModifications(context, root);
return root;
}
@Override
public ViewMetadata getViewMetadata(FacesContext context, String viewId) {
notNull("context", context);
notNull("viewId", viewId);
return new ViewMetadataImpl(viewId);
}
/**
* @see ViewDeclarationLanguage#createView(jakarta.faces.context.FacesContext, java.lang.String)
*/
@Override
public UIViewRoot createView(FacesContext ctx, String viewId) {
notNull("context", ctx);
notNull("viewId", viewId);
if (UIDebug.debugRequest(ctx)) {
UIViewRoot root = (UIViewRoot) ctx.getApplication().createComponent(COMPONENT_TYPE);
root.setViewId(viewId);
return root;
}
UIViewRoot result = super.createView(ctx, viewId);
ViewHandler viewHandler = ctx.getApplication().getViewHandler();
ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(ctx, viewId);
ctx.setResourceLibraryContracts(vdl.calculateResourceLibraryContracts(ctx, viewId));
return result;
}
/**
* @see ViewDeclarationLanguage#buildView(FacesContext, UIViewRoot)
*/
@Override
public void buildView(FacesContext ctx, UIViewRoot view) throws IOException {
StateContext stateCtx = StateContext.getStateContext(ctx);
if (isViewPopulated(ctx, view)) {
Facelet facelet = faceletFactory.getFacelet(ctx, view.getViewId());
// Disable events from being intercepted by the StateContext by
// virute of re-applying the handlers.
try {
stateCtx.setTrackViewModifications(false);
facelet.apply(ctx, view);
reapplyDynamicActions(ctx);
if (stateCtx.isPartialStateSaving(ctx, view.getViewId())) {
markInitialStateIfNotMarked(view);
}
} finally {
stateCtx.setTrackViewModifications(true);
}
return;
}
view.setViewId(view.getViewId());
if (LOGGER.isLoggable(FINE)) {
LOGGER.fine("Building View: " + view.getViewId());
}
if (faceletFactory == null) {
ApplicationAssociate associate = ApplicationAssociate.getInstance(ctx.getExternalContext());
faceletFactory = associate.getFaceletFactory();
assert faceletFactory != null;
}
RequestStateManager.set(ctx, FACELET_FACTORY, faceletFactory);
Facelet facelet = faceletFactory.getFacelet(ctx, view.getViewId());
// populate UIViewRoot
try {
ctx.getAttributes().put(IS_BUILDING_INITIAL_STATE, Boolean.TRUE);
stateCtx.setTrackViewModifications(false);
facelet.apply(ctx, view);
if (facelet instanceof XMLFrontMatterSaver) {
XMLFrontMatterSaver frontMatterSaver = (XMLFrontMatterSaver) facelet;
String docType = frontMatterSaver.getSavedDoctype();
if (docType != null) {
saveDOCTYPEToFacesContextAttributes(docType);
}
String XMLDECL = frontMatterSaver.getSavedXMLDecl();
if (XMLDECL != null) {
saveXMLDECLToFacesContextAttributes(XMLDECL);
}
}
if (!stateCtx.isPartialStateSaving(ctx, view.getViewId())) {
reapplyDynamicActions(ctx);
}
doPostBuildActions(ctx, view);
} finally {
ctx.getAttributes().remove(IS_BUILDING_INITIAL_STATE);
}
ctx.getApplication().publishEvent(ctx, PostAddToViewEvent.class, UIViewRoot.class, view);
markInitialState(ctx, view);
setViewPopulated(ctx, view);
}
/**
* @see jakarta.faces.view.ViewDeclarationLanguage#renderView(jakarta.faces.context.FacesContext,
* jakarta.faces.component.UIViewRoot)
*/
@Override
public void renderView(FacesContext ctx, UIViewRoot viewToRender) throws IOException {
// Suppress rendering if "rendered" property on the component is false
if (!viewToRender.isRendered()) {
return;
}
// Log request
if (LOGGER.isLoggable(FINE)) {
LOGGER.fine("Rendering View: " + viewToRender.getViewId());
}
WriteBehindStateWriter stateWriter = null;
try {
// Only build the view if this view has not yet been built.
if (!isViewPopulated(ctx, viewToRender)) {
ViewDeclarationLanguage vdl = vdlFactory.getViewDeclarationLanguage(viewToRender.getViewId());
vdl.buildView(ctx, viewToRender);
}
// Setup writer and assign it to the ctx
ResponseWriter origWriter = ctx.getResponseWriter();
if (origWriter == null) {
origWriter = createResponseWriter(ctx);
}
ExternalContext extContext = ctx.getExternalContext();
/*
* Make sure we have a session here if we are using server state saving. The WriteBehindStateWriter needs an active
* session when it writes out state to a server session.
*
* Note if you flag a view as transient then we won't acquire the session as you are stating it does not need one.
*/
if (isServerStateSaving() && !viewToRender.isTransient()) {
getSession(ctx);
}
Writer outputWriter = extContext.getResponseOutputWriter();
stateWriter = new WriteBehindStateWriter(outputWriter, ctx, responseBufferSize);
ResponseWriter writer = origWriter.cloneWithWriter(stateWriter);
ctx.setResponseWriter(writer);
// Don't call startDoc and endDoc on a partial response
if (ctx.getPartialViewContext().isPartialRequest()) {
viewToRender.encodeAll(ctx);
try {
ctx.getExternalContext().getFlash().doPostPhaseActions(ctx);
} catch (UnsupportedOperationException uoe) {
if (LOGGER.isLoggable(FINE)) {
LOGGER.fine("ExternalContext.getFlash() throw UnsupportedOperationException -> Flash unavailable");
}
}
} else {
if (ctx.isProjectStage(Development)) {
FormOmittedChecker.check(ctx);
}
// render the view to the response
String xmlDecl = getXMLDECLFromFacesContextAttributes(ctx);
if (null != xmlDecl) {
// Do not escape.
writer.writePreamble(xmlDecl);
}
String docType = getDOCTYPEFromFacesContextAttributes(ctx);
if (null != docType) {
// Do not escape.
writer.writeDoctype(docType);
}
writer.startDocument();
viewToRender.encodeAll(ctx);
try {
ctx.getExternalContext().getFlash().doPostPhaseActions(ctx);
} catch (UnsupportedOperationException uoe) {
if (LOGGER.isLoggable(FINE)) {
LOGGER.fine("ExternalContext.getFlash() throw UnsupportedOperationException -> Flash unavailable");
}
}
writer.endDocument();
}
// Finish writing
writer.close();
// Flush to origWriter
if (stateWriter.stateWritten()) {
stateWriter.flushToWriter();
}
} catch (FileNotFoundException fnfe) {
handleFaceletNotFound(ctx, viewToRender.getViewId(), fnfe.getMessage());
} catch (Exception e) {
handleRenderException(ctx, e);
} finally {
if (stateWriter != null) {
stateWriter.release();
}
}
}
@Override
public StateManagementStrategy getStateManagementStrategy(FacesContext context, String viewId) {
StateManagementStrategy result;
StateContext stateCtx = StateContext.getStateContext(context);
if (stateCtx.isPartialStateSaving(context, viewId)) {
result = new FaceletPartialStateManagementStrategy(context);
} else {
// Spec for this method says:
// Implementations that provide the VDL for Facelets for JSF 2.0
// and later must return non-null from this method.
// Limit the specification violating change to the case where
// we are running in Trinidad.
//
result = isTrinidadStateManager ? null : new JspStateManagementStrategy(context);
}
return result;
}
/**
* Called by Application._createComponent(Resource).
*
* This method creates two temporary UIComponent instances to aid in the creation of the compcomp metadata. These
* instances no longer needed after the method returns and can be safely garbage collected.
*
* PENDING(): memory analysis should be done to verify there are no memory leaks as a result of this implementation.
*
* The instances are
*
* 1. tmp: a jakarta.faces.NamingContainer to serve as the temporary top level component
*
* 2. facetComponent: a jakarta.faces.Panel to serve as the parent UIComponent that is passed to Facelets so that the
* section can be parsed and understood.
*
* Per the compcomp spec, tmp has the compcomp Resource stored in its attr set under the key
* Resource.COMPONENT_RESOURCE_KEY. tmp has the facetComponent added as its UIComponent.COMPOSITE_FACET_NAME facet.
*
*/
@Override
public BeanInfo getComponentMetadata(FacesContext context, Resource ccResource) {
DefaultFaceletFactory factory = (DefaultFaceletFactory) RequestStateManager.get(context, FACELET_FACTORY);
DefaultFaceletFactory ourFactory = factory;
if (ourFactory.needsToBeRefreshed(ccResource.getURL())) {
metadataCache.remove(ccResource);
}
return metadataCache.get(ccResource);
}
public BeanInfo createComponentMetadata(FacesContext context, Resource ccResource) {
// PENDING this implementation is terribly wasteful.
// Must find a better way.
FaceletContext faceletContext = (FaceletContext) context.getAttributes().get(FACELET_CONTEXT_KEY);
DefaultFaceletFactory factory = (DefaultFaceletFactory) RequestStateManager.get(context, FACELET_FACTORY);
VariableMapper orig = faceletContext.getVariableMapper();
// Create tmp and facetComponent
UIComponent tmp = context.getApplication().createComponent("jakarta.faces.NamingContainer");
UIPanel facetComponent = (UIPanel) context.getApplication().createComponent("jakarta.faces.Panel");
// PENDING I think this can be skipped because we don't render
// this component instance.
facetComponent.setRendererType("jakarta.faces.Group");
// PENDING This could possibly be skipped too. However, I think
// this is important because other tag handlers, within
// expect it will be there.
tmp.getFacets().put(COMPOSITE_FACET_NAME, facetComponent);
// We have to put the resource in here just so the classes that eventually
// get called by facelets have access to it.
tmp.getAttributes().put(COMPONENT_RESOURCE_KEY, ccResource);
Facelet facelet;
try {
facelet = factory.getFacelet(context, ccResource.getURL());
VariableMapper wrapper = new VariableMapperWrapper(orig) {
@Override
public ValueExpression resolveVariable(String variable) {
return super.resolveVariable(variable);
}
};
faceletContext.setVariableMapper(wrapper);
context.getAttributes().put(IS_BUILDING_METADATA, TRUE);
// Because mojarra currently requires a
// element within the compcomp markup, we can rely on the
// fact that its tag handler, InterfaceHandler.apply(), is
// called. In this method, we first imbue facetComponent
// with any config information present on the
// element.
// Then we do the normal facelet thing:
// this.nextHandler.apply(). This causes any child tag
// handlers of the to be called. The
// compcomp spec says each such tag handler is responsible
// for adding to the compcomp metadata, referenced from the
// facetComponent parent.
facelet.apply(context, facetComponent);
// When facelet.apply() returns (and therefore
// InterfaceHandler.apply() returns), the compcomp metadata
// pointed to by facetComponent is fully populated.
} catch (Exception e) {
if (e instanceof FacesException) {
throw (FacesException) e;
} else {
throw new FacesException(e);
}
} finally {
context.getAttributes().remove(IS_BUILDING_METADATA);
faceletContext.setVariableMapper(orig);
}
// we extract the compcomp metadata and return it, making sure
// to discard tmp and facetComponent. The compcomp metadata
// should be cacheable and shareable across threads, but this is
// not yet implemented.
return (CompositeComponentBeanInfo) tmp.getAttributes().get(BEANINFO_KEY);
}
/**
* @see jakarta.faces.view.ViewDeclarationLanguage#getScriptComponentResource(jakarta.faces.context.FacesContext,
* jakarta.faces.application.Resource)
*/
@Override
public Resource getScriptComponentResource(FacesContext context, Resource componentResource) {
notNull("context", context);
notNull("componentResource", componentResource);
return null;
}
/**
* @see ViewHandlingStrategy#retargetAttachedObjects(jakarta.faces.context.FacesContext,
* jakarta.faces.component.UIComponent, java.util.List)
*/
@SuppressWarnings({ "unchecked" })
@Override
public void retargetAttachedObjects(FacesContext context, UIComponent topLevelComponent, List handlers) {
notNull("context", context);
notNull("topLevelComponent", topLevelComponent);
notNull("handlers", handlers);
if (handlers == null || handlers.isEmpty()) {
return;
}
BeanInfo componentBeanInfo = (BeanInfo) topLevelComponent.getAttributes().get(BEANINFO_KEY);
if (componentBeanInfo == null) {
return;
}
BeanDescriptor componentDescriptor = componentBeanInfo.getBeanDescriptor();
// There is an entry in targetList for each attached object in the
// section of the composite component.
List targetList = (List) componentDescriptor.getValue(ATTACHED_OBJECT_TARGETS_KEY);
// Each entry in targetList will vend one or more UIComponent instances
// that is to serve as the target of an attached object in the consuming
// page.
List targetComponents;
String forAttributeValue, curTargetName;
// For each of the attached object handlers...
for (AttachedObjectHandler curHandler : handlers) {
// Get the name given to this attached object by the page author
// in the consuming page.
forAttributeValue = curHandler.getFor();
// For each of the attached objects in the section
// of this composite component...
for (AttachedObjectTarget curTarget : targetList) {
// Get the name given to this attached object target by the
// composite component author
curTargetName = curTarget.getName();
targetComponents = curTarget.getTargets(topLevelComponent);
if (curHandler instanceof ActionSource2AttachedObjectHandler && curTarget instanceof ActionSource2AttachedObjectTarget) {
if (forAttributeValue.equals(curTargetName)) {
for (UIComponent curTargetComponent : targetComponents) {
retargetHandler(context, curHandler, curTargetComponent);
}
break;
}
} else if (curHandler instanceof EditableValueHolderAttachedObjectHandler && curTarget instanceof EditableValueHolderAttachedObjectTarget) {
if (forAttributeValue.equals(curTargetName)) {
for (UIComponent curTargetComponent : targetComponents) {
retargetHandler(context, curHandler, curTargetComponent);
}
break;
}
} else if (curHandler instanceof ValueHolderAttachedObjectHandler && curTarget instanceof ValueHolderAttachedObjectTarget) {
if (forAttributeValue.equals(curTargetName)) {
for (UIComponent curTargetComponent : targetComponents) {
retargetHandler(context, curHandler, curTargetComponent);
}
break;
}
} else if (curHandler instanceof BehaviorHolderAttachedObjectHandler && curTarget instanceof BehaviorHolderAttachedObjectTarget) {
BehaviorHolderAttachedObjectHandler behaviorHandler = (BehaviorHolderAttachedObjectHandler) curHandler;
BehaviorHolderAttachedObjectTarget behaviorTarget = (BehaviorHolderAttachedObjectTarget) curTarget;
String eventName = behaviorHandler.getEventName();
if (null != eventName && eventName.equals(curTargetName) || null == eventName && behaviorTarget.isDefaultEvent()) {
for (UIComponent curTargetComponent : targetComponents) {
retargetHandler(context, curHandler, curTargetComponent);
}
}
}
}
}
}
/**
* @see ViewHandlingStrategy#retargetMethodExpressions(jakarta.faces.context.FacesContext,
* jakarta.faces.component.UIComponent)
*/
@Override
public void retargetMethodExpressions(FacesContext context, UIComponent topLevelComponent) {
notNull("context", context);
notNull("topLevelComponent", topLevelComponent);
BeanInfo componentBeanInfo = (BeanInfo) topLevelComponent.getAttributes().get(BEANINFO_KEY);
// PENDING(edburns): log error message if componentBeanInfo is null;
if (componentBeanInfo == null) {
return;
}
PropertyDescriptor attributes[] = componentBeanInfo.getPropertyDescriptors();
MethodMetadataIterator allMetadata = new MethodMetadataIterator(context, attributes);
for (CompCompInterfaceMethodMetadata metadata : allMetadata) {
String attrName = metadata.getName();
String[] targets = metadata.getTargets(context);
Object attrValue = topLevelComponent.getValueExpression(attrName);
// In all cases but one, the attrValue will be a ValueExpression.
// The only case when it will not be a ValueExpression is
// the case when the attrName is an action, and even then, it'll be a
// ValueExpression in all cases except when it's a literal string.
if (attrValue == null) {
Map attrs = topLevelComponent.getAttributes();
attrValue = attrs.containsKey(attrName) ? attrs.get(attrName) : metadata.getDefault();
if (attrValue == null) {
if (metadata.isRequired(context)) {
Object location = attrs.get(VIEW_LOCATION_KEY);
if (location == null) {
location = "";
}
throw new FacesException(
location.toString() + ": Unable to find attribute with name \"" + attrName + "\" in top level component in consuming page, "
+ " or with default value in composite component. " + "Page author or composite component author error.");
} else {
continue;
}
}
}
String targetAttributeName = metadata.getTargetAttributeName(context);
UIComponent targetComp = null;
if (targetAttributeName != null) {
attrName = targetAttributeName;
}
if (targets != null) {
MethodRetargetHandler handler = retargetHandlerManager.getRetargetHandler(attrName);
if (handler != null) {
for (String curTarget : targets) {
targetComp = topLevelComponent.findComponent(curTarget);
if (targetComp == null) {
throw new FacesException(
attrValue.toString() + " : Unable to re-target MethodExpression as inner component referenced by target id '" + curTarget
+ "' cannot be found.");
}
handler.retarget(context, metadata, attrValue, targetComp);
}
} else {
// the developer has specified a target for a MethodExpression
// but the attribute name doesn't match one action, actionListener,
// validator, or valueChangeListener. We can ignore the
// target(s) in this case
if (LOGGER.isLoggable(WARNING)) {
LOGGER.log(WARNING, "jsf.compcomp.unecessary.targets.attribute",
new Object[] { getCompositeComponentName(topLevelComponent), attrName });
}
handler = retargetHandlerManager.getDefaultHandler();
handler.retarget(context, metadata, attrValue, topLevelComponent);
}
} else {
MethodRetargetHandler handler = null;
if (targetAttributeName != null) {
targetComp = topLevelComponent.findComponent(metadata.getName());
handler = retargetHandlerManager.getRetargetHandler(attrName);
}
if (handler == null) {
targetComp = topLevelComponent;
handler = retargetHandlerManager.getDefaultHandler();
}
handler.retarget(context, metadata, attrValue, targetComp);
}
// clear out the ValueExpression that we've retargeted as a
// MethodExpression
topLevelComponent.setValueExpression(attrName, null);
}
}
@Override
public UIComponent createComponent(FacesContext context, String taglibURI, String tagName, Map attributes) {
notNull("context", context);
notNull("taglibURI", taglibURI);
notNull("tagName", tagName);
return associate.getFaceletFactory()._createComponent(context, taglibURI, tagName, attributes);
}
@Override
public List calculateResourceLibraryContracts(FacesContext context, String viewId) {
List result = null;
String longestPattern = null;
if (contractMappings == null) {
return emptyList();
}
String longestMatch = null;
for (Map.Entry> mappings : contractMappings.entrySet()) {
String urlPattern = mappings.getKey();
if (urlPattern.endsWith("*")) {
String prefix = urlPattern.substring(0, urlPattern.length() - 1);
if (viewId.startsWith(prefix)) {
if (longestPattern == null) {
longestPattern = urlPattern;
longestMatch = prefix;
} else if (longestMatch.length() < prefix.length()) {
longestPattern = urlPattern;
longestMatch = prefix;
}
}
} else if (viewId.equals(urlPattern)) {
longestPattern = urlPattern;
break;
}
}
if (longestPattern != null) {
result = contractMappings.get(longestPattern);
}
if (result == null) {
result = contractMappings.get("*");
}
return result;
}
@Override
@SuppressWarnings("deprecation")
public boolean viewExists(FacesContext context, String viewId) {
if (handlesViewId(viewId)) {
return getFaceletFactory().getResourceResolver().resolveUrl(viewId) != null;
}
return false;
}
/**
* @see jakarta.faces.view.ViewDeclarationLanguage#getViews(FacesContext, String)
*/
@Override
public Stream getViews(FacesContext context, String path, ViewVisitOption... options) {
return mapIfNeeded(super.getViews(context, path).filter(viewId -> handlesViewId(viewId)), options);
}
/**
* @see jakarta.faces.view.ViewDeclarationLanguage#getViews(FacesContext, String, int)
*/
@Override
public Stream getViews(FacesContext context, String path, int maxDepth, ViewVisitOption... options) {
return mapIfNeeded(super.getViews(context, path, maxDepth).filter(viewId -> handlesViewId(viewId)), options);
}
// --------------------------------------- Methods from ViewHandlingStrategy
/**
* @param viewId the view ID to check
* @return true
if assuming a default configuration and the view ID's extension is .xhtml
* Otherwise try to match the view ID based on the configured extendsion and prefixes.
*
* @see com.sun.faces.config.WebConfiguration.WebContextInitParameter#FaceletsViewMappings
*/
@Override
public boolean handlesViewId(String viewId) {
if (viewId != null) {
if (handlesByPrefixOrSuffix(viewId)) {
return true;
}
if (isViewIdExactMappedToFacesServlet(viewId)) {
// If the Facelets VDL is reached, no other ViewDeclarationLanguage has declared
// to handle the view (via ViewExists()), so we handle it if the viewId happens to be exact
// mapped to the FacesServlet. The JSP ViewDeclarationLanguage still comes after us,
// but we don't support that.
return true;
}
}
return false;
}
private boolean handlesByPrefixOrSuffix(String viewId) {
if (viewId.endsWith(FLOW_DEFINITION_ID_SUFFIX)) {
return true;
}
// If there's no extensions array or prefixes array, then assume defaults.
// .xhtml extension is handled by the FaceletViewHandler and .jsp will be handled by
// the JSP view handler
if (extensionsArray == null && prefixesArray == null) {
return isMatchedWithFaceletsSuffix(viewId) ? true : viewId.endsWith(DEFAULT_FACELETS_SUFFIX);
}
if (extensionsArray != null) {
for (String extension : extensionsArray) {
if (viewId.endsWith(extension)) {
return true;
}
}
}
if (prefixesArray != null) {
for (String prefix : prefixesArray) {
if (viewId.startsWith(prefix)) {
return true;
}
}
}
return false;
}
@Override
public String getId() {
return FACELETS_VIEW_DECLARATION_LANGUAGE_ID;
}
// ------------------------------------------------------- Protected Methods
/**
* Initialize the core Facelets runtime.
*/
protected void initialize() {
if (LOGGER.isLoggable(FINE)) {
LOGGER.fine("Initializing FaceletViewHandlingStrategy");
}
initializeMappings();
metadataCache = new Cache<>(ccResource -> {
FacesContext context = FacesContext.getCurrentInstance();
return FaceletViewHandlingStrategy.this.createComponentMetadata(context, ccResource);
});
try {
responseBufferSizeSet = webConfig.isSet(FaceletsBufferSize);
responseBufferSize = Integer.parseInt(webConfig.getOptionValue(FaceletsBufferSize));
} catch (NumberFormatException nfe) {
responseBufferSize = Integer.parseInt(FaceletsBufferSize.getDefaultValue());
}
if (LOGGER.isLoggable(FINE)) {
LOGGER.fine("Initialization Successful");
}
vdlFactory = (ViewDeclarationLanguageFactory) FactoryFinder.getFactory(VIEW_DECLARATION_LANGUAGE_FACTORY);
FacesContext context = FacesContext.getCurrentInstance();
ExternalContext extContext = context.getExternalContext();
Map appMap = extContext.getApplicationMap();
@SuppressWarnings("unchecked")
Map> contractDataStructure = (Map>) appMap.remove(RESOURCE_LIBRARY_CONTRACT_DATA_STRUCTURE_KEY);
if (!isEmpty(contractDataStructure)) {
contractMappings = new ConcurrentHashMap<>();
for (Map.Entry> cur : contractDataStructure.entrySet()) {
contractMappings.put(cur.getKey(), new CopyOnWriteArrayList<>(cur.getValue()));
cur.getValue().clear();
}
contractDataStructure.clear();
}
if (context != null) {
StateManager stateManager = Util.getStateManager(context);
if (stateManager != null) {
isTrinidadStateManager = stateManager.getClass().getName().contains("trinidad");
}
}
}
/**
* Initialize mappings, during the first request.
*/
protected void initializeMappings() {
String viewMappings = webConfig.getOptionValue(FaceletsViewMappings);
if (viewMappings != null && viewMappings.length() > 0) {
Map appMap = FacesContext.getCurrentInstance().getExternalContext().getApplicationMap();
String[] mappingsArray = split(appMap, viewMappings, ";");
List extensionsList = new ArrayList<>(mappingsArray.length);
List prefixesList = new ArrayList<>(mappingsArray.length);
for (String aMappingsArray : mappingsArray) {
String mapping = aMappingsArray.trim();
int mappingLength = mapping.length();
if (mappingLength <= 1) {
continue;
}
if (mapping.charAt(0) == '*') {
extensionsList.add(mapping.substring(1));
} else if (mapping.charAt(mappingLength - 1) == '*') {
prefixesList.add(mapping.substring(0, mappingLength - 1));
}
}
extensionsArray = new String[extensionsList.size()];
extensionsList.toArray(extensionsArray);
prefixesArray = new String[prefixesList.size()];
prefixesList.toArray(prefixesArray);
}
}
/**
* @param context the {@link FacesContext} for the current request
* @return a {@link ResponseWriter} for processing the request
* @throws IOException if the writer cannot be created
*/
protected ResponseWriter createResponseWriter(FacesContext context) throws IOException {
ExternalContext extContext = context.getExternalContext();
RenderKit renderKit = context.getRenderKit();
// Avoid a cryptic NullPointerException when the renderkit ID
// is incorrectly set
if (renderKit == null) {
String id = context.getViewRoot().getRenderKitId();
throw new IllegalStateException("No render kit was available for id \"" + id + "\"");
}
if (responseBufferSizeSet) {
// set the buffer for content
extContext.setResponseBufferSize(responseBufferSize);
}
// get our content type
String contentType = (String) context.getAttributes().get("facelets.ContentType");
// get the encoding
String encoding = (String) context.getAttributes().get(FACELETS_ENCODING_KEY);
// Create a dummy ResponseWriter with a bogus writer,
// so we can figure out what content type the ReponseWriter
// is really going to ask for
ResponseWriter writer = renderKit.createResponseWriter(NullWriter.INSTANCE, contentType, encoding);
contentType = getResponseContentType(context, writer.getContentType());
encoding = getResponseEncoding(context, writer.getCharacterEncoding());
// apply them to the response
char[] buffer = new char[1028];
HtmlUtils.writeTextForXML(writer, contentType, buffer);
String str = String.valueOf(buffer).trim();
extContext.setResponseContentType(str);
extContext.setResponseCharacterEncoding(encoding);
// Now, clone with the real writer
writer = writer.cloneWithWriter(extContext.getResponseOutputWriter());
return writer;
}
/**
* Handles the case where rendering throws an Exception.
*
* @param context the {@link FacesContext} for the current request
* @param e the caught Exception
* @throws IOException if the custom debug content cannot be written
*/
protected void handleRenderException(FacesContext context, Exception e) throws IOException {
// Always log
if (LOGGER.isLoggable(SEVERE)) {
UIViewRoot root = context.getViewRoot();
StringBuffer sb = new StringBuffer(64);
sb.append("Error Rendering View");
if (root != null) {
sb.append('[');
sb.append(root.getViewId());
sb.append(']');
}
LOGGER.log(SEVERE, sb.toString(), e);
}
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else if (e instanceof IOException) {
throw (IOException) e;
} else {
throw new FacesException(e.getMessage(), e);
}
}
/**
* Handles the case where a Facelet cannot be found.
*
* @param context the {@link FacesContext} for the current request
* @param viewId the view ID that was to be mapped to a Facelet
* @param message optional message to include in the 404
* @throws IOException if an error occurs sending the 404 to the client
*/
protected void handleFaceletNotFound(FacesContext context, String viewId, String message) throws IOException {
context.getExternalContext().responseSendError(SC_NOT_FOUND, message != null ? viewId + ": " + message : viewId);
context.responseComplete();
}
/**
* @param context the {@link FacesContext} for the current request
* @param orig the original encoding
* @return the encoding to be used for this response
*/
protected String getResponseEncoding(FacesContext context, String orig) {
String encoding = orig;
// 1. get it from request
encoding = context.getExternalContext().getRequestCharacterEncoding();
// 2. get it from the session
if (encoding == null) {
if (null != context.getExternalContext().getSession(false)) {
Map sessionMap = context.getExternalContext().getSessionMap();
encoding = (String) sessionMap.get(CHARACTER_ENCODING_KEY);
if (LOGGER.isLoggable(FINEST)) {
LOGGER.log(FINEST, "Session specified alternate encoding {0}", encoding);
}
}
}
// see if we need to override the encoding
Map